strict-path 0.1.1

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.
Documentation

strict-path

Crates.io Documentation License: MIT OR Apache-2.0 CI Security Audit Kani Verified Protected CVEs Type-State Police

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-path is to paths what prepared statements are to SQL.

⚑ Get Secure in 30 Seconds

[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.md

Context7 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 β†’

πŸ›‘οΈ Complete Path Security

strict-path handles edge cases you'd never think to check:

  1. πŸ”§ soft-canonicalize foundation: Battle-tested against 19+ real-world path CVE scenarios
  2. πŸ”— Full canonicalization: Resolves symlinks, junctions, ./.. components, handles race conditions
  3. 🚫 Advanced attacks: Catches Windows 8.3 short names (PROGRA~1), UNC paths, NTFS ADS, encoding tricks
  4. πŸ“ Compile-time proof: Rust's type system enforces path boundaries
  5. πŸ‘οΈ Explicit operations: Method names like strict_join() make security visible in code review
  6. πŸ›‘οΈ Built-in I/O: Complete filesystem API
  7. πŸ€– LLM-aware: Built for untrusted AI-generated code and modern threat models
  8. ⚑ Dual modes: PathBoundary (detection & rejection) or VirtualRoot (clamping & containing)

Real attacks we handle automatically:

  • Path traversal (../../../etc/passwd)
  • Symlink/junction escapes
  • Windows 8.3 short names (PROGRA~1 β†’ Program Files)
  • NTFS Alternate Data Streams (file.txt:hidden:$DATA)
  • Unicode normalization bypasses (..βˆ•..βˆ•etcβˆ•passwd)
  • Null byte injection (file.txt\x00.pdf)
  • Mixed separators (../\../etc/passwd)
  • UNC path tricks (\\?\C:\..\..\etc\passwd)
  • Archive attacks (Zip slip - CVE-2018-1000178)
  • Race conditions (TOCTOU - CVE-2022-21658)

Recently Addressed CVEs:

  • CVE-2025-8088 (WinRAR): NTFS Alternate Data Stream traversal
  • CVE-2022-21658: Race condition protection (TOCTOU)
  • CVE-2019-9855, CVE-2020-12279, CVE-2017-17793: Windows 8.3 short names

What This Is NOT:

  • ❌ Not just string checking (handles symlinks, Windows quirks)
  • ❌ Not a kernel based sandbox (path-level security only)

πŸ“– Read our complete security methodology β†’ | πŸ“š Built-in I/O Methods β†’

🎯 StrictPath vs VirtualPath: When to Use What

Which type should I use?

  • Path/PathBuf (std): When the path comes from a safe source within your control, not external input.
  • StrictPath: When you want to restrict paths to a specific boundary and error if they escape.
  • VirtualPath: When you want to provide path freedom under isolation.

Choose StrictPath (90% of cases):

  • Archive extraction, config loading
  • File uploads to shared storage (admin panels, CMS assets, single-tenant apps)
  • LLM/AI agent file operations
  • Shared system resources (logs, cache, assets)
  • Any case where escaping a path boundary, is considered malicious

Choose VirtualPath (10% of cases):

  • Multi-tenant file uploads (SaaS per-user storage, isolated user directories)
  • Multi-tenant isolation (per-user filesystem views)
  • Malware analysis sandboxes
  • Container-like plugins
  • Any case where you would like to allow freedom of operations under complete isolation

πŸ“– 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-path accesses 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.


πŸš€ More Real-World Examples

Archive Extraction (Zip Slip Prevention)

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 PathBoundary for VirtualPath type is the VirtualRoot type.

Multi-Tenant Isolation

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()
}

🧠 Compile-Time Safety with Markers

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


vs soft-canonicalize

Compared 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 β†’


πŸ”Œ Ecosystem Integration

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

πŸ“š Complete Integration Guide β†’


πŸ“š Learn More

πŸ“š Complete Guide & Examples | πŸ“– API Docs | 🧭 Choosing Canonicalized vs Lexical Solution


πŸ“„ License

MIT OR Apache-2.0