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

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.0
File Size Uploaded Provenance Actions
proc_jail-0.3.0.tar.gz 63.6 kB Without provenance Details for proc_jail-0.3.0.tar.gz

Built distributions (wheels)

Table of built distributions (wheels) for proc-jail 0.3.0
File Actions
proc_jail-0.3.0-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.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
proc_jail-0.3.0-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl CPython 3.8 abi3 Linux glibc 2.17+ ARM64 Details for proc_jail-0.3.0-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
proc_jail-0.3.0-cp38-abi3-macosx_11_0_arm64.whl CPython 3.8 abi3 macOS 11.0+ ARM64 Details for proc_jail-0.3.0-cp38-abi3-macosx_11_0_arm64.whl
proc_jail-0.3.0-cp38-abi3-macosx_10_12_x86_64.whl CPython 3.8 abi3 macOS 10.12+ x86-64 Details for proc_jail-0.3.0-cp38-abi3-macosx_10_12_x86_64.whl

Total release size: 2.3 MB

Release files / proc_jail-0.3.0.tar.gz

Download URL proc_jail-0.3.0.tar.gz
Size 63.6 kB
Tags Source
SHA-256 checksum
How to use checksums
2620e13d0e67265c80be717ad346f4fcaf92be043c6f5ce8c66d2b46dffe6341
BLAKE2b-256 checksum
How to use checksums
42335b0d8f0be2be84a935d6c42af720f91e52352059e8f775eafe2a4a7a5487
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.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl

Download URL proc_jail-0.3.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Size 588.5 kB
Tags CPython 3.8 Linux glibc 2.17+ x86-64 abi3
SHA-256 checksum
How to use checksums
1128a48d42811b6c006f2edf8476a8379cb6b8afcbf32223858b9252f4ce05fb
BLAKE2b-256 checksum
How to use checksums
301511706642c7d64ee735624958e7ec7c773959aeae93afbff1fa0fc3f17a1c
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.0-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl

Download URL proc_jail-0.3.0-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Size 589.8 kB
Tags CPython 3.8 Linux glibc 2.17+ ARM64 abi3
SHA-256 checksum
How to use checksums
05b3287c30fbb1b5ea5a43638318c3b62c57342e2f0ea9d7501eeae119c9548e
BLAKE2b-256 checksum
How to use checksums
320ace33cc58891a8816d748e45d624d75ba156b52a69fad553f25087fc5ed16
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.0-cp38-abi3-macosx_11_0_arm64.whl

Download URL proc_jail-0.3.0-cp38-abi3-macosx_11_0_arm64.whl
Size 529.5 kB
Tags CPython 3.8 abi3 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
4010432e096b86a3e505da1a7c35e5aa575b60dfdc7247a86260b08b67e06804
BLAKE2b-256 checksum
How to use checksums
ea9e6c3d5b34159d68ff2bef39123e5b30d4ccda72e0778f7f7734ace6b51fce
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.0-cp38-abi3-macosx_10_12_x86_64.whl

Download URL proc_jail-0.3.0-cp38-abi3-macosx_10_12_x86_64.whl
Size 526.3 kB
Tags CPython 3.8 abi3 macOS 10.12+ x86-64
SHA-256 checksum
How to use checksums
8183cded879bda76760c3d604051a69709bc30b2e3aa2a2f24ca7e32c42cf732
BLAKE2b-256 checksum
How to use checksums
342ddfdc14972aaaaab9ed25b57761e0e3474fdf6e9a4d2198d83ab7a4afa82d
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.0

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