path_jail
A zero-dependency filesystem sandbox for Rust. Restricts paths to a root directory, preventing traversal attacks while supporting files that don't exist yet.
The Problem
The standard approach fails for new files:
// This breaks if the file doesn't exist yet!
let path = root.join.canonicalize?;
if !path.starts_with
The Solution
// One-liner for simple cases
let path = join?;
write?;
// Blocked: returns Err(EscapedRoot)
join?;
For multiple paths, create a Jail and reuse it:
use Jail;
let jail = new?;
let path1 = jail.join?;
let path2 = jail.join?;
Features
- Zero dependencies - only stdlib
- Symlink-safe - resolves and validates symlinks
- Works for new files - validates paths that don't exist yet
- Helpful errors - tells you what went wrong and why
Security
| Attack | Example | Blocked |
|---|---|---|
| Path traversal | ../../etc/passwd |
Yes |
| Symlink escape | link -> /etc |
Yes |
| Symlink chains | a -> b -> /etc |
Yes |
| Broken symlinks | link -> /nonexistent |
Yes |
| Absolute injection | /etc/passwd |
Yes |
| Parent escape | foo/../../secret |
Yes |
Limitations
This library validates paths. It does not hold file descriptors.
There is a TOCTOU (time-of-check time-of-use) race condition. If an attacker has write access to the jail directory, they could swap a directory with a symlink between validation and use.
Defends against:
- Logic errors in path construction
- Confused deputy attacks from untrusted input
Does not defend against:
- Malicious local processes racing your I/O
For kernel-enforced sandboxing, use cap-std.
API
One-shot validation
// Validate and join in one call
let safe: PathBuf = join?;
Reusable jail
use Jail;
// Create a jail (root must exist and be a directory)
let jail = new?;
// Get the canonicalized root
let root: &Path = jail.root;
// Safely join a relative path
let path: PathBuf = jail.join?;
// Check if an absolute path is inside the jail
let verified: PathBuf = jail.contains?;
// Get relative path for database storage
let rel: PathBuf = jail.relative?; // "subdir/file.txt"
Want Type-Safe Paths?
If you want to enforce validated paths at compile time, use the newtype pattern:
use ;
use ;
/// A path verified to be inside a jail.
;
// Now your functions can require JailedPath
This makes "confused deputy" bugs a compile error: you cannot accidentally pass an unvalidated PathBuf where a JailedPath is expected.
Alternatives
| path_jail | strict-path | cap-std | |
|---|---|---|---|
| Approach | Path validation | Type-safe path system | File descriptors |
| Returns | std::path::PathBuf |
Custom StrictPath<T> |
Custom Dir/File |
| Dependencies | 0 | ~5 | ~10 |
| TOCTOU-safe | No | No | Yes |
| Best for | Simple file sandboxing | Complex type-safe paths | Kernel-enforced security |
strict-path- More comprehensive, uses marker types for compile-time guaranteescap-std- Capability-based, TOCTOU-safe, but different API thanstd::fs
License
MIT OR Apache-2.0