{"id":31610107,"url":"https://github.com/dk26/strict-path-rs","last_synced_at":"2026-04-22T23:07:11.796Z","repository":{"id":305023631,"uuid":"1020865350","full_name":"DK26/strict-path-rs","owner":"DK26","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.","archived":false,"fork":false,"pushed_at":"2026-04-14T21:44:18.000Z","size":8873,"stargazers_count":6,"open_issues_count":2,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-04-14T23:37:52.805Z","etag":null,"topics":["directory-traversal","file-security","filesystem-security","path-traversal-prevention","path-validation","rust","rust-crate","security","type-safety","web-security"],"latest_commit_sha":null,"homepage":"https://docs.rs/strict-path","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/DK26.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE-APACHE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2025-07-16T14:07:55.000Z","updated_at":"2026-04-14T21:31:03.000Z","dependencies_parsed_at":"2025-07-18T00:41:40.632Z","dependency_job_id":"667d0a48-1a34-43ce-9995-8e904ec58c5a","html_url":"https://github.com/DK26/strict-path-rs","commit_stats":null,"previous_names":["dk26/jailed-path-rs","dk26/strict-path-rs"],"tags_count":21,"template":false,"template_full_name":null,"purl":"pkg:github/DK26/strict-path-rs","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DK26%2Fstrict-path-rs","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DK26%2Fstrict-path-rs/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DK26%2Fstrict-path-rs/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DK26%2Fstrict-path-rs/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/DK26","download_url":"https://codeload.github.com/DK26/strict-path-rs/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DK26%2Fstrict-path-rs/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32158348,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-22T17:06:48.269Z","status":"ssl_error","status_checked_at":"2026-04-22T17:06:19.037Z","response_time":58,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["directory-traversal","file-security","filesystem-security","path-traversal-prevention","path-validation","rust","rust-crate","security","type-safety","web-security"],"created_at":"2025-10-06T09:42:17.904Z","updated_at":"2026-04-22T23:07:11.784Z","avatar_url":"https://github.com/DK26.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# strict-path\r\n\r\n[![Crates.io](https://img.shields.io/crates/v/strict-path.svg)](https://crates.io/crates/strict-path)\r\n[![Documentation](https://docs.rs/strict-path/badge.svg)](https://docs.rs/strict-path)\r\n[![License: MIT OR Apache-2.0](https://img.shields.io/badge/license-MIT%20OR%20Apache--2.0-blue.svg)](https://github.com/DK26/strict-path-rs#license)\r\n[![CI](https://github.com/DK26/strict-path-rs/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/DK26/strict-path-rs/actions/workflows/ci.yml)\r\n[![Security Audit](https://github.com/DK26/strict-path-rs/actions/workflows/audit.yml/badge.svg?branch=main)](https://github.com/DK26/strict-path-rs/actions/workflows/audit.yml)\r\n[![Kani Verified](https://github.com/DK26/strict-path-rs/actions/workflows/kani.yml/badge.svg?branch=main)](https://github.com/DK26/strict-path-rs/actions/workflows/kani.yml)\r\n[![Protected CVEs](https://img.shields.io/badge/protected%20CVEs-19%2B-brightgreen.svg)](https://github.com/DK26/strict-path-rs/blob/main/strict-path/src/path/tests/cve_2025_11001.rs)\r\n[![Type-State Police](https://img.shields.io/badge/protected%20by-Type--State%20Police-blue.svg)](https://github.com/DK26/strict-path-rs)\r\n\r\n**Secure path handling for untrusted input.** Paths from users, config files, archives, or AI agents can't escape the directory you put them in — regardless of symlinks, encoding tricks, or platform quirks. [19+ real-world CVEs covered](https://dk26.github.io/strict-path-rs/security_methodology.html#12-coverage-what-we-protect-against).\r\n\r\n\u003e Prepared statements prevent SQL injection. `strict-path` prevents path injection.\r\n\r\n## 🔍 Why String Checking Isn't Enough\r\n\r\nYou strip `..` and check for `/`. But attackers have a dozen other vectors:\r\n\r\n| Attack vector | String filter | `strict-path` |\r\n|---|---|---|\r\n| `../../../etc/passwd` | ✅ Caught (if done right) | ✅ Blocked |\r\n| Symlink inside boundary → outside | ❌ Passes silently | ✅ Symlink resolved, escape blocked |\r\n| Windows 8.3: `PROGRA~1` bypasses filter | ❌ Passes silently | ✅ Short name resolved, escape blocked |\r\n| NTFS ADS: `file.txt:secret:$DATA` | ❌ Passes silently | ✅ Blocked ([CVE-2025-8088](https://dk26.github.io/strict-path-rs/security_methodology.html)) |\r\n| Unicode tricks: `..∕` (fraction slash U+2215) | ❌ Passes silently | ✅ Blocked |\r\n| Junction/mount point → outside boundary | ❌ Passes silently | ✅ Resolved \u0026 blocked |\r\n| TOCTOU race condition (CVE-2022-21658) | ❌ No defense | ⚡ Mitigated at validation |\r\n| Null byte injection | ❌ Truncation varies | ✅ Blocked |\r\n| Mixed separators: `..\\../etc` | ❌ Often missed | ✅ Normalized \u0026 blocked |\r\n\r\n**How it works:** `strict-path` resolves the path on disk — follows symlinks, expands short names, normalizes encoding — then proves the resolved path is inside the boundary. The input string is irrelevant. Only where the path *actually leads* matters.\r\n\r\n## ⚡ Get Secure in 30 Seconds\r\n\r\n```toml\r\n[dependencies]\r\nstrict-path = \"0.2\"\r\n```\r\n\r\n```rust\r\nuse strict_path::StrictPath;\r\n\r\n// Untrusted input: user upload, API param, config value, AI agent output, archive entry...\r\nlet file = StrictPath::with_boundary(\"/var/app/downloads\")?\r\n    .strict_join(\u0026untrusted_user_input)?; // Every attack vector above → Err(PathEscapesBoundary)\r\n\r\nlet contents = file.read()?; // Built-in safe I/O — stays within the secure API\r\n\r\n// Third-party crate needs AsRef\u003cPath\u003e?\r\nthird_party::process(file.interop_path()); // \u0026OsStr (implements AsRef\u003cPath\u003e)\r\n```\r\n\r\nIf the input resolves outside the boundary — by *any* mechanism — `strict_join` returns `Err`.\r\n\r\n**What you get beyond path validation:**\r\n- 🛡️ **Built-in I/O** — `read()`, `write()`, `create_dir_all()`, `read_dir()` — no need to drop to `std::fs`\r\n- 📐 **Compile-time markers** — `StrictPath\u003cUserUploads\u003e` vs `StrictPath\u003cSystemConfig\u003e` can't be mixed up\r\n- ⚡ **Dual modes** — `StrictPath` (detect \u0026 reject escapes) or `VirtualPath` (clamp \u0026 contain)\r\n- 🤖 **LLM-ready** — doc comments and [context files](https://github.com/DK26/strict-path-rs/blob/main/LLM_CONTEXT_FULL.md) designed for AI agents with function calling\r\n\r\n**Why the API looks the way it does:**\r\n\r\nThis crate combines Rust's type system with Python's \"one obvious way to do it\" philosophy to build an API that **LLMs and humans physically cannot misuse** — wrong code doesn't compile, and the compiler itself teaches you the fix.\r\n\r\n- **No `AsRef\u003cPath\u003e`, no `Deref`** — if `StrictPath` implemented these, any code could silently call `std::fs::read(path)` and skip the boundary check entirely. There is no \"quick shortcut\" that compiles yet bypasses security. The type system rules it out.\r\n- **`interop_path()` returns `\u0026OsStr`, not `\u0026Path`** — `Path` has `.join()` and `.parent()`, which let you build new unvalidated paths. `OsStr` has none of that — it's a one-way exit to third-party crates with no way to accidentally re-enter path manipulation.\r\n- **One method per operation** — every operation has exactly one method. An LLM scanning the API can't pick the wrong overload because there isn't one. No aliases, no convenience wrappers, no \"which one is the secure version?\" Even the weakest model gets it right on the first try.\r\n- **`#[must_use]` with instructions, not just warnings** — the compiler becomes the documentation. When an LLM generates code and forgets to handle a `strict_join()` result, it doesn't get a generic \"unused Result\" — it gets a message like *\"always handle the Result to detect path traversal attacks\"*. The LLM reads the compiler output, self-corrects, and gets it right on the next pass. No docs lookup needed.\r\n- **Doc comments explain *why*, not just *what*** — every non-trivial function documents the reasoning behind the code, what attack a check prevents, or what invariant it enforces. An LLM working with just the source file can reason about design intent without any external context.\r\n- **[Context7](https://github.com/DK26/strict-path-rs/blob/main/LLM_CONTEXT.md) and [LLM context files](https://github.com/DK26/strict-path-rs/blob/main/LLM_CONTEXT_FULL.md)** — machine-readable API references that ship with the crate, sized for different context windows. Critical mistakes front-loaded so an LLM agent hits the important stuff first.\r\n\r\n\u003e **Is this overkill for my use case?** If you accept paths from users, config files, archives, databases, or AI agents — no, this is the minimum.\r\n\u003e If all your paths are hardcoded constants — use `std::path`. See [choosing canonicalized vs lexical](https://dk26.github.io/strict-path-rs/ergonomics/choosing_canonicalized_vs_lexical_solution.html).\r\n\r\n\u003e 📖 **New to strict-path?** Start with the **[Tutorial: Chapter 1 - The Basic Promise →](https://dk26.github.io/strict-path-rs/tutorial/chapter1_basic_promise.html)**\r\n\r\n\u003cdetails\u003e\r\n\u003csummary\u003e🤖 \u003cstrong\u003eLLM / AI Agent Integration\u003c/strong\u003e\u003c/summary\u003e\r\n\r\n\u003cbr\u003e\r\n\r\nOur doc comments and [LLM_CONTEXT_FULL.md](https://github.com/DK26/strict-path-rs/blob/main/LLM_CONTEXT_FULL.md) are designed for LLMs with function calling — enabling AI agents to use this crate safely for file operations.\r\n\r\n**LLM agent prompt (copy/paste):**\r\n``` \r\nFetch and follow this reference (single source of truth):\r\nhttps://github.com/DK26/strict-path-rs/blob/main/LLM_CONTEXT_FULL.md\r\n```\r\n\r\n**Context7 style:**\r\n```\r\nFetch and follow this reference (single source of truth):\r\nhttps://github.com/DK26/strict-path-rs/blob/main/LLM_CONTEXT.md\r\n```\r\n\r\n\u003c/details\u003e\r\n\r\n\u003e 📖 **[Security Methodology →](https://dk26.github.io/strict-path-rs/security_methodology.html)** | 📚 **[Built-in I/O Methods →](https://dk26.github.io/strict-path-rs/best_practices/common_operations.html)** | 📚 **[Anti-Patterns →](https://dk26.github.io/strict-path-rs/anti_patterns.html)**\r\n\r\n## 🎯 **StrictPath vs VirtualPath: When to Use What**\r\n\r\n### Which type should I use?\r\n\r\n- **Path/PathBuf** (std): When the path comes from a safe source within your control, not external input.\r\n- **StrictPath**: When you want to restrict paths to a specific boundary and error if they escape.\r\n- **VirtualPath**: When you want to provide path freedom under isolation.\r\n\r\n**Choose StrictPath (90% of cases):**\r\n- Archive extraction, config loading\r\n- File uploads to shared storage (admin panels, CMS assets, single-tenant apps)\r\n- LLM/AI agent file operations\r\n- Shared system resources (logs, cache, assets)\r\n- **Any case where escaping a path boundary, is considered malicious**\r\n\r\n**Choose VirtualPath (10% of cases):**\r\n- Multi-tenant file uploads (SaaS per-user storage, isolated user directories)\r\n- Multi-tenant isolation (per-user filesystem views)\r\n- Malware analysis sandboxes\r\n- Container-like plugins\r\n- **Any case where you would like to allow freedom of operations under complete isolation**\r\n\r\n\u003e 📖 **[Complete Decision Matrix →](https://dk26.github.io/strict-path-rs/best_practices.html)** | 📚 **[More Examples →](https://dk26.github.io/strict-path-rs/examples/overview.html)**\r\n\r\n---\r\n\r\n## 🚀 **More Real-World Examples**\r\n\r\n### Archive Extraction (Zip Slip Prevention)\r\n\r\n`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:\r\n\r\n```rust\r\nuse strict_path::PathBoundary;\r\n\r\n// Prevents CVE-2018-1000178 (Zip Slip) automatically (https://snyk.io/research/zip-slip-vulnerability)\r\nfn extract_archive(\r\n    extraction_dir: PathBoundary,\r\n    archive_entries: impl IntoIterator\u003cItem=(String, Vec\u003cu8\u003e)\u003e) -\u003e std::io::Result\u003c()\u003e {\r\n\r\n    for (entry_path, data) in archive_entries {\r\n        // Malicious paths like \"../../../etc/passwd\" → Err(PathEscapesBoundary)\r\n        let safe_file = extraction_dir.strict_join(\u0026entry_path)?;\r\n        safe_file.create_parent_dir_all()?;\r\n        safe_file.write(\u0026data)?;\r\n    }\r\n    Ok(())\r\n}\r\n```\r\n\r\n\u003e The equivalent `PathBoundary` for `VirtualPath` type is the `VirtualRoot` type.\r\n\r\n\r\n### Multi-Tenant Isolation\r\n\r\n```rust\r\nuse strict_path::VirtualRoot;\r\n\r\n// No path-traversal or symlinks, could escape a tenant. \r\n// Everything is clamped to the virtual root, including symlink resolutions.\r\nfn handle_file_request(tenant_id: \u0026str, requested_path: \u0026str) -\u003e std::io::Result\u003cVec\u003cu8\u003e\u003e {\r\n    let tenant_root = VirtualRoot::try_new_create(format!(\"./tenants/{tenant_id}\"))?;\r\n    \r\n    // \"../../other_tenant/secrets.txt\" → clamped to \"/other_tenant/secrets.txt\" in THIS tenant\r\n    let user_file = tenant_root.virtual_join(requested_path)?;\r\n    user_file.read()\r\n}\r\n```\r\n\r\n---\r\n\r\n\r\n## 🧠 **Compile-Time Safety with Markers**\r\n\r\n`StrictPath\u003cMarker\u003e` enables **domain separation and authorization** at compile time:\r\n\r\n```rust\r\nstruct UserFiles;\r\nstruct SystemFiles;\r\n\r\nfn process_user(f: \u0026StrictPath\u003cUserFiles\u003e) -\u003e Vec\u003cu8\u003e { f.read().unwrap() }\r\n\r\nlet user_boundary = PathBoundary::\u003cUserFiles\u003e::try_new_create(\"./data/users\")?;\r\nlet sys_boundary = PathBoundary::\u003cSystemFiles\u003e::try_new_create(\"./system\")?;\r\n\r\nlet user_input = get_filename_from_request();\r\nlet user_file = user_boundary.strict_join(user_input)?;\r\nprocess_user(\u0026user_file); // ✅ OK - correct marker type\r\n\r\nlet sys_file = sys_boundary.strict_join(\"config.toml\")?;\r\n// process_user(\u0026sys_file); // ❌ Compile error - wrong marker type!\r\n```\r\n\r\n\u003e 📖 **[Complete Marker Tutorial →](https://dk26.github.io/strict-path-rs/tutorial/chapter3_markers.html)** - Authorization patterns, permission matrices, `change_marker()` usage\r\n\r\n---\r\n\r\n### vs `soft-canonicalize`\r\n\r\n**Compared with manual soft-canonicalize path validations:**\r\n- `soft-canonicalize` = low-level path resolution engine (returns `PathBuf`)\r\n- `strict-path` = high-level security API (returns `StrictPath\u003cMarker\u003e` with compile-time guarantees: fit for LLM era)\r\n\r\n---\r\n\r\n## 🔌 **Ecosystem Integration**\r\n\r\nCompose with standard Rust crates for complete solutions:\r\n\r\n| Integration  | Purpose                 | Guide                                                                                       |\r\n| ------------ | ----------------------- | ------------------------------------------------------------------------------------------- |\r\n| **tempfile** | Secure temp directories | [Guide](https://dk26.github.io/strict-path-rs/ecosystem_integration.html#temporary-directories-tempfile) |\r\n| **dirs**     | OS standard directories | [Guide](https://dk26.github.io/strict-path-rs/os_directories.html)                          |\r\n| **app-path** | Application directories | [Guide](https://dk26.github.io/strict-path-rs/ecosystem_integration.html#portable-application-paths-app-path) |\r\n| **serde**    | Safe deserialization    | [Guide](https://dk26.github.io/strict-path-rs/ecosystem_integration.html#deserializing-boundaries-with-fromstr) |\r\n| **Axum**     | Web server extractors   | [Tutorial](https://dk26.github.io/strict-path-rs/axum_tutorial/overview.html)               |\r\n| **Archives** | ZIP/TAR extraction      | [Guide](https://dk26.github.io/strict-path-rs/examples/archive_extraction.html)             |\r\n\r\n\u003e 📚 **[Complete Integration Guide →](https://dk26.github.io/strict-path-rs/ecosystem_integration.html)**\r\n\r\n---\r\n\r\n## 📚 **Learn More**\r\n\r\n📖 **[API Docs](https://docs.rs/strict-path)** | 📚 **[User Guide](https://dk26.github.io/strict-path-rs/)** | 📚 **[Anti-Patterns](https://dk26.github.io/strict-path-rs/anti_patterns.html)** | 📖 **[Security Methodology](https://dk26.github.io/strict-path-rs/security_methodology.html)** | 🧭 **[Canonicalized vs Lexical](https://dk26.github.io/strict-path-rs/ergonomics/choosing_canonicalized_vs_lexical_solution.html)** | 🛠️ **[`soft-canonicalize`](https://github.com/DK26/soft-canonicalize-rs)**\r\n\r\n---\r\n\r\n## 📄 **License**\r\n\r\nMIT OR Apache-2.0\r\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdk26%2Fstrict-path-rs","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdk26%2Fstrict-path-rs","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdk26%2Fstrict-path-rs/lists"}