mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-18 17:54:47 +00:00
fix(canvas): reject malformed document paths
This commit is contained in:
@@ -13,7 +13,7 @@ Docs: https://docs.openclaw.ai
|
||||
|
||||
- CLI tables: preserve muted/color styling on wrapped continuation lines after multiline cells, keeping `openclaw plugins list` descriptions readable.
|
||||
- iOS: restore first-use Contacts, Calendar, and Reminders permission prompts and add Privacy & Access status/actions in Settings. Thanks @BunsDev.
|
||||
- Canvas: return not found for malformed percent-encoded Canvas/A2UI asset paths and keep decoded parent traversal blocked before path normalization.
|
||||
- Canvas: return not found for malformed percent-encoded Canvas/A2UI/document asset paths and keep decoded parent traversal blocked before path normalization.
|
||||
- Agents: allow dot-dot-prefixed filenames such as `..note.txt` through sandbox FS bridge, remote sandbox reads, and apply_patch summaries without mistaking the name for parent traversal.
|
||||
- CLI/migrate: humanize Codex conflict-status messaging across the migrate UI so selection prompts and plan/result rows say "Codex skill already installed in workspace" instead of surfacing internal `MIGRATION_REASON_*` codes. Thanks @sjf.
|
||||
- CLI/migrate: render migrate result rows with distinct glyphs for manual-review (🔍) and archive (📖) items instead of the misleading "skipped" and "migrated" checkmarks, so users can see which entries still need attention versus which were filed away. Thanks @sjf.
|
||||
|
||||
@@ -239,4 +239,26 @@ describe("canvas documents", () => {
|
||||
),
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
it("rejects malformed encoded hosted canvas document paths", async () => {
|
||||
const stateDir = await mkdtemp(path.join(tmpdir(), "openclaw-canvas-documents-"));
|
||||
tempDirs.push(stateDir);
|
||||
const documentId = "cv_malformed";
|
||||
const documentDir = resolveCanvasDocumentDir(documentId, { stateDir });
|
||||
await mkdir(documentDir, { recursive: true });
|
||||
await writeFile(path.join(documentDir, "%E0%A4%A.html"), "literal-percent-name", "utf8");
|
||||
|
||||
expect(
|
||||
resolveCanvasHttpPathToLocalPath(
|
||||
`/__openclaw__/canvas/documents/${documentId}/%E0%A4%A.html`,
|
||||
{ stateDir },
|
||||
),
|
||||
).toBeNull();
|
||||
expect(
|
||||
resolveCanvasHttpPathToLocalPath(
|
||||
`/__openclaw__/canvas/documents/${documentId}/%25E0%25A4%25A.html`,
|
||||
{ stateDir },
|
||||
),
|
||||
).toBe(path.join(documentDir, "%E0%A4%A.html"));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -153,16 +153,17 @@ export function resolveCanvasHttpPathToLocalPath(
|
||||
}
|
||||
const pathWithoutQuery = trimmed.replace(/[?#].*$/, "");
|
||||
const relative = pathWithoutQuery.slice(prefix.length);
|
||||
const segments = relative
|
||||
.split("/")
|
||||
.map((segment) => {
|
||||
try {
|
||||
return decodeURIComponent(segment);
|
||||
} catch {
|
||||
return segment;
|
||||
}
|
||||
})
|
||||
.filter(Boolean);
|
||||
const segments: string[] = [];
|
||||
for (const segment of relative.split("/")) {
|
||||
if (!segment) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
segments.push(decodeURIComponent(segment));
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
if (segments.length < 2) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user