[{"data":1,"prerenderedAt":600},["Reactive",2],{"content-query-ZKU4xHc7CT":3},{"_path":4,"_dir":5,"_draft":6,"_partial":7,"_locale":8,"title":9,"description":10,"date":11,"image":12,"draft":6,"body":13,"_type":595,"_id":596,"_source":597,"_file":598,"_extension":599},"/blog/umbra-attested-sandboxes-for-ai-agents","blog",true,false,"","Umbra: Attested Sandboxes for AI Coding Agents","AI coding agents do their best work with full autonomy, which is exactly what makes them dangerous. Umbra is an open-source stack that runs agents inside attested confidential VMs where every byte of egress goes through a policy-enforcing proxy, and where you don't have to take anyone's word for what is running.","2026-08-17T00:00:00.000Z","/img/umbra-architecture.png",{"type":14,"children":15,"toc":586},"root",[16,41,54,68,75,83,88,149,154,159,233,246,252,281,310,331,336,342,363,376,382,394,422,427,433,438,473,485,490,496,501,539,545,558,580],{"type":17,"tag":18,"props":19,"children":20},"element","p",{},[21,24,31,33,39],{"type":22,"value":23},"text","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 ",{"type":17,"tag":25,"props":26,"children":28},"code",{"className":27},[],[29],{"type":22,"value":30},"--dangerously-skip-permissions",{"type":22,"value":32}," 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 ",{"type":17,"tag":25,"props":34,"children":36},{"className":35},[],[37],{"type":22,"value":38},"AWS_SECRET_ACCESS_KEY",{"type":22,"value":40}," somewhere you didn't intend.",{"type":17,"tag":18,"props":42,"children":43},{},[44,46,52],{"type":22,"value":45},"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 ",{"type":17,"tag":25,"props":47,"children":49},{"className":48},[],[50],{"type":22,"value":51},"pip install",{"type":22,"value":53},", clone repositories, and call APIs on its own.",{"type":17,"tag":18,"props":55,"children":56},{},[57,66],{"type":17,"tag":58,"props":59,"children":63},"a",{"href":60,"rel":61},"https://github.com/concrete-security/umbra",[62],"nofollow",[64],{"type":22,"value":65},"Umbra",{"type":22,"value":67}," 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.",{"type":17,"tag":69,"props":70,"children":72},"h2",{"id":71},"the-shape-of-the-system",[73],{"type":22,"value":74},"The Shape of the System",{"type":17,"tag":18,"props":76,"children":77},{},[78],{"type":17,"tag":79,"props":80,"children":82},"img",{"alt":81,"src":12},"Umbra v0 architecture",[],{"type":17,"tag":18,"props":84,"children":85},{},[86],{"type":22,"value":87},"Umbra has four components:",{"type":17,"tag":89,"props":90,"children":91},"ul",{},[92,112,129,139],{"type":17,"tag":93,"props":94,"children":95},"li",{},[96,110],{"type":17,"tag":97,"props":98,"children":99},"strong",{},[100,102,108],{"type":22,"value":101},"The ",{"type":17,"tag":25,"props":103,"children":105},{"className":104},[],[106],{"type":22,"value":107},"umbra",{"type":22,"value":109}," CLI",{"type":22,"value":111}," (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.",{"type":17,"tag":93,"props":113,"children":114},{},[115,120,122,127],{"type":17,"tag":97,"props":116,"children":117},{},[118],{"type":22,"value":119},"The Console",{"type":22,"value":121}," (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 ",{"type":17,"tag":97,"props":123,"children":124},{},[125],{"type":22,"value":126},"not",{"type":22,"value":128}," part of the trusted data path.",{"type":17,"tag":93,"props":130,"children":131},{},[132,137],{"type":17,"tag":97,"props":133,"children":134},{},[135],{"type":22,"value":136},"The Dev CVM",{"type":22,"value":138}," (Intel TDX, one or more per developer). The confidential VM where your sandbox and your agent actually live.",{"type":17,"tag":93,"props":140,"children":141},{},[142,147],{"type":17,"tag":97,"props":143,"children":144},{},[145],{"type":22,"value":146},"The Security CVM",{"type":22,"value":148}," (Intel TDX, one per team). The egress proxy that every outbound byte from every sandbox must pass through.",{"type":17,"tag":18,"props":150,"children":151},{},[152],{"type":22,"value":153},"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.",{"type":17,"tag":18,"props":155,"children":156},{},[157],{"type":22,"value":158},"Day to day you don't think about any of this:",{"type":17,"tag":160,"props":161,"children":165},"pre",{"className":162,"code":163,"language":164,"meta":8,"style":8},"language-bash shiki shiki-themes monokai","umbra auth login https://console.example.com\numbra cvm launch\numbra claude   # or: umbra ssh / codex / code / cursor\n","bash",[166],{"type":17,"tag":25,"props":167,"children":168},{"__ignoreMap":8},[169,196,214],{"type":17,"tag":170,"props":171,"children":174},"span",{"class":172,"line":173},"line",1,[175,180,186,191],{"type":17,"tag":170,"props":176,"children":178},{"style":177},"--shiki-default:#A6E22E",[179],{"type":22,"value":107},{"type":17,"tag":170,"props":181,"children":183},{"style":182},"--shiki-default:#E6DB74",[184],{"type":22,"value":185}," auth",{"type":17,"tag":170,"props":187,"children":188},{"style":182},[189],{"type":22,"value":190}," login",{"type":17,"tag":170,"props":192,"children":193},{"style":182},[194],{"type":22,"value":195}," https://console.example.com\n",{"type":17,"tag":170,"props":197,"children":199},{"class":172,"line":198},2,[200,204,209],{"type":17,"tag":170,"props":201,"children":202},{"style":177},[203],{"type":22,"value":107},{"type":17,"tag":170,"props":205,"children":206},{"style":182},[207],{"type":22,"value":208}," cvm",{"type":17,"tag":170,"props":210,"children":211},{"style":182},[212],{"type":22,"value":213}," launch\n",{"type":17,"tag":170,"props":215,"children":217},{"class":172,"line":216},3,[218,222,227],{"type":17,"tag":170,"props":219,"children":220},{"style":177},[221],{"type":22,"value":107},{"type":17,"tag":170,"props":223,"children":224},{"style":182},[225],{"type":22,"value":226}," claude",{"type":17,"tag":170,"props":228,"children":230},{"style":229},"--shiki-default:#88846F",[231],{"type":22,"value":232},"   # or: umbra ssh / codex / code / cursor\n",{"type":17,"tag":18,"props":234,"children":235},{},[236,238,244],{"type":22,"value":237},"Agent sessions run under ",{"type":17,"tag":25,"props":239,"children":241},{"className":240},[],[242],{"type":22,"value":243},"dtach",{"type":22,"value":245}," on the CVM, so a dropped connection reattaches to the same running agent instead of killing it mid-refactor.",{"type":17,"tag":69,"props":247,"children":249},{"id":248},"full-sudo-inside-no-way-around",[250],{"type":22,"value":251},"Full sudo Inside, No Way Around",{"type":17,"tag":18,"props":253,"children":254},{},[255,257,263,265,271,273,279],{"type":22,"value":256},"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: ",{"type":17,"tag":25,"props":258,"children":260},{"className":259},[],[261],{"type":22,"value":262},"sudo apt install",{"type":22,"value":264},", editing ",{"type":17,"tag":25,"props":266,"children":268},{"className":267},[],[269],{"type":22,"value":270},"/etc",{"type":22,"value":272},", ",{"type":17,"tag":25,"props":274,"children":276},{"className":275},[],[277],{"type":22,"value":278},"ptrace",{"type":22,"value":280},", even a rootful Docker daemon for nested containers, devcontainers, and containerized builds.",{"type":17,"tag":18,"props":282,"children":283},{},[284,286,292,294,301,303,308],{"type":22,"value":285},"The trick is ",{"type":17,"tag":287,"props":288,"children":289},"em",{},[290],{"type":22,"value":291},"where",{"type":22,"value":293}," the boundary sits. The sandbox runs under ",{"type":17,"tag":58,"props":295,"children":298},{"href":296,"rel":297},"https://github.com/nestybox/sysbox",[62],[299],{"type":22,"value":300},"sysbox",{"type":22,"value":302},", 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 ",{"type":17,"tag":287,"props":304,"children":305},{},[306],{"type":22,"value":307},"mapped",{"type":22,"value":309}," identity and says no. The trust boundary is a namespace, not a permission prompt.",{"type":17,"tag":18,"props":311,"children":312},{},[313,315,321,323,329],{"type":22,"value":314},"Network bypass is prevented by topology rather than by firewall rules. The sandbox's only network is a Docker bridge declared ",{"type":17,"tag":25,"props":316,"children":318},{"className":317},[],[319],{"type":22,"value":320},"internal: true",{"type":22,"value":322},", 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 ",{"type":17,"tag":25,"props":324,"children":326},{"className":325},[],[327],{"type":22,"value":328},"NET_ADMIN",{"type":22,"value":330}," 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.",{"type":17,"tag":18,"props":332,"children":333},{},[334],{"type":22,"value":335},"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.",{"type":17,"tag":69,"props":337,"children":339},{"id":338},"every-byte-through-the-checkpoint",[340],{"type":22,"value":341},"Every Byte Through the Checkpoint",{"type":17,"tag":18,"props":343,"children":344},{},[345,347,354,356,361],{"type":22,"value":346},"The Security CVM runs a ",{"type":17,"tag":58,"props":348,"children":351},{"href":349,"rel":350},"https://mitmproxy.org/",[62],[352],{"type":22,"value":353},"mitmproxy",{"type":22,"value":355},"-based egress proxy that terminates TLS, applies policy, and re-encrypts outbound. Policy comes from ",{"type":17,"tag":287,"props":357,"children":358},{},[359],{"type":22,"value":360},"profiles",{"type":22,"value":362}," — 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.",{"type":17,"tag":18,"props":364,"children":365},{},[366,368,374],{"type":22,"value":367},"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 ",{"type":17,"tag":25,"props":369,"children":371},{"className":370},[],[372],{"type":22,"value":373},"curl",{"type":22,"value":375}," 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.",{"type":17,"tag":69,"props":377,"children":379},{"id":378},"secrets-the-agent-can-use-but-never-see",[380],{"type":22,"value":381},"Secrets the Agent Can Use but Never See",{"type":17,"tag":18,"props":383,"children":384},{},[385,387,392],{"type":22,"value":386},"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 ",{"type":17,"tag":287,"props":388,"children":389},{},[390],{"type":22,"value":391},"at the proxy",{"type":22,"value":393},", after the allow/deny and secret-scanning checks have passed, only for the destinations the policy names.",{"type":17,"tag":18,"props":395,"children":396},{},[397,399,405,407,413,415,420],{"type":22,"value":398},"So the agent can call the GitHub API all day without ever being able to ",{"type":17,"tag":25,"props":400,"children":402},{"className":401},[],[403],{"type":22,"value":404},"cat",{"type":22,"value":406}," 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 ",{"type":17,"tag":25,"props":408,"children":410},{"className":409},[],[411],{"type":22,"value":412},"umbra secret set --host",{"type":22,"value":414},", bound to specific hosts, and it is resolved per ",{"type":17,"tag":287,"props":416,"children":417},{},[418],{"type":22,"value":419},"CVM owner",{"type":22,"value":421}," 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.",{"type":17,"tag":18,"props":423,"children":424},{},[425],{"type":22,"value":426},"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.",{"type":17,"tag":69,"props":428,"children":430},{"id":429},"why-would-you-believe-any-of-this",[431],{"type":22,"value":432},"Why Would You Believe Any of This?",{"type":17,"tag":18,"props":434,"children":435},{},[436],{"type":22,"value":437},"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.",{"type":17,"tag":18,"props":439,"children":440},{},[441,443,448,450,455,457,464,466,471],{"type":22,"value":442},"Intel TDX measures what a CVM boots — firmware, kernel, OS image, and the exact container compose it runs — into hardware-signed ",{"type":17,"tag":287,"props":444,"children":445},{},[446],{"type":22,"value":447},"quotes",{"type":22,"value":449},". Umbra wraps every TEE connection in ",{"type":17,"tag":97,"props":451,"children":452},{},[453],{"type":22,"value":454},"aTLS",{"type":22,"value":456}," (attested TLS, via the ",{"type":17,"tag":58,"props":458,"children":461},{"href":459,"rel":460},"https://crates.io/crates/atlas-rs",[62],[462],{"type":22,"value":463},"atlas-rs",{"type":22,"value":465}," 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 ",{"type":17,"tag":287,"props":467,"children":468},{},[469],{"type":22,"value":470},"to the code you expected",{"type":22,"value":472},". 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.",{"type":17,"tag":18,"props":474,"children":475},{},[476,478,483],{"type":22,"value":477},"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 ",{"type":17,"tag":287,"props":479,"children":480},{},[481],{"type":22,"value":482},"your",{"type":22,"value":484}," 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.",{"type":17,"tag":18,"props":486,"children":487},{},[488],{"type":22,"value":489},"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.",{"type":17,"tag":69,"props":491,"children":493},{"id":492},"what-it-does-not-do",[494],{"type":22,"value":495},"What It Does Not Do",{"type":17,"tag":18,"props":497,"children":498},{},[499],{"type":22,"value":500},"Honest boundaries matter more in security projects than feature lists, so, clearly stated:",{"type":17,"tag":89,"props":502,"children":503},{},[504,509,514,534],{"type":17,"tag":93,"props":505,"children":506},{},[507],{"type":22,"value":508},"Umbra governs traffic from inside the Dev CVM's network namespace. Tools running on your laptop are outside the boundary.",{"type":17,"tag":93,"props":510,"children":511},{},[512],{"type":22,"value":513},"Secret scanning and injection apply to HTTP(S). Other TCP protocols can be tunneled when policy explicitly allows a destination, but they pass uninspected.",{"type":17,"tag":93,"props":515,"children":516},{},[517,519,524,526,532],{"type":22,"value":518},"Attribution is per sandbox, not per nested container: the traffic log tells you ",{"type":17,"tag":287,"props":520,"children":521},{},[522],{"type":22,"value":523},"which CVM",{"type":22,"value":525},", not which ",{"type":17,"tag":25,"props":527,"children":529},{"className":528},[],[530],{"type":22,"value":531},"docker run",{"type":22,"value":533}," inside it.",{"type":17,"tag":93,"props":535,"children":536},{},[537],{"type":22,"value":538},"It is pre-1.0 software with sharp edges, and the repository documents its known deviations openly instead of papering over them.",{"type":17,"tag":69,"props":540,"children":542},{"id":541},"where-it-stands",[543],{"type":22,"value":544},"Where It Stands",{"type":17,"tag":18,"props":546,"children":547},{},[548,550,556],{"type":22,"value":549},"Umbra is developed spec-first: the documents under ",{"type":17,"tag":25,"props":551,"children":553},{"className":552},[],[554],{"type":22,"value":555},"docs/specs/",{"type":22,"value":557}," 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.",{"type":17,"tag":18,"props":559,"children":560},{},[561,563,569,571,578],{"type":22,"value":562},"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 ",{"type":17,"tag":58,"props":564,"children":566},{"href":60,"rel":565},[62],[567],{"type":22,"value":568},"repository",{"type":22,"value":570}," is the place to dig, starting with the ",{"type":17,"tag":58,"props":572,"children":575},{"href":573,"rel":574},"https://github.com/concrete-security/umbra/blob/main/docs/v0_plan.md",[62],[576],{"type":22,"value":577},"v0 architecture plan",{"type":22,"value":579},". 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.",{"type":17,"tag":581,"props":582,"children":583},"style",{},[584],{"type":22,"value":585},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"title":8,"searchDepth":198,"depth":198,"links":587},[588,589,590,591,592,593,594],{"id":71,"depth":198,"text":74},{"id":248,"depth":198,"text":251},{"id":338,"depth":198,"text":341},{"id":378,"depth":198,"text":381},{"id":429,"depth":198,"text":432},{"id":492,"depth":198,"text":495},{"id":541,"depth":198,"text":544},"markdown","content:blog:umbra-attested-sandboxes-for-ai-agents.md","content","blog/umbra-attested-sandboxes-for-ai-agents.md","md",1788420383441]