shell-sanitize 0.1.0

Type-safe input sanitizer for shell arguments and file paths — rejects injection, traversal, and expansion attacks
Documentation
  • Coverage
  • 100%
    19 out of 19 items documented4 out of 4 items with examples
  • Size
  • Source code size: 70 kB This is the summed size of all the files inside the crates.io package for this release.
  • Documentation size: 4.34 MB This is the summed size of all files generated by rustdoc for all configured targets
  • Ø build duration
  • this release: 13s Average build duration of successful builds.
  • all releases: 13s Average build duration of successful builds in releases after 2024-10-23.
  • Links
  • Homepage
  • Repository
  • crates.io
  • Dependencies
  • Versions
  • Owners
  • ynishi

shell-sanitize

Type-safe input sanitization for shell arguments and file paths.

Reject, don't escape — this crate rejects dangerous input with a clear error instead of trying to transform it into something "safe".

When to use this crate

Prefer [std::process::Command] when possible. Passing arguments via Command::new("git").arg(user_input) bypasses the shell entirely and is always the safest option.

This crate is for situations where shell evaluation is unavoidable:

Scenario Why you can't avoid the shell
SSH remote commands Remote side evaluates through shell
docker exec ctr sh -c "..." Container-side shell
CI/CD pipeline run: blocks YAML → shell evaluation
AI agent tool execution LLM output may reach a shell
Legacy system() / popen() API forces shell involvement

It is also valuable for path validation even without shell involvement: blocking ../../etc/passwd in upload paths, config file references, and template includes.

Crates

Crate Description
shell-sanitize Core framework: Rule trait, Sanitizer builder, Sanitized<T> proof type
shell-sanitize-rules Built-in rules and ready-made presets

Quick start

[dependencies]
shell-sanitize-rules = "0.1"
use shell_sanitize_rules::presets;

// AI agent validates a file path argument
let s = presets::file_path();
assert!(s.sanitize("uploads/photo.jpg").is_ok());
assert!(s.sanitize("../../etc/passwd").is_err());

// Value interpolated into `sh -c "..."`
let s = presets::shell_command();
assert!(s.sanitize("my-branch").is_ok());
assert!(s.sanitize("branch; rm -rf /").is_err());

Presets

Preset Target context Rules
command_arg() Command::new().arg() ControlChar
shell_command() sh -c, SSH, popen ShellMeta + ControlChar + EnvExpansion + Glob
file_path() Upload dest, include PathTraversal + ControlChar
file_path_absolute() Config file, absolute OK PathTraversal(allow_abs) + ControlChar
strict() SSH remote path ops, max protection All 5 rules

Custom rules

Implement the Rule trait from shell-sanitize to create your own rules:

use shell_sanitize::{Rule, RuleResult, RuleViolation, Sanitizer, ShellArg};

struct MaxLengthRule(usize);

impl Rule for MaxLengthRule {
    fn name(&self) -> &'static str { "max_length" }

    fn check(&self, input: &str) -> RuleResult {
        if input.len() > self.0 {
            Err(vec![RuleViolation::new("max_length", "input too long")])
        } else {
            Ok(())
        }
    }
}

let sanitizer = Sanitizer::<ShellArg>::builder()
    .add_rule(MaxLengthRule(255))
    .build();

Defense in depth for AI agents

AI Agent Framework
┌──────────────────────────────────────────┐
│                                          │
│   Path-based tools        Bash tool      │
│   (Read/Write/Glob)       (free-form)    │
│         │                      │         │
│         ▼                      ▼         │
│   ★ shell-sanitize ★     Sandbox/Container
│   file_path() preset     (OS-level isolation)
│   file_path_absolute()                   │
│                                          │
└──────────────────────────────────────────┘

Scope: argument validation, not command validation

This crate validates individual arguments and paths — it does not parse or validate entire shell command strings. Sanitizing an entire command string would break legitimate syntax (pipes, redirects, subshells). Separate the trusted command structure from untrusted data, then validate only the data.

Known limitations

  • Free-form command strings — use sandbox/container isolation
  • Argument injection (--upload-pack=evil) — use -- separators or command-specific validation
  • URL-encoded bypasses (%2e%2e) — decode input before sanitizing
  • Semantic attacks — a path like safe/but/wrong/file.txt passes all rules but may still be the wrong file

License

Licensed under either of

at your option.