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