Policy // the boundary itself
umbra
config driven occlusion framework
You gave an agent a shell. Now name every command it can run.
wrap ward git {
exec git
can run commit { deny-flag "--no-verify" }
never run "reflog expire"
}
That is the entire policy for git. Four lines, and everything nobody named is unreachable.
Deny by default
commit granted by the guardfile reflog refused outright 61 others never named, so never reachable
The problem, and what it costs to leave alone
Most teams cannot answer that question.#
The answer is spread across a system prompt, a tool definition, an allowlist somebody added during an incident, and whichever binaries happen to be on the container. When something goes wrong the question changes from what was allowed to what actually happened, and that answer is usually worse, because what you have is an agent transcript rather than a log.
The expensive failure is not the runaway agent that deletes a repository. That one is rare and memorable. The expensive failure is that you cannot answer either question in a review, so the safe call is to give the agent less than it needs, and the automation quietly stays a demo.
What it does about it
umbra sits between semi-trusted automation and the host system.#
What you did not declare does not get through. The boundary lives in a KDL guardfile rather than in code, so it is one artifact a reviewer reads in a sitting. umbra ships no denylist and knows nothing about your tools. The policy is yours, and umbra enforces it across two surfaces: subprocess execution and outbound HTTP requests.
What it checks
- argv
- Validated before
execve. - scope token
- Checked per verb.
- repo state
- Repo-shaped verbs are refused on a dirty tree.
- egress
- Gated through a per-invocation CONNECT proxy.
- audit log
- Every call appended to a rotating JSONL file.
A public exit-code taxonomy lets an orchestrator tell a policy refusal apart from a tool failure, which is the difference between retrying and stopping.
What it looks like in use
The reviewable surface is four lines, not a codebase.#
commit is reachable and --no-verify is not.
reflog expire is refused outright. Every subcommand nobody named
is unreachable, because grants are deny-by-default and an unknown node fails closed.
wrap ward git { 1
exec git 2
can run commit { deny-flag "--no-verify" } 3
never run "reflog expire" 4
}
- 1 wrap
- Names the wrapper an agent invokes in place of the real binary.
- 2 exec
- The one binary this wrapper is allowed to reach.
- 3 can run
commitis reachable, and--no-verifyis not.- 4 never run
reflog expireis refused outright.
What it does not do
umbra is not a sandbox.#
Note
It performs no execution isolation at all, and that is deliberate rather than unfinished. It is audit-and-gate.
Four defences get conflated constantly and none substitutes for another.
| Defence | What it covers |
|---|---|
| Command-construction safety | Stops argv smuggling metacharacters into execve, and says nothing about who asked. |
| Execution isolation | A container's job. |
| Provenance | Carries the origin claim. |
| Application trust policy | Stays yours. |
umbra also ships no denylist, so an empty guardfile grants nothing and protects nothing. The policy is the thing you write, not the thing you install.
One boundary, four proofs
Constrain what an agent can do, and prove what it did.#
- umbra v0.126.0 Writes the policy. You are here.
- mcp-beaver Preview Renders that same guardfile into a guarded MCP server.
- agent-compose v1.32.0 Composes the context around it, carrying no authority.
- sirens-echo Live The whole thing deployed, answering real people.
Repository and docs