Skip to content

Security: tenuo-ai/path_jail

SECURITY.md

Security Policy

This document is the threat model and security contract for path_jail. It exists because every security library needs one: callers can only use the library correctly if they know what it defends against and what it doesn't.

If you find a vulnerability, see Reporting a vulnerability below.


Attacker model

path_jail is designed to defend against an attacker who supplies path strings to your application — for example:

  • A web client uploading a file with a chosen filename
  • A user-controlled config value naming a path inside a sandbox directory
  • A workflow step naming a file inside a CI working directory

The attacker can supply:

  • Arbitrary bytes in the path (including .., leading /, null bytes, magic link prefixes like /proc/self/fd/N)
  • A pre-existing symlink inside the jail that points outside the jail
  • A pre-existing hard link inside the jail that points to sensitive content
  • Concurrent filesystem activity attempting to swap paths between validation and open (TOCTOU)

The attacker is assumed to not have:

  • Privileges to mount filesystems, run as root inside the jail's filesystem, call ptrace, or otherwise escape the OS sandbox the application runs in
  • The ability to modify the running process's memory
  • A working kernel exploit

If your attacker can do any of the above, no userspace library can help — you need a process-level sandbox (seccomp, landlock, containers, VMs).


What each API defends against

path_jail ships three API layers with different security/ergonomics tradeoffs. Pick the strongest one your environment supports.

Threat Jail (default) secure-open guard (Linux 5.6+)
Path traversal via ..
Absolute path injection (/etc/passwd)
Null-byte injection
Symlink target outside the jail
Broken symlinks (cannot verify target)
Symlink swap on final component (TOCTOU)
Symlink swap on intermediate directories
Concurrent rename of jail root mid-operation ✅¹
Magic links (/proc/self/fd, /proc/self/root)
Hard link to sensitive content (detect) ❌² ❌² ✅³
Bind-mount escape (opt-in) ✅⁴
Atomic open with kernel-enforced containment
Signed attestation of the open event ✅⁵

Footnotes:

  1. guard::FdJail pins an O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC fd to the jail root at construction time. Subsequent renames or replacements of the root path do not affect the jail — all operations remain scoped to the original directory inode.
  2. Hard links are not detectable in user space before open. The path-based APIs do not stat the opened file and so cannot surface nlink.
  3. guard::JailFile::has_hard_links() exposes nlink > 1 from the post-open fstat. Policy is the caller's responsibility — a content-addressed store may legitimately use hard links. If your policy rejects hard links, check has_hard_links() before reading or writing.
  4. Opt in with OpenOptions::no_xdev(true) (maps to RESOLVE_NO_XDEV). Off by default to preserve directory-tree containment semantics, which are what most callers want.
  5. Opt in by implementing the Signer trait. path_jail ships no crypto; bring your own (ed25519-dalek, ring, HSM client, KMS, etc.).

Out of scope

These threats are documented as not defended by any API:

  • Privileged local attackers. A process with root or CAP_SYS_ADMIN on the host can mount, bind-mount, or ptrace around any user-space check.
  • Kernel and filesystem exploits. A kernel-level bug, a FUSE filesystem misbehaving, or an openat2 semantic regression in a specific kernel version are outside our control. We pin to documented kernel ABI.
  • Side-channel attacks. Timing, cache, filesystem-metadata leaks.
  • Directory iteration (read_dir) and recursive walks. Iterating jail contents has its own TOCTOU surface (rename-during-walk) that we do not currently address. If you walk a directory tree, treat it as untrusted input on every iteration.
  • Windows. No Windows-specific protections are implemented. Jail and secure-open compile on Windows but provide no defenses beyond the cross-platform path-string checks; guard is Linux-only.
  • Unicode normalization. Paths are accepted byte-for-byte. We do not normalize NFC/NFD on macOS or fold case on Windows/macOS. If your storage layer is case-insensitive, treat Report.PDF and report.pdf as potentially the same file.
  • Resource exhaustion. Very long paths, deep symlink chains, etc. are rejected by the kernel (ENAMETOOLONG, ELOOP) but path_jail does not impose its own limits.

Choosing the right API

                              ┌──────────────────────────────────┐
You only need a validated     │ Use `Jail::join` / `join_typed`. │
path (e.g., for logging) →    │ Cheap, portable.                 │
                              └──────────────────────────────────┘

                              ┌──────────────────────────────────┐
You open the file in          │ Use `secure-open`.               │
process, on Unix, and need    │ Protects final-component swaps.  │
final-component TOCTOU →      └──────────────────────────────────┘

                              ┌──────────────────────────────────┐
Security-critical opens on    │ Use `guard` (Linux 5.6+).        │
Linux, attestation needed,    │ Kernel-enforced; signable.       │
or hostile multi-tenant →     └──────────────────────────────────┘

guard is the strongest. Use it on Linux where you can.


Versioning & supported releases

  • We follow Semantic Versioning. In the 0.y.z pre-release series, minor-version bumps (0.4 → 0.5) may include breaking API changes; patch bumps (0.4.0 → 0.4.1) will not. Starting with 1.0.0, the standard semver contract applies: only major bumps (1.x → 2.0) may break the public API.
  • Security fixes are issued on the latest minor line. We do not currently backport to older 0.x lines; after 1.0.0 we will evaluate backports case-by-case for high-severity findings.
  • The MSRV (currently 1.85) may be bumped in any minor release in the 0.x series. After 1.0.0, MSRV bumps will be treated as minor-version changes and documented in the changelog.

Reporting a vulnerability

Do not open a public GitHub issue for security bugs.

Use GitHub Private Vulnerability Reporting on the tenuo-ai/path_jail repository (Security tab → "Report a vulnerability"), or email security@tenuo.ai with:

  • A minimal reproducer (Rust code that demonstrates the issue)
  • The affected path_jail version and feature flags
  • The platform (OS, kernel version, architecture)
  • Your assessment of the impact (information disclosure / write outside jail / etc.)

We aim to acknowledge within 5 business days and to ship a fix within 30 days for high-severity findings (escape from a documented containment guarantee). Lower-severity findings (e.g., a missing defense for a documented out-of-scope threat) will be triaged on the open repository.


A note on the Jail default API

The README quick-start uses Jail::new and Jail::join — the path-based API. That API is not TOCTOU-safe. It validates a path string and returns a PathBuf; whatever the caller does with that PathBuf is a separate operation with its own race window.

This is documented but easy to miss. If you operate in any of these environments, strongly prefer guard over the path-based API:

  • Multi-tenant systems where another local process can manipulate the filesystem
  • File-upload paths where the same directory is also writable by other workers
  • Anywhere "the file you validated" and "the file you opened" need to be the same file with certainty

We may make guard the default in 1.0.0 or a future major release. For now, choose explicitly.

There aren't any published security advisories