| Crates.io | strict-path |
| lib.rs | strict-path |
| version | 0.1.1 |
| created_at | 2025-09-11 22:28:56.119393+00 |
| updated_at | 2026-01-18 20:31:44.376801+00 |
| description | Handle paths from external or unknown sources securely. Defends against 19+ real-world CVEs including symlinks, Windows 8.3 short names, and encoding tricks and exploits. |
| homepage | https://github.com/DK26/strict-path-rs |
| repository | https://github.com/DK26/strict-path-rs |
| max_upload_size | |
| id | 1834690 |
| size | 570,903 |
Handle paths from external or unknown sources securely. strict-path defends against 19+ real-world CVEs including symlinks, Windows 8.3 short names, and encoding tricks and exploits.
Analogy:
strict-pathis to paths what prepared statements are to SQL.
[dependencies]
strict-path = "0.1"
use strict_path::StrictPath;
// GET /download?file=report.pdf
let untrusted_user_input = request.query_param("file").to_string(); // Untrusted: "report.pdf" or "../../etc/passwd"
let file = StrictPath::with_boundary("/var/app/downloads")?
.strict_join(&untrusted_user_input)?; // Validates untrusted input - attack blocked!
let contents = file.read()?; // Safe built-in I/O
send_response(contents);
Note: Our doc comments and LLM_CONTEXT_FULL.md are designed for LLMs with function callingβenabling AI agents to use this crate safely and correctly for file and path operations.
π€ LLM agent prompt (copy/paste)
Fetch and follow this reference (single source of truth): https://github.com/DK26/strict-path-rs/blob/main/LLM_CONTEXT_FULL.mdContext7 Style
Fetch and follow this reference (single source of truth): https://github.com/DK26/strict-path-rs/blob/main/LLM_CONTEXT.md
π New to strict-path? Start with the Tutorial: Chapter 1 - The Basic Promise β
strict-path handles edge cases you'd never think to check:
soft-canonicalize foundation: Battle-tested against 19+ real-world path CVE scenarios./.. components, handles race conditionsPROGRA~1), UNC paths, NTFS ADS, encoding tricksstrict_join() make security visible in code reviewReal attacks we handle automatically:
../../../etc/passwd)PROGRA~1 β Program Files)file.txt:hidden:$DATA)..β..βetcβpasswd)file.txt\x00.pdf)../\../etc/passwd)\\?\C:\..\..\etc\passwd)Recently Addressed CVEs:
What This Is NOT:
π Read our complete security methodology β | π Built-in I/O Methods β
Choose StrictPath (90% of cases):
Choose VirtualPath (10% of cases):
π Complete Decision Matrix β | π More Examples β
API Philosophy: Minimal, restrictive, and explicitβdesigned to prevent and easily detect both human and LLM agent API misuse. Security is prioritized above performance; if your use case doesn't involve symlinks and you need to squeeze every bit of performance, a lexical-only solution may be a better fit.
strict-pathaccesses the disk to validate and secure paths, by resolving all its components. This predicts correctly where a path would end-up leading to on a disk filesystem by simulating disk access. This method ignores anything a hacker could put as input path string, since we validate only against where the file being accessed from or written to, would end up being.
PathBoundary is a special type that represents a boundary for paths. It is optional, and could be used to express parts in our code where we expect a path to represent a boundary path:
use strict_path::PathBoundary;
// Prevents CVE-2018-1000178 (Zip Slip) automatically (https://snyk.io/research/zip-slip-vulnerability)
fn extract_archive(
extraction_dir: PathBoundary,
archive_entries: impl IntoIterator<Item=(String, Vec<u8>)>) -> std::io::Result<()> {
for (entry_path, data) in archive_entries {
// Malicious paths like "../../../etc/passwd" β Err(PathEscapesBoundary)
let safe_file = extraction_dir.strict_join(&entry_path)?;
safe_file.create_parent_dir_all()?;
safe_file.write(&data)?;
}
Ok(())
}
The equivalent
PathBoundaryforVirtualPathtype is theVirtualRoottype.
use strict_path::VirtualRoot;
// No path-traversal or symlinks, could escape a tenant.
// Everything is clamped to the virtual root, including symlink resolutions.
fn handle_file_request(tenant_id: &str, requested_path: &str) -> std::io::Result<Vec<u8>> {
let tenant_root = VirtualRoot::try_new_create(format!("./tenants/{tenant_id}"))?;
// "../../other_tenant/secrets.txt" β clamped to "/other_tenant/secrets.txt" in THIS tenant
let user_file = tenant_root.virtual_join(requested_path)?;
user_file.read()
}
StrictPath<Marker> enables domain separation and authorization at compile time:
struct UserFiles;
struct SystemFiles;
fn process_user(f: &StrictPath<UserFiles>) -> Vec<u8> { f.read().unwrap() }
let user_boundary = PathBoundary::<UserFiles>::try_new_create("./data/users")?;
let sys_boundary = PathBoundary::<SystemFiles>::try_new_create("./system")?;
let user_input = get_filename_from_request();
let user_file = user_boundary.strict_join(user_input)?;
process_user(&user_file); // β
OK - correct marker type
let sys_file = sys_boundary.strict_join("config.toml")?;
// process_user(&sys_file); // β Compile error - wrong marker type!
π Complete Marker Tutorial β - Authorization patterns, permission matrices,
change_marker()usage
soft-canonicalizeCompared with manual soft-canonicalize path validations:
soft-canonicalize = low-level path resolution engine (returns PathBuf)strict-path = high-level security API (returns StrictPath<Marker> with compile-time guarantees: fit for LLM era)π Security Methodology β | π Anti-Patterns Guide β
Compose with standard Rust crates for complete solutions:
| Integration | Purpose | Guide |
|---|---|---|
| tempfile | Secure temp directories | Guide |
| dirs | OS standard directories | Guide |
| app-path | Application directories | Guide |
| serde | Safe deserialization | Guide |
| Axum | Web server extractors | Tutorial |
| Archives | ZIP/TAR extraction | Guide |
soft-canonicalize - Path resolution engineπ Complete Guide & Examples | π API Docs | π§ Choosing Canonicalized vs Lexical Solution
MIT OR Apache-2.0