π Complete Guide & Examples | π API Docs | π§ Choosing Canonicalized vs Lexical Solution
Prevent directory traversal attacks with compile-time path boundary enforcement. File paths are mathematically proven to stay within designated boundaries. Choose StrictPath to detect and reject escape attempts, or VirtualPath to contain and isolate them. Built on battle-tested canonicalization defending against 19+ real-world CVEs including symlinks, Windows 8.3 short names, and encoding tricks.
[dependencies]
strict-path = "0.1.0-rc.1"use strict_path::StrictPath;
// GET /download?file=report.pdf
let user_input = request.query_param("file"); // Untrusted: "report.pdf" or "../../etc/passwd"
let untrusted_user_input = user_input.to_string();
let file = StrictPath::with_boundary("/var/app/downloads")?
.strict_join(&untrusted_user_input)?; // Validates untrusted input - attack blocked!
let contents = file.read()?; // Built-in safe I/OThat's it. Simple, safe, and path traversal attacks are blocked automatically.
Analogy:
StrictPathis to paths what prepared statements are to SQL. The boundary is your prepared statement (the policy). The untrusted segment is the parameter (validated safely). Injection attempts become inert.
- Path/PathBuf (std): When the path comes from a safe source within your control, not external input.
- StrictPath: When you want to restrict paths to a specific boundary and error if they escape.
- VirtualPath: When you want to provide path freedom under isolation.
See the detailed decision matrix below: StrictPath vs VirtualPath: When to Use What.
For reusable boundaries (e.g., passing to functions):
use strict_path::PathBoundary;
fn extract_archive(
extraction_dir: PathBoundary,
entries: Vec<(String, Vec<u8>)>) -> std::io::Result<()> {
for (entry_name, data) in entries {
let safe_file = extraction_dir.strict_join(&entry_name)?;
safe_file.create_parent_dir_all()?;
safe_file.write(&data)?;
}
Ok(())
}π New to strict-path? Start with the Tutorial: Stage 1 - The Basic Promise β
Note: Our doc comments and LLM_API_REFERENCE.md are designed for LLMs with function callingβenabling AI agents to use this crate safely and correctly for file and path operations.
Fetch and follow this reference (single source of truth): https://github.com/DK26/strict-path-rs/blob/main/LLM_API_REFERENCE.mdFetch and follow this reference (single source of truth): https://github.com/DK26/strict-path-rs/blob/main/LLM_USER.md
use strict_path::PathBoundary;
fn extract_archive(
extraction_dir: PathBoundary,
archive_entries: impl IntoIterator<Item=(String, Vec<u8>)>) -> std::io::Result<()> {
for (entry_path, data) in archive_entries {
// Malicious paths like "../../../etc/passwd" β Err(PathEscapesBoundary)
let safe_file = extraction_dir.strict_join(&entry_path)?;
safe_file.create_parent_dir_all()?;
safe_file.write(&data)?;
}
Ok(())
}Prevents CVE-2018-1000178 (Zip Slip) automatically.
use strict_path::VirtualRoot;
fn handle_file_request(tenant_id: &str, requested_path: &str) -> std::io::Result<Vec<u8>> {
let tenant_root = VirtualRoot::try_new_create(format!("./tenants/{tenant_id}"))?;
// "../../other_tenant/secrets.txt" β clamped to "/other_tenant/secrets.txt" in THIS tenant
let user_file = tenant_root.virtual_join(requested_path)?;
user_file.read()
}strict-path handles edge cases you'd never think to check:
- π§
soft-canonicalizefoundation: Battle-tested against 19+ real-world path CVEs - π Full canonicalization: Resolves symlinks, junctions,
./..components, handles race conditions - π« Advanced attacks: Catches Windows 8.3 short names (
PROGRA~1), UNC paths, NTFS ADS, encoding tricks - π Compile-time proof: Rust's type system enforces path boundaries
- ποΈ Explicit operations: Method names like
strict_join()make security visible in code review - π‘οΈ Built-in I/O: Complete filesystem API without needing
.interop_path() - π€ LLM-aware: Built for untrusted AI-generated paths and modern threat models
- β‘ Dual modes: Strict (detect & reject) or Virtual (clamp & contain)
Real attacks we handle automatically:
- Windows 8.3 short names (
PROGRA~1βProgram Files) - NTFS Alternate Data Streams (
file.txt:hidden:$DATA) - Unicode normalization bypasses (
..β..βetcβpasswd) - Symlink time-bombs (TOCTOU race conditions)
- Mixed separators (
../\../etc/passwd) - UNC path tricks (
\\?\C:\..\..\etc\passwd)
Recently Addressed CVEs:
- CVE-2025-8088 (WinRAR): NTFS Alternate Data Stream traversal
- CVE-2022-21658: Race condition protection (TOCTOU)
- CVE-2019-9855, CVE-2020-12279, CVE-2017-17793: Windows 8.3 short names
π Read our complete security methodology β | π Built-in I/O Methods β
The Core Question: Are path escapes attacks or expected behavior?
| Mode | Philosophy | Returns on Escape | Use When |
|---|---|---|---|
| StrictPath | "Detect & reject escape attempts" | Err(PathEscapesBoundary) |
Archive extraction, file uploads |
| VirtualPath | "Contain & clamp escape attempts" | Clamped within virtual root | Multi-tenant apps, malware sandboxes |
Choose StrictPath (90% of cases):
- Archive extraction, file uploads, config loading
- LLM/AI agent file operations
- Shared system resources (logs, cache, assets)
- Any case where escapes = attacks
Choose VirtualPath (10% of cases):
- Multi-tenant isolation (per-user filesystem views)
- Malware analysis sandboxes
- Container-like plugins
- Any case where escapes = expected but must be controlled
π Complete Decision Matrix β | π More Examples β
StrictPath<Marker> enables domain separation and authorization at compile time:
struct UserFiles;
struct SystemFiles;
fn process_user(f: &StrictPath<UserFiles>) -> Vec<u8> { f.read().unwrap() }
let user_boundary = PathBoundary::<UserFiles>::try_new_create("user_data")?;
let sys_boundary = PathBoundary::<SystemFiles>::try_new_create("system")?;
let user_input = get_filename_from_request();
let user_file = user_boundary.strict_join(user_input)?;
// process_user(&sys_file)?; // β Compile error - wrong marker type!π Complete Marker Tutorial β - Authorization patterns, permission matrices,
change_marker()usage
Protects Against (99% of attacks):
- Path traversal (
../../../etc/passwd) - Symlink/junction escapes
- Archive attacks (Zip slip - CVE-2018-1000178)
- Encoding tricks (Unicode, null bytes)
- Windows attacks (8.3 names, UNC, NTFS ADS)
- Race conditions (TOCTOU - CVE-2022-21658)
What This Is / Is NOT:
- β Follows filesystem links and resolves paths properly
- β Works with your permissions (doesn't replace them)
- β Not just string checking (handles symlinks, Windows quirks)
- β Not an OS-level sandbox (path-level security)
- β Not a replacement for proper auth (validates paths, not users)
vs. soft-canonicalize:
soft-canonicalize= low-level path resolution engine (returnsPathBuf)strict-path= high-level security API (returnsStrictPath<Marker>with compile-time guarantees)
π Security Methodology β | π Anti-Patterns Guide β
Compose with standard Rust crates for complete solutions:
| Integration | Purpose | Guide |
|---|---|---|
| tempfile | Secure temp directories | Guide |
| dirs | OS standard directories | Guide |
| app-path | Application directories | Guide |
| serde | Safe deserialization | Guide |
| Axum | Web server extractors | Tutorial |
| Archives | ZIP/TAR extraction | Guide |
- π API Documentation - Complete API reference
- π User Guide - Tutorials and patterns
- Best Practices - Detailed decision matrix
- Anti-Patterns - Common mistakes
- Examples - Copy-paste scenarios
- π§ LLM_API_REFERENCE.md - Quick reference for AI agents
- π οΈ
soft-canonicalize- Path resolution engine
MIT OR Apache-2.0