agentsh: Runtime Policy Enforcement for AI Agents That Actually Sees Subprocesses
Hook
Your AI agent’s code execution sandbox probably stops working the moment it calls subprocess.run(). While you’re carefully approving each tool invocation, that ‘safe’ Python script just spawned curl, ssh, and a bash pipeline—completely invisible to your approval layer.
Context
The current generation of AI coding agents lives in a security paradox. Tools like Devin, Aider, and GPT Engineer need real system access to be useful—they must read files, run tests, execute builds, and interact with services. The standard approach wraps these capabilities in function-calling APIs with approval gates: the agent requests ‘run_command(“npm test”)’, you click approve, it executes. This works until you realize that npm test spawns a hundred subprocesses, any of which could be malicious or simply destructive. Your approval only blessed the top-level command; everything underneath runs unchecked.
Existing sandboxing approaches don’t solve this cleanly. Containers provide isolation but no visibility or per-operation policies—you either allow network access or you don’t, with no middle ground for ‘allow HTTPS to GitHub but block SSH to internal servers.’ Traditional MAC systems like AppArmor require static policies written by security experts, not dynamic rules that adapt per agent session. gVisor provides strong isolation but adds significant overhead and still lacks the audit granularity needed to understand what an agent actually did. What’s missing is a gateway that sits at the system call boundary, understands subprocess trees as cohesive sessions, enforces policies on every operation regardless of process hierarchy depth, and produces structured audit logs showing exactly what happened.
Technical Insight
agentsh solves the subprocess visibility problem through a combination of execution interposition and platform-specific enforcement mechanisms. On Linux, it uses ptrace to attach to process trees and seccomp-bpf to filter system calls, while a FUSE filesystem layer intercepts file operations. On macOS, it leverages Endpoint Security Framework and Network Extension for deeper integration (though requiring Apple entitlements), with a FUSE-T fallback. Windows support uses minifilter drivers and AppContainers. The key architectural insight is treating the initial agent command and all its descendants as a single policy-enforced session.
The primary integration path is through shell shims—replacing /bin/sh and /bin/bash with agentsh-aware wrappers. When an agent calls subprocess.run(['sh', '-c', 'git clone ...']), it unknowingly invokes agentsh’s shim, which routes the command through the policy engine before allowing execution. This requires no code changes in the agent itself:
# Start agentsh server (or let first command autostart it)
agentsh server --policy=agent-policy.yaml &
# Replace system shells with shims (usually in container setup)
cp /bin/sh /bin/sh.real
cp $(which agentsh-shim) /bin/sh
# Now any agent subprocess tree is automatically intercepted
python agent.py # Agent's subprocess.run() calls flow through agentsh
Policies use a first-match-wins YAML structure with rule types for files, network, and processes. The redirect capability is where agentsh diverges from simple blocking:
sessions:
- id: default
rules:
- type: file
operation: write
path: "/etc/**"
action: deny
- type: file
operation: write
path: "/home/*/.ssh/config"
action: redirect
redirect_to: "/tmp/ssh-config-sandbox"
- type: network
operation: connect
host: "*.internal.corp"
action: prompt
- type: process
operation: exec
command: "curl"
args: ["--insecure", "-k"]
action: deny
reason: "Certificate validation required"
- type: network
operation: connect
port: 22
action: deny
The redirect mechanism transparently rewrites system calls. When an agent tries to write SSH config changes (a common vector for persistence), agentsh redirects the write to a sandbox location, returns success to the agent, but prevents actual system modification. The agent proceeds thinking it succeeded, avoiding retry loops where a blocked operation causes the LLM to attempt increasingly creative bypasses. This is policy-as-steering rather than policy-as-wall.
Subprocess tracking works through session identifiers that persist across the entire execution tree. When agentsh intercepts the initial command, it assigns a session UUID and propagates it to all child processes via environment variables and ptrace tracking. Every system call carries session context, so policy evaluation knows ‘this is still part of agent session X’ even five levels deep in a bash pipeline:
agent.py (session:abc123)
└─ sh -c 'npm test' (session:abc123, inherits rules)
└─ node test.js (session:abc123)
└─ sh -c 'rm -rf /tmp/test-*' (session:abc123, evaluated)
The structured audit output emits JSON events for every intercepted operation, enabling post-hoc analysis of agent behavior:
{
"timestamp": "2025-01-15T10:23:45Z",
"session_id": "abc123",
"pid": 8472,
"operation": "file.write",
"path": "/home/user/.ssh/config",
"redirected_to": "/tmp/ssh-config-sandbox",
"action": "redirect",
"policy_rule": 2
}
This creates an audit trail showing not just ‘the agent wrote files’ but exactly which subprocess attempted which operation and how policy responded. Critical for debugging why an agent failed or investigating security incidents.
The enforcement implementation uses platform-specific capabilities transparently. On Linux with seccomp, agentsh achieves near-perfect interception—every syscall passes through the BPF filter. On macOS with FUSE-T (the fallback without entitlements), file operations route through FUSE but process/network interception is limited, dropping enforcement coverage to roughly 70%. The agentsh binary detects platform capabilities at startup and logs enforcement level, so you know exactly what protection you’re getting.
Gotcha
Platform variance is agentsh’s biggest operational challenge. The full security model only works on Linux with kernel 4.14+. On macOS, you need either Apple’s Endpoint Security entitlement (requiring developer enrollment and notarization) or accept degraded enforcement through FUSE-T. Windows support is labeled experimental with 85% coverage. This means a policy tested on your Linux development server might provide silently weaker protection when deployed to macOS CI runners. You need platform-specific testing and can’t assume portability.
The shell shim approach introduces subtle risks. Replacing /bin/sh system-wide means every process spawned by the agent—including legitimate administrative tools you might exec into the container for debugging—flows through agentsh. If the policy is too restrictive or agentsh crashes, you’ve broken basic shell functionality. In practice, you’d probably want the shims only in agent-specific containers, not production infrastructure. The first-match-wins rule evaluation also requires careful policy ordering; a broad catch-all rule early in the policy will shadow more specific rules below it, and there’s no apparent validation tooling to catch these mistakes before runtime. Policy testing is manual—run the agent and watch what breaks.
Verdict
Use agentsh if you’re running AI agents in production environments where subprocess visibility is currently a blind spot, especially on Linux infrastructure where full enforcement is available. It’s particularly valuable for containerized agent deployments in enterprise settings where compliance requires detailed audit trails and you need policies that steer agent behavior rather than just blocking actions and hoping the LLM understands why. The redirect mechanism alone justifies adoption for agents that frequently hit permission boundaries. Skip it if your agents only make API calls without local execution (pure function-calling agents don’t need syscall interception), you’re developing primarily on macOS without Apple entitlements (degraded enforcement undermines the security model), or you’re running in environments where modifying system shells violates operational policies or container image integrity requirements. Also skip if you need a mature, battle-tested security tool—at 22 GitHub stars, this is early-stage software that likely has sharp edges.