# strict-path
[](https://crates.io/crates/strict-path)
[](https://docs.rs/strict-path)
[](https://github.com/DK26/strict-path-rs#license)
[](https://github.com/DK26/strict-path-rs/actions/workflows/ci.yml)
[](https://github.com/DK26/strict-path-rs/actions/workflows/audit.yml)
[](https://github.com/DK26/strict-path-rs/actions/workflows/kani.yml)
[](https://github.com/DK26/strict-path-rs/blob/main/strict-path/src/path/tests/cve_2025_11001.rs)
[](https://github.com/DK26/strict-path-rs)
**Handle paths from external or unknown sources securely.** strict-path uses Rust's type system to mathematically prove that paths always stay within defined boundariesβno escapes in any shape or form, symlinks included. Choose **StrictPath** (via `PathBoundary`) to detect and reject escape attempts, or **VirtualPath** (via `VirtualRoot`) to contain and clamp them within a virtual sandbox.
Built on [`soft-canonicalize`](https://github.com/DK26/soft-canonicalize-rs) (with [`proc-canonicalize`](https://github.com/DK26/proc-canonicalize-rs) for Linux container support), defending against 19+ real-world CVEs including symlinks, Windows 8.3 short names, and encoding tricks.
> **API Philosophy:** Minimal, restrictive, and explicitβdesigned to prevent and easily detect both human and LLM agent API misuse. Security is prioritized above performance; if your use case doesn't involve symlinks and you need to squeeze every bit of performance, a lexical-only solution may be a better fit.
---
## β‘ **Get Secure in 30 Seconds**
```toml
[dependencies]
strict-path = "0.1.0"
```
```rust
use strict_path::StrictPath;
// GET /download?file=report.pdf
let untrusted_user_input = request.query_param("file").to_string(); // Untrusted: "report.pdf" or "../../etc/passwd"
let file = StrictPath::with_boundary("/var/app/downloads")?
.strict_join(&untrusted_user_input)?; // Validates untrusted input - attack blocked!
let contents = file.read()?; // Safe built-in I/O sugar operation
```
**That's it.** Simple, safe, and path traversal attacks are blocked automatically.
> **Analogy:** `StrictPath` is 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.
### Which type should I use?
- **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):
```rust
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: Chapter 1 - The Basic Promise β](https://dk26.github.io/strict-path-rs/tutorial/chapter1_basic_promise.html)**
> **Note:** Our doc comments and [LLM_CONTEXT_FULL.md](https://github.com/DK26/strict-path-rs/blob/main/LLM_CONTEXT_FULL.md) are designed for LLMs with function callingβenabling AI agents to use this crate safely and correctly for file and path operations.
>
> ### π€ LLM agent prompt (copy/paste)
>
> ```
> Fetch and follow this reference (single source of truth):
> https://github.com/DK26/strict-path-rs/blob/main/LLM_CONTEXT_FULL.md
> ```
>
> #### Context7 Style
>
> ```
> Fetch and follow this reference (single source of truth):
> https://github.com/DK26/strict-path-rs/blob/main/LLM_CONTEXT.md
> ```
---
## π **More Real-World Examples**
### Archive Extraction (Zip Slip Prevention)
```rust
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)](https://snyk.io/research/zip-slip-vulnerability) automatically.
### Multi-Tenant Isolation
```rust
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()
}
```
### LLM Agent Restriction
```rust
use strict_path::PathBoundary;
fn execute_agent_file_operation(
workspace: &PathBoundary,
llm_generated_path: &str, // LLM output: could be "notes.md" or "../../../etc/passwd"
) -> std::io::Result<()> {
// LLM-generated paths are untrusted β validate before any I/O
let safe_file = workspace.strict_join(llm_generated_path)?;
safe_file.write(b"agent output")?;
Ok(())
}
```
Contains AI agents within predefined boundariesβno accidental (or intentional) escapes to sensitive system files.
---
## π‘οΈ **Complete Security Solution**
**strict-path handles edge cases you'd never think to check:**
1. **π§ [`soft-canonicalize`](https://github.com/DK26/soft-canonicalize-rs) foundation**: Battle-tested against 19+ real-world path CVEs
2. **π Full canonicalization**: Resolves symlinks, junctions, `.`/`..` components, handles race conditions
3. **π« Advanced attacks**: Catches Windows 8.3 short names (`PROGRA~1`), UNC paths, NTFS ADS, encoding tricks
4. **π Compile-time proof**: Rust's type system enforces path boundaries
5. **ποΈ Explicit operations**: Method names like `strict_join()` make security visible in code review
6. **π‘οΈ Built-in I/O**: Complete filesystem API without needing `.interop_path()`
7. **π€ LLM-aware**: Built for untrusted AI-generated paths and modern threat models
8. **β‘ 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 β](https://dk26.github.io/strict-path-rs/security_methodology.html)** | π **[Built-in I/O Methods β](https://dk26.github.io/strict-path-rs/best_practices.html#builtin-io-operations)**
## π― **StrictPath vs VirtualPath: When to Use What**
**The Core Question**: Are path escapes attacks or expected behavior?
| **StrictPath** | "Detect & reject escape attempts" | `Err(PathEscapesBoundary)` | Archive extraction, shared storage uploads |
| **VirtualPath** | "Contain & clamp escape attempts" | Clamped within virtual root | Multi-tenant uploads, per-user isolation |
**Choose StrictPath (90% of cases):**
- Archive extraction, config loading
- File uploads to shared storage (admin panels, CMS assets, single-tenant apps)
- LLM/AI agent file operations
- Shared system resources (logs, cache, assets)
- **Any case where escapes = attacks**
**Choose VirtualPath (10% of cases):**
- Multi-tenant file uploads (SaaS per-user storage, isolated user directories)
- 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 β](https://dk26.github.io/strict-path-rs/best_practices.html)** | π **[More Examples β](https://dk26.github.io/strict-path-rs/examples.html)**
---
## π§ **Compile-Time Safety with Markers**
`StrictPath<Marker>` enables **domain separation and authorization** at compile time:
```rust
struct UserFiles;
struct SystemFiles;
fn process_user(f: &StrictPath<UserFiles>) -> Vec<u8> { f.read().unwrap() }
let user_boundary = PathBoundary::<UserFiles>::try_new_create("./data/users")?;
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(&user_file); // β
OK - correct marker type
let sys_file = sys_boundary.strict_join("config.toml")?;
// process_user(&sys_file); // β Compile error - wrong marker type!
```
> π **[Complete Marker Tutorial β](https://dk26.github.io/strict-path-rs/tutorial/chapter3_markers.html)** - Authorization patterns, permission matrices, `change_marker()` usage
---
## β οΈ **Security Coverage**
**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 (returns `PathBuf`)
- `strict-path` = high-level security API (returns `StrictPath<Marker>` with compile-time guarantees)
> π **[Security Methodology β](https://dk26.github.io/strict-path-rs/security_methodology.html)** | π **[Anti-Patterns Guide β](https://dk26.github.io/strict-path-rs/anti_patterns.html)**
---
## π **Ecosystem Integration**
Compose with standard Rust crates for complete solutions:
| **tempfile** | Secure temp directories | [Guide](https://dk26.github.io/strict-path-rs/ecosystem_integration.html#tempfile) |
| **dirs** | OS standard directories | [Guide](https://dk26.github.io/strict-path-rs/os_directories.html) |
| **app-path** | Application directories | [Guide](https://dk26.github.io/strict-path-rs/ecosystem_integration.html#app-path) |
| **serde** | Safe deserialization | [Guide](https://dk26.github.io/strict-path-rs/ecosystem_integration.html#serde-and-fromstr) |
| **Axum** | Web server extractors | [Tutorial](https://dk26.github.io/strict-path-rs/axum_tutorial/overview.html) |
| **Archives** | ZIP/TAR extraction | [Guide](https://dk26.github.io/strict-path-rs/examples/archive_extraction.html) |
> π **[Complete Integration Guide β](https://dk26.github.io/strict-path-rs/ecosystem_integration.html)**
---
## π **Learn More**
- π **[API Documentation](https://docs.rs/strict-path)** - Complete API reference
- π **[User Guide](https://dk26.github.io/strict-path-rs/)** - Tutorials and patterns
- [Best Practices](https://dk26.github.io/strict-path-rs/best_practices.html) - Detailed decision matrix
- [Anti-Patterns](https://dk26.github.io/strict-path-rs/anti_patterns.html) - Common mistakes
- [Examples](https://dk26.github.io/strict-path-rs/examples.html) - Copy-paste scenarios
- π§ **[LLM_CONTEXT_FULL.md](LLM_CONTEXT_FULL.md)** - Full API reference for AI agents
- π **[LLM_CONTEXT.md](LLM_CONTEXT.md)** - Context7-style usage guide for AI agents
- π οΈ **[`soft-canonicalize`](https://github.com/DK26/soft-canonicalize-rs)** - Path resolution engine
---
## π **License**
MIT OR Apache-2.0