From 9e1d4d86fbc338e3038a0169b3921e4112f53e21 Mon Sep 17 00:00:00 2001 From: OpenClaw Subagent Date: Wed, 18 Mar 2026 17:03:56 +0100 Subject: [PATCH] fix: sanitize path traversal in filename (TDD) --- src/__tests__/sanitize.test.ts | 31 +++++++++++++++++++++++++++++++ src/utils/sanitize.ts | 17 +++++++++++++++-- 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/src/__tests__/sanitize.test.ts b/src/__tests__/sanitize.test.ts index 550ecd0..f332026 100644 --- a/src/__tests__/sanitize.test.ts +++ b/src/__tests__/sanitize.test.ts @@ -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"); + }); }); diff --git a/src/utils/sanitize.ts b/src/utils/sanitize.ts index 8ad506d..5aa8c14 100644 --- a/src/utils/sanitize.ts +++ b/src/utils/sanitize.ts @@ -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; }