proc_jail
Process execution guard for agentic systems.
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.
Related Projects
| Project | Description | PyPI/Crates.io |
|---|---|---|
| path_jail | Path traversal prevention | |
| url_jail | SSRF-safe URL validation | |
| safe_unzip | Zip Slip and zip bomb prevention | |
| tenuo | Capability-based authorization for AI agents |
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
- SECURITY.md - Security properties, limitations, threat model
- CHANGELOG.md - Version history
- docs/windows.md - Why Windows is not supported
- docs/risky-binaries.md - Blocked binary categories
- docs/design-decisions.md - Rationale for key decisions
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)
| 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)
| File | Actions Reset | |||
|---|---|---|---|---|
| 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.