strict_path/
lib.rs

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