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.

  • v0.126.0
  • MIT
  • brew // scoop
  • linux // macos // windows

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
commit is reachable, and --no-verify is not.
4 never run
reflog expire is 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.

Four defences, none a substitute 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.#

  1. umbra v0.126.0 Writes the policy. You are here.
  2. mcp-beaver Preview Renders that same guardfile into a guarded MCP server.
  3. agent-compose v1.32.0 Composes the context around it, carrying no authority.
  4. sirens-echo Live The whole thing deployed, answering real people.

Repository and docs

Reference#