The sanitizer that strips ../ exactly once
The file upload handler of a document management service · difficulty 2/3 · ~8 min
Users of a document management service upload files into their own folder on disk. They may choose a relative path inside that folder (for example `2024/q1/report.pdf`), so subfolders are allowed. The upload handler then writes to `<uploads root>/<user id>/<sanitized path>`.
A teammate asked the AI assistant: "Write `sanitizeUploadPath(input)` that cleans the user-supplied path so it can never escape the user's folder. Keep letters, digits, dots, dashes, underscores and forward slashes; turn whitespace into underscores; drop everything else."
The code arrived with tests that pass. The contract promised by the docstring:
- The result is a relative path with no leading slash.
- The result never contains a segment that moves up the tree (`..`), whatever the input looked like.
- Only `a-z`, `A-Z`, `0-9`, `.`, `_`, `-` and `/` survive; whitespace becomes `_`.
- An input that leaves nothing usable becomes `file`.
Before it lands on the branch that writes to disk, review it with an attacker's patience.
code.ts
Visible tests (they pass on the code as given)
import { describe, it, expect } from "@debunk/harness";
import { sanitizeUploadPath } from "./code";
describe("sanitizeUploadPath", () => {
it("keeps a normal relative path with subfolders", () => {
expect(sanitizeUploadPath("2024/q1/report.pdf")).toBe("2024/q1/report.pdf");
});
it("strips directory traversal and leading slashes", () => {
expect(sanitizeUploadPath("../secret.txt")).toBe("secret.txt");
expect(sanitizeUploadPath("/etc/passwd")).toBe("etc/passwd");
});
it("replaces whitespace and drops disallowed characters", () => {
expect(sanitizeUploadPath("report final <v2>.pdf")).toBe("report_final_v2.pdf");
});
});