safename 0.1.0

Filename and path validation for security hardening
Documentation
# 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

```rust
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

```rust
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

```rust
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

```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:

- [Fixing Unix/Linux Filenames]https://dwheeler.com/essays/fixing-unix-linux-filenames.html
- [LWN: Restricting pathnames]https://lwn.net/Articles/686021/

## License

Copyright 2025 Adam Mill

Licensed under the Apache License, Version 2.0. See [LICENSE.txt](LICENSE.txt) for details.