Simon Willison calls it the lethal trifecta: private data, untrusted content, and a way to exfiltrate, all in the same session. A coding agent on your laptop has all three by default. It reads your code (private data), it reads whatever a dependency's README or a fetched URL says (untrusted content), and it can run curl
(a way out). Once those three are true at once, the only question left is whether something in that untrusted content can talk the model into connecting them.
Most of what gets called an "AI guardrail" tries to stop that by reading the prompt or the model's output and flagging what looks dangerous. That's advisory. It runs before or after the model has already decided what to do, and a sufficiently clever piece of injected text just needs to talk the model past the filter, not past a real barrier. The filter's verdict is a suggestion the model is free to ignore, because nothing forces the model's actual tool call to go through it.
Doberman's answer is boring on purpose: don't inspect the conversation, inspect the call. Every tool call a coding agent makes - shell command, file write, MCP call - gets normalized into one object and routed through a decision engine before it executes. The model can be talked into requesting anything it wants. What it can't do is make the call happen without going through that one chokepoint, because there is no other path to the tool.
Two properties turn "a chokepoint exists" into something you can actually rely on:
Fail closed. Any error, any unhandled case, any uncertainty resolves to a denial. That has to include silence: an approval prompt nobody answers is not a maybe, it's bounded by a hard deadline (2 minutes for a desktop dialog, 20 minutes as the whole-challenge backstop) and then denies, logged distinctly as timeout
so you can tell it apart from an explicit denied
later. Agents run unattended a lot of the time. A guardrail that quietly lets things through when it's confused, or when nobody's watching, isn't a guardrail.
Raise-only. The guardrail and its learning can tighten automatically as it sees more of your workflow. It can never loosen itself. Any permanent weakening of policy needs a possession-factor-gated human approval (a TOTP code if you've enrolled one, otherwise the local password) and lands in an append-only ledger. That matters because the same channel an attacker uses to try to get an action approved is the channel that would let them quietly turn the guardrail down, if loosening were as easy as tightening.
Put those together and you get an actual guarantee instead of a probability: a blocked action never runs, full stop, regardless of what the model was convinced to try, because the block doesn't depend on catching the reasoning, it depends on stopping the call.
Fail-closed and raise-only apply within a single action. The taint floor is what carries risk across actions in the same session. Once a session has read something that looks like a secret, every outbound action after that gets evaluated with a raised floor, and if an outbound value actually matches a secret the session read earlier, it's blocked outright, not authed - blocked. This is the direct answer to the trifecta: reading the secret doesn't get punished, trying to move it out afterward does, and the system doesn't need to guess intent to catch it, it just needs to remember what this session has already touched.
None of this means anything if the agent can edit its own leash. Doberman's own config, hooks, and policy files sit inside the boundary it enforces: the protected agent cannot edit them to let itself out. If an attacker's plan requires disabling or reconfiguring the guardrail as a step, that step is itself a gated action, not a side door.
A sandbox answers a different question than an authorization layer does, and conflating them is where a lot of "we're covered" confidence comes from. A sandbox limits where damage lands if an action executes: a container boundary, a restricted filesystem, a network namespace. It doesn't know or care why the action is happening. Authorization decides whether the action happens at all, with context. The same curl
call to the same host can be a PASS in a clean session and a BLOCK in a session that just read a .env
file, because the decision isn't about the command in isolation, it's about the command plus what this session has already done. A sandbox can't see that; it has no session memory to raise a floor with.
Run both. They're not competing for the same job. The sandbox bounds the blast radius of whatever gets through. The authorization layer decides what gets through in the first place, with the accumulated context a static sandbox boundary never has.
Doberman is open source (Apache-2.0), alpha, and defense-in-depth, not airtight, on purpose - no single rule here is being sold as a guarantee except the two structural ones above. Code and docs: https://github.com/DobermanCore/Doberman-Core