← All exercises

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");
  });
});