Skip to main content

proc_jail

A fail-closed process execution guard for untrusted input, automation, and agentic systems.

Crates.io PyPI License

proc_jail provides a policy-enforced wrapper around process spawning for Rust and Python. It applies deterministic bounds to commands derived from untrusted input, helping prevent command injection, unauthorized binary execution, and resource abuse.

Features

  • No shell interpretation: Commands use argv-style execution, not shell strings
  • Allowlist-only: Explicit enumeration of permitted binaries and flags
  • Fail closed: Any error or ambiguity results in denial
  • Resource limits: Timeout, stdout/stderr byte limits
  • Double-dash injection: Automatic -- insertion to prevent flag injection
  • Python and Rust APIs: Native bindings for both languages

Quick Start (Python)

pip install proc_jail
from proc_jail import ProcPolicyBuilder, ProcRequest, ArgRules

# Define a policy
policy = (
    ProcPolicyBuilder()
    .allow_bin("/usr/bin/grep")
    .arg_rules("/usr/bin/grep", 
        ArgRules()
        .allowed_flags(["-n", "-i", "-l", "-c"])
        .max_flags(4)
        .max_positionals(10)
        .inject_double_dash())
    .timeout(30)
    .build()
)

# Create and execute a request
request = ProcRequest("/usr/bin/grep", ["-n", "pattern", "file.txt"])
output = policy.prepare(request).spawn_sync()

print(output.stdout_string())

Quick Start (Rust)

[dependencies]
proc_jail = "0.2"
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
use proc_jail::{ProcPolicy, ProcRequest, ArgRules, InjectDoubleDash};
use std::time::Duration;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let policy = ProcPolicy::builder()
        .allow_bin("/usr/bin/grep")
        .arg_rules("/usr/bin/grep", ArgRules::new()
            .allowed_flags(&["-n", "-i", "-l", "-c"])
            .max_flags(4)
            .max_positionals(10)
            .inject_double_dash(InjectDoubleDash::AfterFlags))
        .timeout(Duration::from_secs(30))
        .build()?;

    let request = ProcRequest::new(
        "/usr/bin/grep",
        vec!["-n".into(), "pattern".into(), "file.txt".into()],
    );

    let output = policy.prepare(request)?.spawn().await?;
    println!("{}", output.stdout_string());
    Ok(())
}

Why proc_jail?

Traditional process spawning is dangerous in agentic systems:

# VULNERABLE: Shell injection
subprocess.run(f"grep '{query}' file.txt", shell=True)

# Attacker sets: query = "x'; rm -rf / #"
# Executes: grep 'x'; rm -rf / #' file.txt

With proc_jail, the same attack becomes harmless:

request = ProcRequest("/usr/bin/grep", [query, "file.txt"])
output = policy.prepare(request).spawn_sync()

# If query = "x'; rm -rf / #"
# Executes: grep "x'; rm -rf / #" file.txt
# The injection is just a literal string, not interpreted

Policies

Binary Allowlist

Only explicitly allowed binaries can be executed:

ProcPolicyBuilder()
    .allow_bin("/usr/bin/grep")
    .allow_bin("/usr/bin/jq")
    # ...

Argument Rules

Every binary requires explicit argument rules:

.arg_rules("/usr/bin/grep", 
    ArgRules()
    .allowed_flags(["-n", "-i", "-l"])
    .max_flags(3)
    .max_positionals(10)
    .inject_double_dash())

Subcommand Pinning

Pin allowed subcommands for tools like git:

.arg_rules("/usr/bin/git",
    ArgRules()
    .subcommand("status")
    .allowed_flags(["--porcelain", "-s"])
    .max_flags(2)
    .max_positionals(0))

Risky Binary Detection

Shells, interpreters, and privilege escalation tools are blocked by default:

# Even if allowed, bash is denied by default
policy.prepare(ProcRequest("/bin/bash", []))  # Error: BinRiskyDenied

# Opt-in with explicit acknowledgment
ProcPolicyBuilder()
    .allow_risky_binaries()
    # ...

Environment Control

By default, no environment variables are passed. Dangerous variables (LD_PRELOAD, PYTHONPATH, etc.) are always stripped.

Resource Limits

ProcPolicyBuilder()
    .timeout(30)           # seconds
    .max_stdout(10485760)  # 10 MB
    .max_stderr(1048576)   # 1 MB

The command runs in its own process group. When a limit is hit, the command exits, or the returned future is cancelled, the whole group is killed with SIGKILL, so grandchildren (for example npm run build → node) do not outlive the call.

Launching Through a Wrapper

To run a validated command inside an OS sandbox, launch it through the sandbox's launcher. Validation still applies to the original binary and arguments; the wrapper and launch env are trusted caller input.

let prepared = policy
    .prepare(request)?
    .wrap_with("/usr/bin/sandbox-exec", ["-p", &profile])  // sandbox-exec -p <profile> <bin> <argv...>
    .with_launch_env("TMPDIR", sandbox_tmp);                // added after the env policy
let output = prepared.spawn().await?;

bin()/argv() still return the validated command; launch_program()/launch_args() return what is executed. Python exposes the same methods on PreparedCommand.

Platform Support

Unix only (Linux, macOS). Windows is not supported because CreateProcess passes arguments as a single string that each program parses differently, making injection prevention impossible to guarantee. See docs/windows.md for details.

Project Description PyPI/Crates.io
path_jail Path traversal prevention PyPI
url_jail SSRF-safe URL validation PyPI
safe_unzip Zip Slip and zip bomb prevention PyPI
tenuo Capability-based authorization for AI agents PyPI

Integration with Tenuo

proc_jail provides the execution layer, while Tenuo provides cryptographic authorization. Together they offer defense in depth:

from proc_jail import ProcPolicyBuilder, ProcRequest, ArgRules

# proc_jail handles the safe execution
# Tenuo handles the authorization (who can call which tools)

def execute_grep(user_query: str, target_file: str) -> str:
    """Execute grep with proc_jail protection."""
    policy = (
        ProcPolicyBuilder()
        .allow_bin("/usr/bin/grep")
        .arg_rules("/usr/bin/grep", 
            ArgRules()
            .allowed_flags(["-n", "-i"])
            .max_positionals(10)
            .inject_double_dash())
        .timeout(30)
        .build()
    )
    
    # Even if user_query = "'; rm -rf / #", it's treated as a literal string
    request = ProcRequest("/usr/bin/grep", ["-n", user_query, target_file])
    output = policy.prepare(request).spawn_sync()
    return output.stdout_string()

Why both?

  • Tenuo: Cryptographic proof the agent is authorized for this tool
  • proc_jail: Prevents command injection even if the agent is compromised

Documentation

Repository Structure

proc_jail/
├── src/           # Rust library source
├── tests/         # Rust integration tests
├── docs/          # Documentation
├── python/        # Python bindings (PyO3)
│   ├── src/       # Rust binding code
│   └── proc_jail/ # Python package
└── ...

Development

# Build Rust library
cargo build

# Run tests
cargo test

# Build Python bindings
cd python
pip install maturin
maturin develop

License

MIT OR Apache-2.0

Metadata

Release files for proc-jail 0.3.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for proc-jail 0.3.1
File Size Uploaded Provenance Actions
proc_jail-0.3.1.tar.gz 64.8 kB Without provenance Details for proc_jail-0.3.1.tar.gz

Built distributions (wheels)

Table of built distributions (wheels) for proc-jail 0.3.1
File Actions
proc_jail-0.3.1-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl CPython 3.8 abi3 Linux glibc 2.17+ x86-64 Details for proc_jail-0.3.1-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
proc_jail-0.3.1-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl CPython 3.8 abi3 Linux glibc 2.17+ ARM64 Details for proc_jail-0.3.1-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
proc_jail-0.3.1-cp38-abi3-macosx_11_0_arm64.whl CPython 3.8 abi3 macOS 11.0+ ARM64 Details for proc_jail-0.3.1-cp38-abi3-macosx_11_0_arm64.whl
proc_jail-0.3.1-cp38-abi3-macosx_10_12_x86_64.whl CPython 3.8 abi3 macOS 10.12+ x86-64 Details for proc_jail-0.3.1-cp38-abi3-macosx_10_12_x86_64.whl

Total release size: 2.3 MB

Release files / proc_jail-0.3.1.tar.gz

Download URL proc_jail-0.3.1.tar.gz
Size 64.8 kB
Tags Source
SHA-256 checksum
How to use checksums
983304f9498f0ea269307c56ffc6881d5fdc4b66747a717c7cf98efb0e1a2cda
BLAKE2b-256 checksum
How to use checksums
3adaaccd3a36a498aecafc5e6ccde6cb5db747bd1f51aa0bf659d47cf4e15932
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.16

Release files / proc_jail-0.3.1-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl

Download URL proc_jail-0.3.1-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Size 592.5 kB
Tags CPython 3.8 Linux glibc 2.17+ x86-64 abi3
SHA-256 checksum
How to use checksums
112cebdf7a6543dfcf06ce6dd99dc45eb2c9aaa53678e494e88cfa75088fa9a3
BLAKE2b-256 checksum
How to use checksums
0fd5720a2a05c497acc70310e6d09f4940dbd0353c5b1b63bb31c5286e26eea4
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.16

Release files / proc_jail-0.3.1-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl

Download URL proc_jail-0.3.1-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Size 596.3 kB
Tags CPython 3.8 Linux glibc 2.17+ ARM64 abi3
SHA-256 checksum
How to use checksums
a82e2860ef6106bcd251eadf7cf8daa817c03e8eb3e5a924453ef992f69ebf12
BLAKE2b-256 checksum
How to use checksums
543c6198fc5fd72f61b7b0e975573b4fa94cee8044ab87536de0fa33b8206b6c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.16

Release files / proc_jail-0.3.1-cp38-abi3-macosx_11_0_arm64.whl

Download URL proc_jail-0.3.1-cp38-abi3-macosx_11_0_arm64.whl
Size 531.9 kB
Tags CPython 3.8 abi3 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
4183e19472cc1f2f77d089e49d159db259a1c3cc110f86f1af966ec9552b60ac
BLAKE2b-256 checksum
How to use checksums
9b0b1320b1d40479822ff6a5ad4e3a13271539f2702410612d1d9788fd0b442d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.16

Release files / proc_jail-0.3.1-cp38-abi3-macosx_10_12_x86_64.whl

Download URL proc_jail-0.3.1-cp38-abi3-macosx_10_12_x86_64.whl
Size 531.4 kB
Tags CPython 3.8 abi3 macOS 10.12+ x86-64
SHA-256 checksum
How to use checksums
739f7a4012f94b84d8f490dc970af0d9e8958f6cfdb03481225039ec3905546a
BLAKE2b-256 checksum
How to use checksums
fe9d25880875d9d0f8482bb8c651ee864d77f90ad3db240ddc454d3611b461bb
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.16

Security for proc-jail 0.3.1

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page