safename 0.1.0

Filename and path validation for security hardening
Documentation
  • Coverage
  • 100%
    40 out of 40 items documented8 out of 8 items with examples
  • Size
  • Source code size: 94.43 kB This is the summed size of all the files inside the crates.io package for this release.
  • Documentation size: 5.45 MB This is the summed size of all files generated by rustdoc for all configured targets
  • Ø build duration
  • this release: 17s Average build duration of successful builds.
  • all releases: 17s Average build duration of successful builds in releases after 2024-10-23.
  • Links
  • Homepage
  • theroyalwhee0/safename
    2 0 3
  • crates.io
  • Dependencies
  • Versions
  • Owners
  • theroyalwhee0

safename

Filename and path validation for security hardening, inspired by David A. Wheeler's proposed Linux safename LSM.

Problem

Unix/Linux filesystems allow almost any bytes in filenames, which creates security vulnerabilities:

  • Command injection: Filenames like -rf or --help are interpreted as flags
  • Shell expansion: ~user, $HOME, *.txt expand unexpectedly
  • Terminal attacks: Control characters can inject escape sequences
  • Path traversal: Backslashes normalize to forward slashes on some systems
  • Delimiter injection: Colons in PATH, semicolons in scripts

This library validates and sanitizes filenames to prevent these attacks.

Usage

use safename::{validate_file, is_file_safe, sanitize_file, SafeNameError};

// Check if a filename is safe
assert!(is_file_safe("normal_file.txt"));
assert!(!is_file_safe("-rf"));           // Leading dash
assert!(!is_file_safe("file\x00name"));  // Control character

// Get detailed error information
match validate_file("-rf") {
    Ok(()) => println!("Valid"),
    Err(SafeNameError::InvalidByte { index, byte }) => {
        println!("Invalid byte 0x{:02X} at index {}", byte, index);
        // Prints: Invalid byte 0x2D at index 0
    }
    Err(SafeNameError::InvalidLength { len, max }) => {
        println!("Length {} exceeds max {}", len, max);
    }
}

// Sanitize unsafe filenames (returns Result)
let safe = sanitize_file("-rf").unwrap();  // Returns b"_rf"

Path validation

use safename::{validate_path, is_path_safe, sanitize_path};

assert!(is_path_safe("/home/user/file.txt"));
assert!(!is_path_safe("/home/-rf"));  // Component starts with dash

Custom options

use safename::{validate_file_with_options, FileValidationOptions};

let opts = FileValidationOptions { max_len: 64, ..Default::default() };
assert!(validate_file_with_options(b"short.txt", &opts).is_ok());

Default Rules

Always blocked:

Bytes Description
0x00-0x1F Control characters (NUL, tab, newline, escape, etc.)
/ Path separator (cannot appear in filename)
0x7F DEL control character
0xFF Invalid UTF-8 leading byte

Position-dependent:

Position Blocked Reason
Initial - Interpreted as command-line option
Initial ~ Shell home directory expansion
Initial space Quoting bugs, argument splitting
Final space Quoting bugs, argument splitting

Feature Flags

Features are organized into tiers:

low (default)

Cross-platform safety. Includes:

  • block-colon - Blocks : (PATH injection, /etc/passwd formats)
  • block-backslash - Blocks \ (path traversal via normalization)

require-utf8 (default)

Requires valid UTF-8 encoding. Enabled by default alongside low.

require-ascii

Alternative to require-utf8 for ASCII-only environments. Blocks all bytes >= 0x80.

Note: require-utf8 and require-ascii are mutually exclusive (compile error if both enabled).

medium

Shell safety without breaking common filenames. Includes low plus:

  • block-quotes - Blocks " and '
  • block-chaining - Blocks &, ;, | (for &&, ;, ||)
  • block-redirection - Blocks |, >, <
  • block-expansion - Blocks $, %, *, ?, `

high

Maximum restriction. Includes medium plus:

  • block-brackets - Blocks (, ), [, ]
  • block-space - Blocks spaces everywhere (not just leading/trailing)

Note: These features may break common filenames like file (copy).txt or my document.pdf.

Cargo.toml

[dependencies]
safename = "0.1"                          # low (default)
safename = { version = "0.1", features = ["medium"] }
safename = { version = "0.1", features = ["high"] }
safename = { version = "0.1", default-features = false }  # minimal
safename = { version = "0.1", default-features = false, features = ["require-ascii"] }  # ASCII-only

Background

Inspired by David A. Wheeler's work on safe filenames:

License

Copyright 2025 Adam Mill

Licensed under the Apache License, Version 2.0. See LICENSE.txt for details.