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