proc_jail

Crates.ioproc_jail
lib.rsproc_jail
version0.1.0
created_at2026-01-12 01:19:01.202353+00
updated_at2026-01-12 01:19:01.202353+00
descriptionProcess execution guard for agentic systems
homepage
repositoryhttps://github.com/tenuo-ai/proc_jail
max_upload_size
id2036826
size197,698
(aimable100)

documentation

https://docs.rs/proc_jail

README

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.1"
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

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.

Related Projects

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

Commit count: 10

cargo fmt