cybersecurity · intermediate · ~20 min

Sanitize a user-supplied filename

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

Challenge

Implement void sanitize_filename(char *s) that mutates s in place so it contains only safe characters. Replace any byte that is NOT in the allow-list with an underscore (_).

Allow-list:

  • ASCII letters A-Z, a-z
  • Digits 0-9
  • _, -, .

Additionally, the function must:

  • Replace a leading . with _ (no hidden files).
  • Cap the result at 64 characters (truncate by writing '\0' at index 64).

Examples

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

Edge cases

  • Empty string.
  • Filename longer than 64.
  • Filename consisting entirely of dots or slashes.

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

Mutable null-terminated string.

Output format

s is mutated.

Constraints

Single pass; 64-character cap. 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.