Skip to main content

strict_path/
lib.rs

1//! # strict-path
2//!
3//! **Secure path handling for untrusted input.** Prevents directory traversal, symlink escapes,
4//! and [19+ real-world CVE attack patterns](https://dk26.github.io/strict-path-rs/security_methodology.html).
5//! If a `StrictPath` value exists, the path is proven to be inside its boundary — by construction,
6//! not by string checks.
7//!
8//! Also known as: path jailing, path sandboxing, chroot-like path restriction, path sanitization.
9//!
10//! This crate performs full normalization/canonicalization and boundary enforcement with:
11//! - Safe symlink/junction handling (including cycle detection)
12//! - Windows-specific quirks (8.3 short names, UNC and verbatim prefixes, ADS)
13//! - Robust Unicode normalization and mixed-separator handling across platforms
14//! - Canonicalized path proofs encoded in the type system
15//!
16//! If a `StrictPath<Marker>` value exists, it is already proven to be inside its
17//! designated boundary by construction — not by best-effort string checks.
18//!
19//! 📚 **[Complete Guide & Examples](https://dk26.github.io/strict-path-rs/)** | 📖 **[API Reference](https://docs.rs/strict-path)**
20//!
21//! ## Quick Start
22//!
23//! ```rust
24//! # use strict_path::StrictPath;
25//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
26//! # let temp = tempfile::tempdir()?;
27//! # let request = std::collections::HashMap::from([("file", "report.pdf")]);
28//! # std::fs::write(temp.path().join("report.pdf"), b"file contents")?;
29//! // GET /download?file=report.pdf
30//! let user_input = request.get("file").unwrap(); // Untrusted: "report.pdf" or "../../etc/passwd"
31//! let untrusted_user_input = user_input.to_string();
32//!
33//! let file: StrictPath = StrictPath::with_boundary(temp.path())?
34//!     .strict_join(&untrusted_user_input)?; // Validates untrusted input - attack blocked!
35//!
36//! let contents = file.read()?; // Built-in safe I/O
37//! # let _ = &contents; // send_response(contents);
38//! # Ok(()) }
39//! ```
40//!
41//! ## Core Types
42//!
43//! - **`StrictPath`** — The fundamental security primitive. Every `StrictPath` is mathematically proven
44//!   to be within its designated boundary via canonicalization and type-level guarantees.
45//! - **`PathBoundary`** — Creates and validates `StrictPath` instances from external input.
46//! - **`VirtualPath`** (feature `virtual-path`) — Extends `StrictPath` with user-friendly virtual root
47//!   semantics (treating the boundary as "/").
48//! - **`VirtualRoot`** (feature `virtual-path`) — Creates `VirtualPath` instances with containment semantics.
49//!
50//! **[→ Read the security methodology](https://dk26.github.io/strict-path-rs/security_methodology.html)**
51//!
52//! ## Which Type Should I Use?
53//!
54//! **`Path`/`PathBuf` (std)** — When the path comes from a safe source within your control, not external input.
55//!
56//! **`StrictPath`** — When you want to restrict paths to a specific boundary and error if they escape.
57//! - **Use for:** Archive extraction, config loading, shared system resources, file uploads to shared storage (admin panels, CMS)
58//! - **Behavior:** Returns `Err(PathEscapesBoundary)` on escape attempts (detect attacks)
59//! - **Coverage:** 90% of use cases
60//!
61//! **`VirtualPath`** (feature `virtual-path`) — When you want to provide path freedom under isolation.
62//! - **Use for:** Multi-tenant file uploads (SaaS per-user storage), malware sandboxes, security research, per-user filesystem views
63//! - **Behavior:** Silently clamps/redirects escapes within virtual boundary (contain behavior)
64//! - **Coverage:** 10% of use cases
65//!
66//! **[→ Read the detailed comparison](https://dk26.github.io/strict-path-rs/best_practices.html)**
67//!
68//! ## Type-System Guarantees
69//!
70//! Use marker types to encode policy directly in your APIs:
71//!
72//! ```rust
73//! # use strict_path::{PathBoundary, StrictPath};
74//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
75//! struct PublicAssets;
76//! struct UserUploads;
77//!
78//! # std::fs::create_dir_all("./assets")?;
79//! # std::fs::create_dir_all("./uploads")?;
80//! let assets = PathBoundary::<PublicAssets>::try_new("./assets")?;
81//! let uploads = PathBoundary::<UserUploads>::try_new("./uploads")?;
82//!
83//! // User input from request parameters, form data, database, etc.
84//! let requested_css = "style.css";      // From request: /static/style.css
85//! let uploaded_avatar = "avatar.jpg";   // From form: <input type="file">
86//!
87//! let css: StrictPath<PublicAssets> = assets.strict_join(requested_css)?;
88//! let avatar: StrictPath<UserUploads> = uploads.strict_join(uploaded_avatar)?;
89//!
90//! fn serve_public_asset(file: &StrictPath<PublicAssets>) { /* ... */ }
91//!
92//! serve_public_asset(&css);       // ✅ OK
93//! // serve_public_asset(&avatar); // ❌ Compile error (wrong marker)
94//! # std::fs::remove_dir_all("./assets").ok();
95//! # std::fs::remove_dir_all("./uploads").ok();
96//! # Ok(()) }
97//! ```
98//!
99//! ## Security Foundation
100//!
101//! Built on [`soft-canonicalize`](https://crates.io/crates/soft-canonicalize) (with
102//! [`proc-canonicalize`](https://crates.io/crates/proc-canonicalize) for Linux container realpath support),
103//! this crate protects against:
104//! - **CVE-2025-8088** (NTFS ADS path traversal)
105//! - **CVE-2022-21658** (TOCTOU attacks)
106//! - **CVE-2019-9855, CVE-2020-12279, CVE-2017-17793** (Windows 8.3 short names)
107//! - Path traversal, symlink attacks, Unicode normalization bypasses, null byte injection, race conditions
108//!
109//! > **Trade-off:** Security is prioritized above performance. This crate verifies paths on disk
110//! > and follows symlinks for validation. If your use case doesn't involve symlinks and you need
111//! > maximum performance, a lexical-only solution may be a better fit.
112//!
113//! > **Scope:** Validation happens at join-time (canonicalization + boundary check). Filesystem
114//! > changes between validation and I/O are outside scope (TOCTOU) — the same limitation as SQL
115//! > prepared statements, which prevent injection but don't protect against concurrent schema changes.
116//!
117//! **[→ Read attack surface analysis](https://dk26.github.io/strict-path-rs/security_methodology.html#12-coverage-what-we-protect-against)**
118//!
119//! ## Interop with External APIs
120//!
121//! Use `.interop_path()` to pass paths to external APIs expecting `AsRef<Path>`:
122//!
123//! ```rust
124//! # use strict_path::PathBoundary;
125//! # fn external_api<P: AsRef<std::path::Path>>(_p: P) {}
126//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
127//! let restriction: PathBoundary = PathBoundary::try_new_create("./safe")?;
128//!
129//! // User input from CLI args, API request, config file, etc.
130//! let user_input = "file.txt";
131//! let jp = restriction.strict_join(user_input)?;
132//!
133//! // ✅ Preferred: borrow as &OsStr (implements AsRef<Path>)
134//! external_api(jp.interop_path());
135//!
136//! // Escape hatches (use sparingly):
137//! let owned: std::path::PathBuf = jp.clone().unstrict();
138//! # let root_cleanup: strict_path::StrictPath = strict_path::StrictPath::with_boundary("./safe")?;
139//! # root_cleanup.remove_dir_all().ok();
140//! # Ok(()) }
141//! ```
142//!
143//! **[→ Read the anti-patterns guide](https://dk26.github.io/strict-path-rs/anti_patterns.html)**
144//!
145//! ## Critical Anti-Patterns
146//!
147//! - **NEVER wrap `.interop_path()` in `Path::new()` or `PathBuf::from()`** — defeats all security
148//! - **NEVER use std path operations on untrusted input** — use `.strict_join()`, not `Path::new().join()`
149//! - **Use `.interop_path()` directly** for external APIs — it's already `AsRef<Path>`, no wrapping needed
150//! - **Use proper display methods** — `.strictpath_display()` not `.interop_path().to_string_lossy()`
151//!
152//! Note: `.interop_path()` returns `&OsStr` (which is `AsRef<Path>`). After `.unstrict()` (explicit escape hatch), you own a `PathBuf` and can do whatever you need.
153//!
154//! **[→ See full anti-patterns list](https://dk26.github.io/strict-path-rs/anti_patterns.html)**
155//!
156//! ## Feature Flags
157//!
158//! - `virtual-path` — Enables `VirtualRoot`/`VirtualPath` for containment scenarios
159//! - `junctions` (Windows) — Built-in NTFS junction helpers for strict/virtual paths
160//!
161//! ## Ecosystem Integration
162//!
163//! Use ecosystem crates directly with `PathBoundary` for maximum flexibility:
164//! - `tempfile` — RAII temporary directories via `tempfile::tempdir()` → `PathBoundary::try_new()`
165//! - `dirs` — OS standard directories via `dirs::config_dir()` → `PathBoundary::try_new_create()`
166//! - `app-path` — Portable app paths via `AppPath::with("subdir")` → `PathBoundary::try_new_create()`
167//! - `serde` — `PathBoundary`/`VirtualRoot` implement `FromStr` for automatic deserialization
168//!
169//! **[→ See Ecosystem Integration Guide](https://dk26.github.io/strict-path-rs/ecosystem_integration.html)**
170//!
171//! **[→ Read the getting started guide](https://dk26.github.io/strict-path-rs/getting_started.html)**
172//!
173//! ## Additional Resources
174//!
175//! - **[LLM Context (Full)](https://github.com/DK26/strict-path-rs/blob/main/LLM_CONTEXT_FULL.md)** —
176//!   Concise, copy-pastable reference optimized for AI assistants
177//! - **[LLM Context (Context7)](https://github.com/DK26/strict-path-rs/blob/main/LLM_CONTEXT.md)** —
178//!   Context7-style usage guide for AI agents
179//! - **[Complete Guide](https://dk26.github.io/strict-path-rs/)** — Comprehensive documentation with examples
180//! - **[API Reference](https://docs.rs/strict-path)** — Full type and method documentation
181//! - **[Repository](https://github.com/DK26/strict-path-rs)** — Source code and issue tracker
182
183#![forbid(unsafe_code)]
184
185pub mod error;
186pub mod path;
187pub mod validator;
188
189// Public exports
190pub use error::StrictPathError;
191pub use path::strict_path::StrictPath;
192pub use validator::path_boundary::PathBoundary;
193
194// Iterator exports
195pub use path::strict_path::StrictOpenOptions;
196pub use path::strict_path::StrictReadDir;
197pub use validator::path_boundary::BoundaryReadDir;
198
199#[cfg(feature = "virtual-path")]
200pub use path::virtual_path::VirtualPath;
201
202#[cfg(feature = "virtual-path")]
203pub use path::virtual_path::VirtualReadDir;
204
205#[cfg(feature = "virtual-path")]
206pub use validator::virtual_root::VirtualRoot;
207
208#[cfg(feature = "virtual-path")]
209pub use validator::virtual_root::VirtualRootReadDir;
210
211/// Result type alias for this crate's operations.
212pub type Result<T> = std::result::Result<T, StrictPathError>;
213
214#[cfg(test)]
215mod tests;
216
217#[cfg(kani)]
218mod verification;