cybersecurity · intermediate · ~20 min

Sanitize a user-supplied filename

Allow-list filtering, leading-dot rule, and length capping in one pass.

Challenge

Scrub an attacker-controlled filename down to a safe set of characters, in place — the standard defense against path traversal, hidden-file tricks, and shell-special bytes.

Task

Implement void sanitize_filename(char *s) that mutates s in place so that:

  • every byte NOT on the allowlist below is replaced with an underscore (_),
  • a leading . is replaced with _ (no hidden files), and
  • the result is capped at 64 characters (write '\0' at index 64).

Allowlist: ASCII letters A-Z / a-z, digits 0-9, and _ - ..

Input

  • s: a mutable, NUL-terminated filename the grader provides. May be empty or longer than 64 chars.

Output

No return value. s is modified in place to the sanitized filename.

Example

"hello.txt"             ->   "hello.txt"
"hello world.txt"       ->   "hello_world.txt"
"../etc/passwd"         ->   "_._etc_passwd"
".htaccess"             ->   "_htaccess"
"normal_name-1.tar.gz"  ->   "normal_name-1.tar.gz"
(100 'a' chars)         ->   first 64 'a's, then NUL
""                      ->   ""

Edge cases

  • Empty string: stays empty.
  • Filename longer than 64: truncated to 64 characters.
  • A name made entirely of dots/slashes: becomes underscores.

Rules

  • Use an allowlist, not a blocklist. Single pass, no allocations.

Why this matters

When a user uploads a file or names an export, the filename is attacker-controlled. Allowing arbitrary bytes invites path traversal, NUL injection, hidden-file creation (.htaccess), and shell-special bytes. A short allowlist scrub is the standard defensive answer.

Input format

A mutable, NUL-terminated filename (may be empty or over 64 chars).

Output format

No return; s is mutated in place to the sanitized, allowlisted, <=64-char name.

Constraints

Allowlist letters/digits/_-.; replace leading dot; cap at 64; single pass, no allocations.

Starter code

void sanitize_filename(char *s) { /* TODO */ }

Common mistakes

Using a deny-list (if (c == '/' || c == '\\') ...) — endlessly incomplete. Forgetting the leading-dot rule.

Edge cases to handle

Empty; leading dot; all special chars; longer than 64.

Complexity

O(strlen(s)).

Background lessons

Up next

Solve this exercise in the browser editor — compile and run against the test harness, no setup required.