mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-08-17 04:34:46 +00:00
fix(core): reject Windows-style workspace artifact paths (#6483)
Reject Windows-style absolute and traversal paths in record_artifact workspacePath validation by checking portable slash-normalized path semantics. This keeps artifact metadata aligned with the workspace-relative locator contract while preserving valid relative paths. Co-authored-by: chatgpt-codex-connector[bot] <199175422+chatgpt-codex-connector[bot]@users.noreply.github.com>
This commit is contained in:
parent
58e51eb96c
commit
e83d548cd9
2 changed files with 59 additions and 12 deletions
|
|
@ -93,15 +93,58 @@ describe('RecordArtifactTool', () => {
|
|||
).toThrow(/exactly one/);
|
||||
});
|
||||
|
||||
it('rejects workspace traversal and unsafe urls before reporting success', () => {
|
||||
it('rejects workspace paths that escape the workspace', () => {
|
||||
const tool = new RecordArtifactTool();
|
||||
|
||||
expect(() =>
|
||||
tool.build({
|
||||
title: 'Escape',
|
||||
workspacePath: '../secret.txt',
|
||||
}),
|
||||
).toThrow(/workspacePath/);
|
||||
for (const workspacePath of [
|
||||
'../secret.txt',
|
||||
'..\\secret.txt',
|
||||
'..\\..\\secret.txt',
|
||||
'reports\\..\\..\\secret.txt',
|
||||
'reports/..\\..\\secret.txt',
|
||||
'C:\\tmp\\report.html',
|
||||
'C:/tmp/report.html',
|
||||
'C:tmp\\report.html',
|
||||
'\\\\server\\share\\report.html',
|
||||
'\\tmp\\report.html',
|
||||
]) {
|
||||
expect(() =>
|
||||
tool.build({
|
||||
title: 'Escape',
|
||||
workspacePath,
|
||||
}),
|
||||
).toThrow(/workspacePath/);
|
||||
}
|
||||
});
|
||||
|
||||
it('accepts safe workspace-relative artifact paths', async () => {
|
||||
const tool = new RecordArtifactTool();
|
||||
|
||||
await expect(
|
||||
tool
|
||||
.build({
|
||||
title: 'Safe report',
|
||||
workspacePath: 'reports/summary.html',
|
||||
})
|
||||
.execute(signal),
|
||||
).resolves.toMatchObject({
|
||||
artifacts: [{ workspacePath: 'reports/summary.html' }],
|
||||
});
|
||||
|
||||
await expect(
|
||||
tool
|
||||
.build({
|
||||
title: 'Windows-style relative report',
|
||||
workspacePath: 'reports\\summary.html',
|
||||
})
|
||||
.execute(signal),
|
||||
).resolves.toMatchObject({
|
||||
artifacts: [{ workspacePath: 'reports\\summary.html' }],
|
||||
});
|
||||
});
|
||||
|
||||
it('rejects unsafe urls before reporting success', () => {
|
||||
const tool = new RecordArtifactTool();
|
||||
|
||||
expect(() =>
|
||||
tool.build({
|
||||
|
|
|
|||
|
|
@ -360,14 +360,18 @@ function validateWorkspacePath(value: string): string | null {
|
|||
if (stringError) {
|
||||
return stringError;
|
||||
}
|
||||
if (path.isAbsolute(trimmed)) {
|
||||
if (
|
||||
path.isAbsolute(trimmed) ||
|
||||
path.win32.isAbsolute(trimmed) ||
|
||||
/^[A-Za-z]:/.test(trimmed)
|
||||
) {
|
||||
return '"workspacePath" must be relative to the workspace';
|
||||
}
|
||||
const normalized = path.normalize(trimmed);
|
||||
const portableNormalized = path.posix.normalize(trimmed.replace(/\\/g, '/'));
|
||||
if (
|
||||
normalized === '..' ||
|
||||
normalized.startsWith(`..${path.sep}`) ||
|
||||
path.isAbsolute(normalized)
|
||||
portableNormalized === '..' ||
|
||||
portableNormalized.startsWith('../') ||
|
||||
path.posix.isAbsolute(portableNormalized)
|
||||
) {
|
||||
return '"workspacePath" must stay inside the workspace';
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue