📚 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—no exceptions, no escapes. 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.
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
"If you can read this file, you've already passed your first PathBoundary checkpoint."
use strict_path::PathBoundary;
// 1. Define the boundary - paths are contained within ./app/uploads_dir
// try_new() requires directory exists (use try_new_create() to create if missing)
let uploads_boundary = PathBoundary::try_new("./app/uploads_dir")?;
// 2. Validate untrusted user input against the boundary
let user_provided_path = get_filename_from_request(); // e.g., from HTTP form data
let user_file = uploads_boundary.strict_join(user_provided_path)?;
// 3. Safe I/O operations - guaranteed within boundary
user_file.create_parent_dir_all()?;
user_file.write(b"file contents")?;
let contents = user_file.read_to_string()?;
// 4. Escape attempts are detected and rejected
let malicious_input = "../../etc/passwd"; // Attacker-controlled input
match uploads_boundary.strict_join(malicious_input) {
Ok(_) => panic!("Escapes should be caught!"),
Err(e) => println!("Attack blocked: {e}"), // PathEscapesBoundary error
}With virtual-path feature enabled:
use strict_path::VirtualRoot;
// Virtual filesystem for multi-tenant isolation (requires "virtual-path" feature)
// Note: path_absolutize::absolutize_virtually REJECTS escapes (returns Err);
// VirtualPath CLAMPS them within the boundary (returns Ok, contained path)
let tenant_id = "alice";
let tenant_dir = format!("./tenant_data/{tenant_id}");
let tenant_vroot = VirtualRoot::try_new_create(tenant_dir)?;
let tenant_file = tenant_vroot.virtual_join("../../../sensitive")?;
// Escape attempt is silently clamped - stays within tenant_data
println!("Virtual path: {}", tenant_file.virtualpath_display()); // Shows: "/sensitive"use strict_path::StrictPath;
// Concise form - boundary created inline and joined in one expression
// with_boundary() requires directory exists; use with_boundary_create() to create if missing
let user_input = get_config_name(); // e.g., from CLI args or environment
let config_file = StrictPath::with_boundary("./app/config")?.strict_join(user_input)?;
config_file.write(b"settings")?;With virtual-path feature enabled:
use strict_path::VirtualPath;
// Virtual paths require dynamic tenant/user IDs for per-user isolation
let user_id = get_authenticated_user_id();
let user_dir = format!("./user_data/{user_id}");
let user_avatar = VirtualPath::with_root_create(user_dir)?.virtual_join("/profile/avatar.png")?;
user_avatar.create_parent_dir_all()?;
user_avatar.write(b"image data")?;
// Each user sees "/profile/avatar.png" but they're isolated on disk📖 New to strict-path? Start with the Tutorial: Stage 1 - The Basic Promise → to learn the core concepts step-by-step.
"One does not simply walk into /etc/passwd."
use strict_path::StrictPath;
let user_input = "../../../etc/passwd";
// ❌ This single line can destroy your server
// std::fs::write(user_input, data)?;
// ✅ This single line makes it mathematically impossible - boundary + validation chained
let result = StrictPath::with_boundary_create("./app/uploads_dir")?.strict_join(user_input)?; // Returns Err(PathEscapesBoundary) - attack blocked!virtual-path(opt-in): EnablesVirtualRoot/VirtualPathand all virtual APIs.junctions(Windows-only, opt-in): Enables built-in NTFS directory junction helpers.
Enable in Cargo.toml:
[dependencies]
strict-path = { version = "...", features = ["virtual-path"] }Windows junction helpers:
[dependencies]
strict-path = { version = "...", features = ["junctions"] }📚 Complete Integration Guide → - Full examples for tempfile, dirs, app-path, and serde patterns
The Reality: Every web server, LLM agent, and file processor faces the same vulnerability. One unvalidated path from user input, config files, or AI responses can grant attackers full filesystem access.
The Solution: Comprehensive path security with mathematical guarantees — including symlink safety, Windows path quirks, and encoding pitfalls.
Analogy:
StrictPathis to paths what a prepared statement is to SQL.
- The boundary/root you create is like preparing a statement: it encodes the policy (what’s allowed).
- The untrusted filename or path segment is like a bound parameter: it’s validated/clamped safely via
strict_join/virtual_join.- The API makes injection attempts inert: hostile inputs can’t escape the boundary, just like SQL parameters can’t change the query.
"Symlinks: the ninja assassins of your filesystem."
strict-path isn't just validation—it's a complete solution that handles edge cases you'd never think to check:
- 🔧
soft-canonicalizefoundation: Battle-tested against 19+ real-world path CVEs - 🚫 Advanced pattern detection: Catches encoding tricks, Windows 8.3 short names (
PROGRA~1), UNC paths, NTFS Alternate Data Streams, and malformed inputs - 🔗 Full canonicalization pipeline: Resolves symlinks, junctions,
.and..components, and handles filesystem race conditions - 📐 Mathematical correctness: Rust's type system provides compile-time proof of path boundaries
- 🔐 Authorization architecture: Enable compile-time authorization guarantees through marker types
- 👁️ Explicit operations: Method names like
strict_join()make security violations visible in code review - 🛡️ Safe builtin I/O operations: Complete filesystem API for everyday operations
- 🤖 LLM-aware design: Built for untrusted AI-generated paths and modern threat models
- ⚡ Dual protection modes: Choose Strict (validate & reject) or Virtual (clamp & contain) based on your use case
- 🏗️ Battle-tested architecture: Prototyped and refined across real-world production systems
- 🎯 Zero-allocation interop: Seamless integration with existing
std::pathecosystems when needed
📖 Read our complete security methodology →
Deep dive into our 7-layer security approach: from CVE research to comprehensive testing
- CVE-2025-8088 (WinRAR ADS): NTFS Alternate Data Stream traversal prevention
- CVE-2022-21658 (TOCTOU): Race condition protection during path resolution
- CVE-2019-9855, CVE-2020-12279, CVE-2017-17793: Windows 8.3 short name vulnerabilities
Your security audit becomes: "We use strict-path for comprehensive path security." ✅
📚 Built-in I/O Methods → - Complete reference for safe filesystem operations without .interop_path()
- Not just string checking: Actually follows filesystem links and resolves paths properly
- Not a simple wrapper: Built from the ground up for security, not a thin layer over existing types
- Not just removing "..": Handles symlinks, Windows short names, and other escape tricks
- Not a permission system: Works with your existing file permissions, doesn't replace them
- Not a sandbox: Secures paths at the path level, not at the OS level
soft-canonicalize is the low-level foundation; strict-path is the complete security solution built on top.
| Aspect | soft-canonicalize |
strict-path |
|---|---|---|
| Level | Low-level path resolution | High-level security API |
| Purpose | Normalize paths for comparison | Enforce path boundaries |
| Type system | Returns PathBuf |
Returns StrictPath<Marker> with compile-time guarantees |
Use soft-canonicalize for custom path security logic; use strict-path for comprehensive protection with minimal code.
[dependencies]
strict-path = "0.1.0-rc.1"use strict_path::StrictPath;
// 1. Create a boundary (your security perimeter)
// Use sugar for simple flows; switch to PathBoundary when you need reusable policy
let safe_root = StrictPath::with_boundary("uploads")?;
// 2. ANY external input becomes safe
let safe_path = safe_root.strict_join(dangerous_user_input)?; // Attack = Error
// 3. Use normal file operations - guaranteed secure
safe_path.write(file_data)?;
let info = safe_path.metadata()?; // Inspect filesystem metadata when needed
safe_path.remove_file()?; // Remove when cleanup is requiredThat's it. No complex validation logic. No CVE research. No security expertise required.
"Security is hard because the edge cases are infinite—until now."
What would you check for when validating a file path? Most developers think: "I'll block ../ and call it a day." But real attackers use techniques you've probably never heard of:
- Windows 8.3 short names:
PROGRA~1→Program Files(filesystem aliases that bypass string checks) - NTFS Alternate Data Streams:
config.txt:hidden:$DATA(secret channels in "normal" files) - Unicode normalization:
..∕..∕etc∕passwd(visually identical but different bytes) - Symlink time-bombs: Links that resolve differently between validation and use (TOCTOU)
- Mixed path separators:
../\../etc/passwd(exploiting parser differences) - UNC path shenanigans:
\\?\C:\Windows\..\..\..\etc\passwd(Windows extended paths)
The reality: You'd need months of research, testing across platforms, and deep filesystem knowledge to handle these correctly.
Our approach: We've already done the research. strict-path is built on soft-canonicalize, which has been battle-tested against 19+ real CVEs. You get comprehensive protection without becoming a path security expert.
StrictPath<Marker> enables compile-time domain separation and authorization guarantees—making wrong path usage a compiler error. Use markers like StrictPath<SystemFiles> for domain separation or StrictPath<(SystemFiles, ReadOnly)> for permission matrices.
📖 Complete Marker Tutorial → - Domain separation, authorization patterns, permission matrices, and change_marker() usage
Critical distinction - Detect vs. Contain:
StrictPath(default): Detects escape attempts and returnsErr(PathEscapesBoundary). Use when path escapes indicate malicious intent.VirtualPath(opt-in): Contains escape attempts by clamping to virtual root. Use when path escapes are expected but must be controlled.
Primary threats:
- Malicious actors: Attackers actively probe for path traversal through user inputs, config files, archives, and external APIs.
StrictPathdetects and rejects;VirtualPathcontains within isolated boundaries. - LLM agents: While generally reliable, LLMs can occasionally produce unexpected paths.
StrictPath/VirtualPathprovides insurance—validation (strict) or clamping (virtual) ensures safe operation.
| Source/Input | Choose | Why |
|---|---|---|
| HTTP/CLI args/config/LLM/DB (untrusted segments) | StrictPath |
Detect and reject attacks explicitly |
| Archive extraction, file uploads | StrictPath |
Detect malicious paths; fail on escape |
| Malware analysis sandbox, multi-tenant isolation | VirtualPath |
Contain escapes; observe safely |
| Your own code/hardcoded paths | Path/PathBuf |
You control the value |
| External APIs/webhooks/inter-service messages | StrictPath |
Validate on consume before touching FS |
Complete Decision Matrix → - Full guide with rationale, symlink behavior, edge cases, and advanced patterns
use strict_path::PathBoundary;
fn extract_zip(
extraction_dir: PathBoundary, // We set a boundary that paths cannot escape
zip_entries: impl IntoIterator<Item=(String, Vec<u8>)>) -> std::io::Result<()> {
for (name, data) in zip_entries {
// Hostile names like "../../../etc/passwd" are rejected with PathEscapesBoundary error
let safe_path = extraction_dir.strict_join(&name)?; // Zip slip detected & blocked
safe_path.create_parent_dir_all()?;
safe_path.write(&data)?;
}
Ok(())
}use strict_path::VirtualRoot;
// Each tenant gets isolated filesystem view
let tenant_id = get_authenticated_tenant_id();
let tenant_dir = format!("./app/tenant_data/{tenant_id}");
let tenant_root = VirtualRoot::try_new_create(tenant_dir)?;
// User input from request/form data
let user_input = get_document_name_from_request(); // e.g., "report.pdf" from user
let user_file = tenant_root.virtual_join(user_input)?;
user_file.create_parent_dir_all()?;
user_file.write(b"tenant data")?;
// Escape attempts are silently clamped within tenant boundary
let attack_input = "../../../etc/passwd"; // Attacker-controlled input
let escaped = tenant_root.virtual_join(attack_input)?;
println!("{}", escaped.virtualpath_display()); // Shows: "/etc/passwd" (still within tenant_root)📚 More Real-World Examples → - LLM agents, web servers, config managers, and more
What this protects against (99% of real attacks):
- Path traversal (
../../../etc/passwd) - Symlink escapes and directory bombs
- Archive extraction attacks (Zip slip)
- Encoding bypass attempts (Unicode normalization, null bytes)
- Windows-specific attacks (8.3 short names, UNC paths, NTFS ADS, junctions)
- Race conditions (TOCTOU during path resolution)
- Canonicalization edge cases (mixed separators, malformed paths)
The reality: These aren't theoretical—they're real vulnerabilities. Instead of researching each CVE, you get comprehensive protection from day one.
What requires system privileges (rare): Hard links and mount points require admin/root access. If attackers have that level of access, they've already won. This library stops the 99% of practical attacks that don't require special privileges.
📚 Complete Security Methodology → - Deep dive into our 7-layer security approach
📚 Anti-Patterns Guide → - Common mistakes and how to fix them
- 📖 Complete API Reference - Comprehensive API documentation
- 📚 User Guide & Examples - In-depth tutorials and patterns
- Best Practices (detailed decision matrix): https://dk26.github.io/strict-path-rs/best_practices.html
- Anti-Patterns (don’t-do list with fixes): https://dk26.github.io/strict-path-rs/anti_patterns.html
- Examples (copy/pasteable scenarios): https://dk26.github.io/strict-path-rs/examples.html
- 🔧 LLM_API_REFERENCE.md - Quick reference for all methods (LLM-focused)
- 🛠️
soft-canonicalize- The underlying path resolution engine
"Integrate like a pro: strict-path plays nice with everyone except attackers."
- 🗂️ OS Directories: Compose with
dirscrate for platform-specific paths - Full Guide - 📄 Serde: Use
FromStrfor safe deserialization - Integration Guide - 🧪 Temporary Files: Compose with
tempfilecrate for secure temp directories - tempfile Guide - 📦 App Paths: Compose with
app-pathcrate for application directories - app-path Guide - 🌐 Axum: Custom extractors for web servers - Complete Tutorial
- 📦 Archive Handling: Safe ZIP/TAR extraction - Extractor Guide
MIT OR Apache-2.0