Skip to main content

proc_jail

Process execution guard for agentic systems.

Crates.io PyPI License

proc_jail provides a safe wrapper around process spawning, enforcing deterministic bounds on process execution to 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.2.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.2.0
File Size Uploaded Provenance Actions
proc_jail-0.2.0.tar.gz 70.7 kB Without provenance Details for proc_jail-0.2.0.tar.gz

Built distributions (wheels)

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

Total release size: 2.3 MB

Release files / proc_jail-0.2.0.tar.gz

Download URL proc_jail-0.2.0.tar.gz
Size 70.7 kB
Tags Source
SHA-256 checksum
How to use checksums
4cbcc19da81690a08c87eb0435a8fedd8538afa97585362b24f9efa15e5d8fec
BLAKE2b-256 checksum
How to use checksums
2a65348f1720f2618fa706baabbf8e88a0bd9a6375514ba973357e3ddedde52c
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.2.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl

Download URL proc_jail-0.2.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Size 585.1 kB
Tags CPython 3.8 Linux glibc 2.17+ x86-64 abi3
SHA-256 checksum
How to use checksums
bee5190433b44b15b28990d2a4ad801a07675428025937d3729a5b1454b3b25a
BLAKE2b-256 checksum
How to use checksums
e557ac00e5460b38a842f0299138da90d7784faa99c24ac7c4d1d8cb6d74f63f
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.2.0-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl

Download URL proc_jail-0.2.0-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Size 586.7 kB
Tags CPython 3.8 Linux glibc 2.17+ ARM64 abi3
SHA-256 checksum
How to use checksums
6d53766f3a5c600f6697d44b49d2f91042a358e66ea07f00cb049e9049c59440
BLAKE2b-256 checksum
How to use checksums
3647490768251443eb63110e065667c32650686f3d5c15eede3c79a865b2acd8
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.2.0-cp38-abi3-macosx_11_0_arm64.whl

Download URL proc_jail-0.2.0-cp38-abi3-macosx_11_0_arm64.whl
Size 523.6 kB
Tags CPython 3.8 abi3 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
d77beff938f9f8191ae44889c9be43ab56728ef5cb3db8477d0ee9394e46e79f
BLAKE2b-256 checksum
How to use checksums
d0c6016a8eef0ceb0b2cb409d2ac11cf0e5b52d6cf8a29e821e972466634228a
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.2.0-cp38-abi3-macosx_10_12_x86_64.whl

Download URL proc_jail-0.2.0-cp38-abi3-macosx_10_12_x86_64.whl
Size 522.0 kB
Tags CPython 3.8 abi3 macOS 10.12+ x86-64
SHA-256 checksum
How to use checksums
43e81babb80c46ff33bdc626337f3b22f877983937af7a31b517434e0925372a
BLAKE2b-256 checksum
How to use checksums
da5d81c116ef8efb6d9b7268742108d9f2f9b10b883078533c44799c192a8c28
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.2.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