Skip to main content

Process execution guard for agentic systems

Project description

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

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distributions

No source distribution files available for this release.See tutorial on generating distribution archives.

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

proc_jail-0.1.0-cp312-cp312-macosx_11_0_arm64.whl (501.1 kB view details)

Uploaded CPython 3.12macOS 11.0+ ARM64

proc_jail-0.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (564.6 kB view details)

Uploaded CPython 3.8manylinux: glibc 2.17+ x86-64

File details

Details for the file proc_jail-0.1.0-cp312-cp312-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for proc_jail-0.1.0-cp312-cp312-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 dad2efa9baac144d15e55f01896156c477d28aa66212a4a23b5a9bf8db1bacfb
MD5 91006e7e52e509e0586753234b3146ca
BLAKE2b-256 e5951b31ca5f3bc7eb2aaaae904e70e48ab99f22a6250d1e4aff7e71d5029ecc

See more details on using hashes here.

File details

Details for the file proc_jail-0.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for proc_jail-0.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 e90611a4f6d5fa5fc8b78b7123c120fd717cc7804807197ab4d7fddd39452532
MD5 3abb272350a9eb00b835fb6f9ebda525
BLAKE2b-256 164b5430be3aa336b316becb4e8c74db4dd1d9e64c68cfc973c965bcc0997dcf

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page