mirror of
https://github.com/whyour/qinglong.git
synced 2026-10-16 22:35:32 +08:00
fix: 加固安全输入边界并修复订阅兼容性 (#3084)
* fix: harden scoped API inputs and isolate login throttling * fix: close script path gaps and harden security boundaries * fix: preserve subscription aliases and avoid extension eval * fix: allow cancelling invalid historical subscriptions * fix: make script parent validation explicit for CodeQL * fix: limit command execution and clarify trusted path branches * fix: separate diff tool script access from config APIs * fix: clean up obsolete subscription SSH credentials
This commit is contained in:
@@ -9,7 +9,7 @@ const load = require('../helpers/load-security-module.cjs');
|
||||
|
||||
test('HTTP authentication protects init, scopes, expired sessions and config secrets', async (t) => {
|
||||
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'ql-http-security-'));
|
||||
for (const dir of ['config/grpc', 'scripts', 'upload', 'tmp'])
|
||||
for (const dir of ['config/grpc', 'scripts', 'upload', 'tmp', 'bak'])
|
||||
fs.mkdirSync(path.join(tmp, dir), { recursive: true });
|
||||
fs.writeFileSync(path.join(tmp, 'config/grpc/client.key'), 'SENTINEL');
|
||||
fs.writeFileSync(path.join(tmp, 'config/normal.txt'), 'normal');
|
||||
@@ -40,6 +40,8 @@ test('HTTP authentication protects init, scopes, expired sessions and config sec
|
||||
scriptPath: path.join(tmp, 'scripts/'),
|
||||
uploadPath: path.join(tmp, 'upload'),
|
||||
tmpPath: path.join(tmp, 'tmp'),
|
||||
bakPath: path.join(tmp, 'bak'),
|
||||
writePathList: [path.join(tmp, 'scripts')],
|
||||
blackFileList: ['auth.json', 'grpc'],
|
||||
baseUrl: '/panel',
|
||||
};
|
||||
@@ -48,6 +50,16 @@ test('HTTP authentication protects init, scopes, expired sessions and config sec
|
||||
scopes: ['configs'],
|
||||
tokens: [{ value: 'config-app', expiration: Date.now() / 1000 + 3600 }],
|
||||
},
|
||||
{
|
||||
scopes: ['configs', 'scripts'],
|
||||
tokens: [
|
||||
{ value: 'script-config-app', expiration: Date.now() / 1000 + 3600 },
|
||||
],
|
||||
},
|
||||
{
|
||||
scopes: ['scripts'],
|
||||
tokens: [{ value: 'script-app', expiration: Date.now() / 1000 + 3600 }],
|
||||
},
|
||||
{
|
||||
scopes: ['envs'],
|
||||
tokens: [{ value: 'env-app', expiration: Date.now() / 1000 + 3600 }],
|
||||
@@ -68,6 +80,8 @@ test('HTTP authentication protects init, scopes, expired sessions and config sec
|
||||
getToken: (r) => (r.headers.authorization || '').replace(/^Bearer /, ''),
|
||||
getPlatform: () => 'desktop',
|
||||
getFileContentByName: (p) => fs.promises.readFile(p, 'utf8'),
|
||||
fileExist: (p) => fs.existsSync(p),
|
||||
rmPath: (p) => fs.promises.rm(p),
|
||||
},
|
||||
'../shared/i18n': { t: (x) => x },
|
||||
'../shared/store': {
|
||||
@@ -75,6 +89,7 @@ test('HTTP authentication protects init, scopes, expired sessions and config sec
|
||||
},
|
||||
'../config/serverEnv': { serveEnv: (_req, res) => res.end() },
|
||||
'../services/user': User,
|
||||
'../services/script': class {},
|
||||
'../data/open': {},
|
||||
'../data/system': {},
|
||||
'../shared/utils': {
|
||||
@@ -96,6 +111,9 @@ test('HTTP authentication protects init, scopes, expired sessions and config sec
|
||||
load(path.join(__dirname, '../../back/api/config.ts'), mocks).default(
|
||||
router,
|
||||
);
|
||||
load(path.join(__dirname, '../../back/api/script.ts'), mocks).default(
|
||||
router,
|
||||
);
|
||||
router.get('/envs', (_req, res) => res.json({ code: 200 }));
|
||||
return router;
|
||||
};
|
||||
@@ -210,4 +228,75 @@ test('HTTP authentication protects init, scopes, expired sessions and config sec
|
||||
).status,
|
||||
400,
|
||||
);
|
||||
const scriptName = 'data/scripts/test.js';
|
||||
for (const prefix of ['/open', '/panel/open']) {
|
||||
const denied = await request(
|
||||
`${prefix}/configs/save`,
|
||||
'config-app',
|
||||
'POST',
|
||||
{
|
||||
name: scriptName,
|
||||
content: 'overwrite',
|
||||
},
|
||||
);
|
||||
assert.equal(denied.body.code, 403);
|
||||
assert.equal(fs.existsSync(path.join(tmp, 'scripts/test.js')), false);
|
||||
}
|
||||
assert.equal(
|
||||
(
|
||||
await request('/open/configs/save', 'script-config-app', 'POST', {
|
||||
name: scriptName,
|
||||
content: 'authorized script',
|
||||
})
|
||||
).body.code,
|
||||
403,
|
||||
);
|
||||
assert.equal(fs.existsSync(path.join(tmp, 'scripts/test.js')), false);
|
||||
assert.equal(
|
||||
(await request(`/open/configs/detail?path=${scriptName}`, 'config-app'))
|
||||
.body.code,
|
||||
403,
|
||||
);
|
||||
assert.equal(
|
||||
(
|
||||
await request('/open/configs/save', 'config-app', 'POST', {
|
||||
name: 'normal.txt',
|
||||
content: 'normal config',
|
||||
})
|
||||
).body.code,
|
||||
200,
|
||||
);
|
||||
assert.equal(
|
||||
(
|
||||
await request('/api/configs/save', valid, 'POST', {
|
||||
name: scriptName,
|
||||
content: 'owner script',
|
||||
})
|
||||
).body.code,
|
||||
403,
|
||||
);
|
||||
for (const prefix of ['/open', '/panel/open']) {
|
||||
assert.equal(
|
||||
(
|
||||
await request(`${prefix}/scripts`, 'config-app', 'POST', {
|
||||
filename: 'test.js',
|
||||
content: 'unauthorized',
|
||||
})
|
||||
).status,
|
||||
401,
|
||||
);
|
||||
assert.equal(
|
||||
(
|
||||
await request(`${prefix}/scripts`, 'script-app', 'POST', {
|
||||
filename: 'test.js',
|
||||
content: 'authorized script',
|
||||
})
|
||||
).body.code,
|
||||
200,
|
||||
);
|
||||
assert.equal(
|
||||
fs.readFileSync(path.join(tmp, 'scripts/test.js'), 'utf8'),
|
||||
'authorized script',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user