IronCurtain: Treating AI Agents Like Untrusted Binaries
Hook
Most AI agent frameworks ask the model nicely to follow rules. IronCurtain assumes your agent is hostile malware and enforces policy at the boundary—except the 'syscalls' are git pushes, API calls, and npm install. Since our last look it has grown from a research sketch into a shipping CLI (npm install -g @provos/ironcurtain) with a multi-agent vulnerability-hunting workflow bolted on.
Context
The rise of autonomous AI agents—systems that can read files, execute code, commit to repositories, and call APIs without human intervention—has created a glaring security gap. Today's agent frameworks hand the agent the same privileges as the user: full filesystem access, credentials, and network. Security researchers call this ambient authority, and it means a single prompt injection or slow multi-turn drift can cause an agent to delete files, exfiltrate secrets, or push malicious code.
The usual responses are both unsatisfying. Lock the agent in a narrow sandbox and it stops being useful—you can't block all file writes when the agent's job is to refactor your codebase. Make the user approve every action and it stops being autonomous—nobody wants to click 'approve' fifty times while an agent debugs a test suite. IronCurtain emerged from this tension: how do you let an agent perform real operations while maintaining cryptographic-grade certainty that it won't exfiltrate your AWS keys or rm -rf your home directory? Its answer is semantic interposition—enforcing policy at the level of high-level operations ("commit to git," "read secrets," "install a package") rather than low-level syscalls ("open file descriptor"), with the rules themselves derived from a plain-English constitution.
Technical Insight
IronCurtain treats the AI agent as fundamentally untrusted, with security enforced mechanically rather than by trusting the model to behave. It now ships two session modes with different trust models, both routing every effect through a policy engine that can allow, deny, or escalate to a human.
In Code Mode, IronCurtain's own LLM agent writes TypeScript that executes inside a V8 isolate—the same primitive behind Chrome's site isolation. The agent code has zero access to Node.js APIs, the filesystem, or sockets; the only way out is a structured MCP tool call:
// This runs in a V8 isolate with no host access
const result = await mcpClient.callTool({
server: "filesystem",
tool: "read_file",
arguments: { path: "./config.json" }
});
// Trying to bypass via Node.js fails—no globals available
// require('fs').readFileSync(...) -> ReferenceError
// process.env.API_KEY -> ReferenceError
Forcing everything through MCP—a protocol never designed for security—happens to create perfect interposition points. Every tool call is checked against policy before it reaches the real MCP server.
The policy itself comes from a constitution: a short English document describing what the agent may and may not do. An LLM pipeline compiles it through distinct stages—Annotate → Compile → Resolve Lists → Generate Scenarios → Verify & Repair—turning prose into deterministic if/then rules, with categorical references like "major news sites" emitted as symbolic @list-name lookups. Crucially, compilation is the only place the LLM touches policy; at runtime the rules are pure data. A clause compiles to something like:
{ "tool": "git_push", "decision": "escalate",
"reason": "Remote-contacting git operations require human approval" }
Anything that doesn't match an explicit allow or escalate is denied by default.
For external agents like Claude Code or Goose that can't run in an isolate, Docker Agent Mode runs the agent in a network-isolated container and mediates every external effect. LLM API calls pass through a TLS-terminating MITM proxy that swaps placeholder keys for real ones and enforces a host allowlist; MCP tool calls hit the same policy engine; and—new since launch—package installations (npm/PyPI) go through a validating registry proxy, closing the supply-chain hole where an agent quietly pip installs a typosquat.
Two design choices give the system real teeth. First, structural invariants: certain protections are hardcoded and cannot be overridden by any constitution—the agent can never modify its own policy files, audit logs, or configuration. Second, trusted user input: text you type in command mode (Ctrl-A) is captured host-side before it ever reaches the container, creating a verified human-intent signal. An optional auto-approver can then clear unambiguous escalations—type "push my changes to origin" and a subsequent git_push is approved without a prompt, but a spontaneous attempt to edit ~/.ssh/config still blocks. This dissolves approval fatigue without letting the agent social-engineer its own approvals, because intent verification happens entirely outside its control.
Layered on top is something genuinely new: multi-agent workflow orchestration. The bundled vulnerability-discovery workflow hunts memory-safety and logic bugs in native code through a tiered harness pipeline (Tier 1 isolated function → Tier 2 multi-component → Tier 3 full build) with libFuzzer/AFL++ coverage gating, hypothesis-driven discover/triage states, and a final human report-review gate. A design-and-code workflow runs plan/design/implement/review cycles. Each agent runs in its own container with role-specific policy boundaries—comparable in scope to Amazon Kiro or Google Jules for coding, but with first-class security enforcement underneath. The defense-in-depth posture means even if an agent escapes the V8 isolate via a zero-day or bypasses the proxy, the policy engine still evaluates every MCP call—and if the policy engine has a bug, the isolation layers backstop it.
Gotcha
IronCurtain still labels itself a research prototype, and that warning deserves attention. The entire policy-compilation pipeline—where an LLM turns English into executable security rules—remains unproven territory. The verification stage generates test scenarios and repairs mismatches, but there's no formal verification: a subtle compilation error could greenlight a destructive operation, and the docs themselves tell you to review the generated compiled-policy.json by hand. Vague constitution wording produces vague policy.
The Node.js constraint is unchanged and still a deployment headache: isolated-vm requires Node 22+ and tops out at Node 25 (<26). If you standardize on Node 20 LTS or need to move to Node 26, you're stuck. The MCP ecosystem is also only as secure as its weakest server—a buggy filesystem wrapper can leak data even if your policy is perfect—and the TLS MITM proxy means you're trusting IronCurtain with your real API keys and a certificate authority. Other named limits: Code Mode rests on V8 isolates rather than OS-level virtualization (a V8 zero-day allows escape), and over-eager escalations can still train users into habitual approval.
Verdict
Use IronCurtain if you're building experimental AI agents that need real-world tool access—git, filesystem, package installs, APIs—and you want programmatic safety rails beyond crossing your fingers. With the npm CLI, three LLM providers (Anthropic, Google, OpenAI), personas, daemon/Signal-based mobile approval, and the bundled vulnerability-discovery workflow, the on-ramp is far smoother than it was a month ago, and the constitution-to-enforcement model plus the new registry proxy and structural invariants are genuinely novel answers to a hard problem. It's a strong fit for research teams exploring agentic workflows, security engineers prototyping policy frameworks, and offensive-security folks who want a containerized, policy-bounded harness for agent-driven bug hunting. Skip it if you need production-grade stability, can't live with the Node version window, or require formally verified guarantees—the "LLM-compiled constitution" concept is still unproven at scale and breaking changes are expected. And skip it if your threat model includes state-level adversaries: this is built to prevent accidental harm, prompt-injection drift, and basic agent misbehavior, not to withstand a determined APT.