fix: sanitize path traversal in filename (TDD)
Some checks failed
Build & Deploy to Staging / Build & Deploy to Staging (push) Failing after 2m0s

This commit is contained in:
OpenClaw Subagent 2026-03-18 17:03:56 +01:00
parent f0cb83a901
commit 9e1d4d86fb
2 changed files with 46 additions and 2 deletions

View file

@ -21,4 +21,35 @@ describe("sanitizeFilename", () => {
it("supports custom default name", () => {
expect(sanitizeFilename("", "invoice.pdf")).toBe("invoice.pdf");
});
// Path traversal security tests
it("removes path traversal attempts with ../", () => {
const result = sanitizeFilename("../../etc/passwd");
expect(result).not.toContain("/");
expect(result).not.toContain("..");
});
it("removes path traversal with single ../", () => {
const result = sanitizeFilename("../secret.pdf");
expect(result).not.toContain("/");
expect(result).not.toContain("..");
});
it("replaces forward slashes in folder/file paths", () => {
const result = sanitizeFilename("folder/file.pdf");
expect(result).not.toContain("/");
expect(result).toContain("_");
});
it("handles whitespace-only filename", () => {
expect(sanitizeFilename(" ")).toBe("document.pdf");
});
it("handles filename with only special characters", () => {
expect(sanitizeFilename("///...")).toBe("document.pdf");
});
it("still handles null bytes correctly", () => {
expect(sanitizeFilename("file\x00.pdf")).toBe("file_.pdf");
});
});

View file

@ -1,4 +1,17 @@
export function sanitizeFilename(name: string, defaultName = "document.pdf"): string {
const sanitized = String(name || "").replace(/[\x00-\x1f"'\\\r\n]/g, "_").trim().substring(0, 200);
return sanitized || defaultName;
// Replace control chars, quotes, backslashes, AND forward slashes
let sanitized = String(name || "")
.replace(/[\x00-\x1f"'\\\r\n/]/g, "_")
.trim()
.substring(0, 200);
// Replace any sequence of dots (including ".." path traversal)
sanitized = sanitized.replace(/\.{2,}/g, "_");
// Strip leading dots to prevent hidden files or remaining single dots
sanitized = sanitized.replace(/^\.+/, "");
// If result is empty or only underscores/dots/whitespace, return default
const cleaned = sanitized.replace(/[_.\s]/g, "");
return cleaned ? sanitized.trim() : defaultName;
}