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