Last edited: January 1, 1970

If you use AI coding agents seriously, you know the tension. Run them with approval prompts and you spend your day clicking "yes" until your attention wears out and the prompts stop meaning anything. Turn approvals off (the flag is literally called --dangerously-skip-permissions in Claude Code) and you have handed a language model your shell, your credentials, and your network. The agent reads untrusted text all day long: web pages, GitHub issues, the README of every dependency it considers. Prompt injection is not a hypothetical, one poisoned page is enough to turn a helpful agent into a tool that quietly ships your AWS_SECRET_ACCESS_KEY somewhere you didn't intend.

The common answers all give up something important. A container on your laptop still sees your environment variables and dotfiles, and its network is wide open by default. A hosted cloud sandbox moves the problem: now you trust the provider, its operators, and whatever they run under your workload. Cutting the network entirely makes the agent useless, because half the value of an autonomous agent is that it can pip install, clone repositories, and call APIs on its own.

Umbra is an open-source project I've been contributing to that takes a different position: assume the agent is hostile, build it a machine where that doesn't matter, and make the machine's integrity provable instead of assumed. This post is a tour of how it works. Two follow-up posts will go deeper into the attestation layer and into where TEEs sit in the wider privacy-tech landscape.

The Shape of the System

Umbra has four components:

  • The umbra CLI (Rust). The developer and admin surface: login, launching and updating CVMs, managing profiles and secrets, and opening sessions. Every connection it makes to a TEE is verified locally before a byte of your session flows.
  • The Console (Python, FastAPI + Postgres). The multi-tenant control plane: OIDC login, permissions, egress-policy profiles, CVM orchestration through provider adapters, and the audit and traffic-log stores. It is a conventional HTTPS service and deliberately not part of the trusted data path.
  • The Dev CVM (Intel TDX, one or more per developer). The confidential VM where your sandbox and your agent actually live.
  • The Security CVM (Intel TDX, one per team). The egress proxy that every outbound byte from every sandbox must pass through.

One design decision worth pausing on: the Console orchestrates, attests, and stores policy, but it sits in neither the SSH path nor the egress path. Your session goes straight from your machine to your Dev CVM over an attested tunnel, and sandbox traffic goes straight from the Dev CVM to the Security CVM. A compromised control plane is a bad day, but it does not get to read your code or your traffic.

Day to day you don't think about any of this:

umbra auth login https://console.example.com
umbra cvm launch
umbra claude   # or: umbra ssh / codex / code / cursor

Agent sessions run under dtach on the CVM, so a dropped connection reattaches to the same running agent instead of killing it mid-refactor.

Full sudo Inside, No Way Around

The sandbox would be pointless if the agent kept hitting walls. Inside its container the agent logs in as a normal user with passwordless sudo and everything a developer expects to work, works: sudo apt install, editing /etc, ptrace, even a rootful Docker daemon for nested containers, devcontainers, and containerized builds.

The trick is where the boundary sits. The sandbox runs under sysbox, which aligns the container with a kernel user namespace: root inside the namespace maps to an unprivileged UID on the host VM. The agent can be root in its own world all it wants; the moment it tries something that touches the host — loading a kernel module, changing host routes, reading host-owned key material — the kernel checks the mapped identity and says no. The trust boundary is a namespace, not a permission prompt.

Network bypass is prevented by topology rather than by firewall rules. The sandbox's only network is a Docker bridge declared internal: true, which means no default route to anywhere exists in its namespace. The only container with an uplink is a small fail-closed forwarder, and the only place that uplink reaches is the team's Security CVM. The agent can hold NET_ADMIN inside its namespace and rewrite its own iptables forever; no capability lets you fabricate a route to an interface that does not exist. The forwarder is also TCP-only, so UDP-based covert channels — WireGuard, QUIC-style tunnels — have no path off the box at all.

And if the agent fork-bombs the VM? That's your CVM, your problem, and nobody else's. Isolation between developers is enforced a level down, by separate TDX VMs.

Every Byte Through the Checkpoint

The Security CVM runs a mitmproxy-based egress proxy that terminates TLS, applies policy, and re-encrypts outbound. Policy comes from profiles — composable rule sets an admin edits through the CLI — and covers method-scoped destination allow/deny rules, secret-scanning patterns, and credential injections. Multiple profiles compose into one effective policy per sandbox. No policy at all means fail closed: a sandbox with nothing attached reaches nothing.

Every request produces a traffic-log record with the enforcement decision, attributable to the exact CVM that made it, queryable later through the CLI. Requests are scanned for secret patterns before leaving, so a prompt-injected curl that tries to smuggle a key out in a request body gets blocked at the proxy, not discovered in an incident review. Policy edits converge in about five seconds — the Security CVM pulls from the Console rather than trusting pushes.

Secrets the Agent Can Use but Never See

My favorite property of the whole design: real credentials never enter the sandbox. The agent's environment contains placeholders. The real values live write-only in the Console — no API reads them back — and are injected into request headers at the proxy, after the allow/deny and secret-scanning checks have passed, only for the destinations the policy names.

So the agent can call the GitHub API all day without ever being able to cat the token it is using. A secret can be shared (a team bot token attached to a profile) or personal: you register your own credential with umbra secret set --host, bound to specific hosts, and it is resolved per CVM owner at enforcement time. Two developers on the same profile each get their own identity injected; being in a profile never grants you a colleague's credentials.

The injection surface is deliberately narrow — set or overwrite HTTP request headers, nothing else. No request signing, no body rewriting. Narrow enough to reason about is a feature.

Why Would You Believe Any of This?

Everything above describes a VM with a proxy, and "trust me, that's what is deployed" is exactly the assurance model Umbra is trying to escape. This is where confidential computing earns its place.

Intel TDX measures what a CVM boots — firmware, kernel, OS image, and the exact container compose it runs — into hardware-signed quotes. Umbra wraps every TEE connection in aTLS (attested TLS, via the atlas-rs library): the TLS handshake is cryptographically bound to a fresh quote, so you are not just talking over an encrypted channel, you are talking over an encrypted channel to the code you expected. The binding uses the TLS session's exporter material and a fresh nonce baked into the quote, which means a quote leaked from one session cannot be replayed to impersonate another.

The CLI verifies your Dev CVM against a per-CVM policy file — golden measurements published by the image release pipeline, plus material bound at launch such as your authorized SSH keys. SSH host keys are not the trust anchor here; the attestation is. The same verification runs in the other trust edges too: the Console attests the Security CVM before issuing it any credentials, and the forwarder inside your Dev CVM verifies the Security CVM before shipping your traffic to it. Nobody in the chain takes anyone's word for anything.

Attestation is a deep enough topic that it gets its own post — what a TDX quote actually contains, what aTLS adds on top of vanilla TLS, and what an attacker would have to break at each layer.

What It Does Not Do

Honest boundaries matter more in security projects than feature lists, so, clearly stated:

  • Umbra governs traffic from inside the Dev CVM's network namespace. Tools running on your laptop are outside the boundary.
  • Secret scanning and injection apply to HTTP(S). Other TCP protocols can be tunneled when policy explicitly allows a destination, but they pass uninspected.
  • Attribution is per sandbox, not per nested container: the traffic log tells you which CVM, not which docker run inside it.
  • It is pre-1.0 software with sharp edges, and the repository documents its known deviations openly instead of papering over them.

Where It Stands

Umbra is developed spec-first: the documents under docs/specs/ are the behavioral contract, and a change is expected to land with its spec, implementation, and tests together. The release path is being built around deterministic image builds and SLSA provenance, because attested infrastructure is only as trustworthy as the supply chain that produced the measurements you verify against.

If any of this is relevant to what you are building — agent infrastructure, confidential computing, or the uncomfortable overlap of AI autonomy and security — the repository is the place to dig, starting with the v0 architecture plan. And watch this blog for the two follow-ups: one on attested TLS with atlas, and one on TEEs — both in this specific use case and in the broader landscape of privacy-enhancing technologies next to FHE, MPC, and zero-knowledge proofs.