What Zones Are
A zone is a named compartment binding a policy to an identity. A run starts in a low-privilege zone with no credentials and escalates into a higher one for individual commands, each checked separately.
Zones are optional. With no zones.toml in the machine config directory
(/etc/wardcell, beside config.toml) they do not exist, and wardcell works
exactly as the rest of these docs describe — one policy per run, selected by
--profile or selects_on. You never need a zone to sandbox an agent.
Add them when you want granular, identity-aware control over what an agent may do as you. An agent needs a credential for a fraction of its work — pushing a branch, calling an internal API — but without zones it either holds that credential for all of its work or cannot do that part at all.
The common shape is two zones:
- An
anonymouszone with no credential injection — the agent can read the project and reach the open web, which is all it needs to research, search, and draft. Nothing it does there is signed as you. - A
userzone it escalates into only for the steps that act as you —git push, calling a real API — where the proxy attaches your identity to the request. The agent never holds the secret; it just asked to enter the zone, and that request was checked.
So the agent roams freely while it is only reading and thinking — exactly where a prompt injection would land — and your identity is spent only on the specific commands that genuinely need it.
Entering a zone can also require a human to approve it, gated on the command
the escalation is trying to run: git push into the user zone can be waved
through while curl into it waits for you to say yes. The agent asks to enter,
you see what it wants to run and from where, and the step proceeds only once you
approve — so a hijacked agent cannot spend your identity without a person in the
loop. See Entry Approval.
flowchart LR
subgraph host["host (trusted)"]
S["supervisor"]
end
subgraph z1["zone: anonymous"]
A["agent<br/>no credentials"]
end
subgraph z2["zone: user"]
B["git push<br/>credential injected at the proxy"]
end
A -->|"zone enter user"| S
S -->|"checks enter_from,<br/>spawns a new sandbox"| B
A zone is (policy, identity)
Section titled “A zone is (policy, identity)”The policy half is nothing new — it is ordinary fragment resolution against
the same base.sbx, the same selects_on, the same merge algorithm. A zone’s
filesystem and network reach is exactly what --profile <that profile> would
have produced.
Only the identity half is new: whether the proxy attaches a credential to outbound requests, and which plugin decides what that credential is.
There is no second merge algorithm and no per-zone base policy.
One file switches them on
Section titled “One file switches them on”No zones.toml in /etc/wardcell | zones do not exist. --profile works as normal. Nothing about using wardcell requires this file. |
zones.toml present | every run and draft resolves through a zone, and --profile is rejected. |
A fresh install starts in the first state.
Why escalation is safe
Section titled “Why escalation is safe”wardcell zone enter never widens the calling process. It asks a supervisor — a
trusted process outside the sandbox — which spawns a brand-new sandbox for
the target zone and runs the command there.
That matters because a self-elevation path is one bug away from being an escape. A supervisor that only ever constructs fresh sandboxes has no in-process state an attacker can corrupt into a wider grant.
Zone identity is also a property of which loopback port a connection arrived on — each zone gets its own proxy listener — never something inferred from request content. A forged header cannot claim a zone.
When you need them
Section titled “When you need them”Yes, if a credential should exist for some commands and not others, or if an org wants to mandate a privilege floor across a fleet.
No, if one policy covers the whole run. A plain profile is simpler and does the same job.