Inspiration
I went looking for the field in Google's Agent Registry that records what code an agent is running, and there isn't one.
The writable resource is Service. It has name, displayName, description,
interfaces[], registryResource, createTime, updateTime, and one of agentSpec /
mcpServerSpec / endpointSpec. That's the lot. No state, no approvalState, no labels
or annotations I could stash one in. The read-only Agent resource has an attributes map
but it's output-only with reserved keys, so I can't write a verdict there either.
Agent Identity doesn't help. Its SPIFFE ID is, in Google's words, "mapped directly to the resource URI where the agent is hosted". It tells you where an agent runs. It says nothing about what's running there.
Which means "enterprise-approved" currently just means somebody with the right IAM role deployed it.
So I tried it. I redeployed an agent with completely different code, under the same name and the same service account. The SPIFFE ID didn't change. The IAM grants didn't change. The Agent Registry entry came back byte-identical. Google's platform noticed nothing at all.
That's the problem I wanted to fix.
Who it's for
Not the engineer who wants to run an agent fleet. The person who has to approve it.
Every organisation has someone whose job is to sign off before something reaches production. They're not on the agent team, they usually can't read the agent's code, and they are the reason most of these fleets are still sitting in a staging project nobody talks about. The only question they can currently get a real answer to is "who deployed this", because that's the only question the platform answers. The question they actually need answered is "what is it running", and nobody can tell them.
So they say no. Not to be difficult. Saying yes would mean vouching for something they have no way of checking.
I built this for that person. It turns an approval they'd have to take on trust into one they can verify themselves, in a browser, with no account and no access to anything of mine. That's why the admission records are world-readable and why the verifier runs client-side rather than behind an API I control. An approval only I can read is just me asserting something.
What it does
Every agent runs as a workload on Google Confidential Space, so the hardware will attest
which image is running. When an agent wants to join the registry it has to hand over that
attestation. My control plane checks it offline against a Google root certificate pinned
into the binary at build time, twenty checks, and only then calls services.create on the
real Agent Registry.
I split approval from proof, because they answer different questions:
- an operator approves a digest up front, stored in
policies/admission - the attestation proves the code actually running is that digest
Either one alone is useless. An allowlist with no attestation is just a database row that anyone with write access can satisfy. An attestation with no allowlist is trust-on-first-use, where whoever launches a workload first gets to admit themselves.
After admission each agent re-attests every 60 seconds. If the digest stops matching what it was admitted under, the admission is revoked and the reason records both digests.
The fleet
I picked cloud estate hygiene, because it's a case where you genuinely can't run the agents without solving this first.
| Agent | Job | Cloud access |
|---|---|---|
| Warden | Orchestrates and holds the policy | no estate access |
| Surveyor | Inventories the estate, emits findings | broad read, no write |
| Assessor | Works out blast radius | no estate access |
| Executor | Performs one approved remediation | write, very narrow |
No agent has two of those powers, and the split is IAM rather than instruction text. Executor
gets compute.storageAdmin, so it can delete a disk and not an instance, and it is the only
agent in the project with any mutating permission at all. Assessor gets aiplatform.user so
it can call Gemini, plus the three housekeeping roles every workload here needs, and nothing
that lets it see the estate. It reasons about findings it is handed and has no way to go and
check them, which is the point. Every grant is in deploy/00-bootstrap.sh and I would rather
you read it than take the table's word for it.
Executor can delete production resources, and Assessor decides what Executor gets told to delete. Their IAM permissions are worth nothing unless you can prove what code is sitting behind them, and right now nobody can.
That's the friction I'm removing. Not a chore, but the security review that stops fleets like this reaching production in the first place.
The workers are RemoteA2aAgent, so every delegation is a network hop that can be checked.
In-process sub-agents would have been simpler and would have proved nothing, because the
entire claim is that a worker's admission gets checked on the hop, and there is no hop if
they share a process.
What happens when a worker misbehaves
This is the part I'd most like someone to actually read the code for, because it's the question I found hardest. A fleet where one agent tells another agent what to delete is only as good as what it does when a worker returns nonsense.
Warden's loop is in agents/warden/agent.py. One of these defences is permissions, one is
code, and three are instructions to a model, and that difference matters enough that I've
labelled each one. An instruction is a request. A permission is not.
- PERMISSIONS. Assessor cannot act on its own reasoning. It has no estate access, so the agent doing the thinking has no way to touch anything. A hallucination there produces a wrong opinion and nothing else. This one holds even if the model ignores every word I wrote.
- CODE.
before_tool_callbackrefuses to delegate to a worker Warden believes is revoked. It runs on every delegation, inrefuse_unadmitted_workers. - INSTRUCTION. A vague assessment isn't an assessment. Warden is told to accept one only if it cites the same finding id that was sent and reaches an explicit safe-or-unsafe conclusion with a stated reason, and to discard anything else. A model can disregard this. It cannot disregard the fact that Assessor had no credentials.
- INSTRUCTION. Findings go across verbatim, because Assessor can't see the estate and anything Warden paraphrases is information Assessor will never have.
- INSTRUCTION. No retries and no routing around a failed worker. Working around a worker that can't prove what it's running is the failure this project exists to prevent, so the instruction forbids it in those words, and doing nothing is named as the always-safe branch.
I'd rather label those three honestly than let the list imply more enforcement than there is. The permissions layer is what makes the fleet safe to run. The instructions are what make it useful.
There are two layers of admission checking and today only one of them runs. Warden's callback fails open on purpose, because it sits inside the agent whose behaviour is in question and anything that compromises Warden removes it. The design puts the real check at the gateway, which fails closed and runs somewhere the caller doesn't control. That gateway check is built and registered but not bound, so right now the delegation hop is guarded by the fast-fail callback and by IAM, and not by the gateway. I go into why below, and it isn't a story that flatters me.
Proof
Four agents in Google's Agent Registry, each one there because an attestation checked out:
surveyor -> https://surveyor.agents.agent-attest.internal
assessor -> https://assessor.agents.agent-attest.internal
warden -> https://warden.agents.agent-attest.internal
executor -> https://executor.agents.agent-attest.internal
An admission record, which anyone can read with no account and no credentials:
imageDigest sha256:7ed646755d8d02338baac93d614c487757495c8da320e54c96a30ba7199d604f
platform GCP_AMD_SEV
swname CONFIDENTIAL_SPACE
dbgstat disabled-since-boot
admitted true
lastAttestedDigest sha256:7ed646755d8d… (matches, heartbeat healthy)
attestationFailures 0
Then deploy/40-drift-demo.sh redeploys one agent with different code. Same name, same
service account, same registry entry, and a build nobody approved:
--- Google Agent Registry: STILL IDENTICAL. Nothing here changed. ---
name: projects/agent-attest-2026/locations/us-central1/services/assessor
registryResource: projects/746874807798/.../agentregistry-00000000-0000-0000-1e9d-7c2701a54ee6
--- agent-attest admission record ---
revoked : True
reason : Presented sha256:4452b7b78c8fd38f… but was admitted as sha256:7ed646755d8d…
The identity is unchanged and the registry entry is unchanged. The code is not.
Caught in about sixty seconds, unattended, on live infrastructure.
The video is that same run, live and unedited, in one take. The terminal, the project and the console are all the real ones, and the Confidential Space instances and the Cloud Run revision are both on screen. There's no cut between asking the question and getting the answer.
Don't take my word for it
The verifier has no DOM dependency and no Node-only dependency, so the same source that runs
in my Cloud Run control plane also runs in a browser. web/ bundles it into a page where you
paste an attestation token and recompute my verdict yourself, locally, against the pinned
root. You don't have to trust my servers for any of it. The bundle is about 66 kB gzipped,
measured by web/build.sh rather than guessed at. If you check it you'll get a number within
a couple of hundred bytes of mine, because the exact figure moves between gzip
implementations, which is itself a small lesson in stating precision you can't reproduce.
The admission records are world-readable in Firestore for the same reason.
I didn't write a separate browser implementation. Two implementations that agree would only prove I can write two things that agree.
How I built it
TypeScript control plane on Cloud Run, and it's the only service account in the project
granted roles/agentregistry.admin. Python for the Agent Gateway authorization extension,
because Google ships ext_authz SDKs for Python, Go and Java and only the Python one
implements the authorization protocol. I'd have preferred to keep it in one language, but
hand-rolling proto generation and H2C for a fail-closed security control on a deadline is the
kind of decision that looks clever for two days.
ADK agents in Python on Confidential Space, an orchestrator plus remote A2A workers. Firestore for the records. Gemini 3.5 Flash through Vertex AI.
The attestation verifier came from an earlier project of mine and I reused it under MIT, with
the registry binding rewritten from an on-chain lookup to Firestore. It's pure WebCrypto, no
WASM, and it implements the offline PKI (x5c) path instead of fetching Google's JWKS. That's
what lets the same code run in a browser without needing the network for any of the crypto.
Technologies used: Gemini 3.5 Flash via Vertex AI. Google ADK 2.6.2, an orchestrator plus
three remote A2A workers. Google Agent Registry (agentregistry.googleapis.com). Google
Confidential Space on AMD SEV. Agent Gateway with an Envoy ext_authz v3 authorization
extension. Cloud Run, Firestore, Cloud Asset Inventory, Artifact Registry, VPC with an
internal load balancer and private DNS. TypeScript on Node 22 for the control plane and
verifier, Python 3.12 for the agents and the gateway callout, Docker, WebCrypto, Cloudflare
Pages for the static verification page.
Other data sources used
Everything this system reasons about comes from Google Cloud or from the hardware. There are no third-party datasets, no scraped content, no external APIs and no user data anywhere in it.
- The Confidential Space attestation service, reached over the launcher socket at
/run/container_launcher/teeserver.sock. This is the only source of the claims that matter:image_id,hwmodel,swname,dbgstatand the launch policy. - Google's Confidential Space PKI root certificate, pinned into the build at compile time. Pinned rather than fetched, because a root you download at runtime is a root somebody else can change.
- The live Google Cloud estate of the project itself. Surveyor reads it through Cloud
Asset Inventory, which is why its only read grant is
cloudasset.viewer. Executor writes through the Compute API. The orphaned disk in the demo is a real disk in a real project. - Google Agent Registry, read and written through
agentregistry.googleapis.com. - Firestore, for admission records, the approved-digest policy and the attestation trail. World-readable by design.
Two tokens are committed under verifier/test/fixtures. One is real, minted by a TEE in this
project, so the test suite and the demo page both work on a fresh clone with no Google Cloud
account. The other is a deliberately forged chain the tests use to prove the pinned root
rejects it.
Against the Fortified Enterprise Fleet component list
The track names seven platform components. Here's what I actually used, including the ones I didn't, because a checklist answered honestly is worth more than one answered generously.
| Component | Used | What I did |
|---|---|---|
| Agent Registry | yes | Four agents in the real registry, each there because an attestation verified. This is the centre of the whole project. |
| Agent Identity | yes, as the subject | The entry is an argument about what a SPIFFE ID does and doesn't prove. It proves where an agent runs. It doesn't prove what runs there. |
| Agent Gateway | partly | Real authzExtension and gateway resources, verified against Envoy's actual ext_authz v3 protos. The policy binding never completed. |
| Agent Observability | partly | Append-only, world-readable attestation trail with per-check results on every heartbeat, so an incident is reconstructable after the fact. It's a durable audit log. It is not OpenTelemetry instrumented and I'm not going to call it that. |
| Agent Runtime | no | The workload has to mint its own hardware attestation, which means it has to be the thing running inside the TEE. That rules out a managed runtime for the fleet. The control plane does run on Cloud Run. |
| Memory Bank | no | There is no exemption store. Warden's instruction tells it to check its memory before acting on a resource a human has exempted, and nothing implements that memory, which is a gap I found writing this up rather than one I designed in. The state that genuinely has to survive weeks is the admission and attestation record, and that's in Firestore. Memory Bank is where the exemption list belongs and it's the next thing I'd build. |
| Model Armor | no | Orthogonal rather than skipped. Model Armor guards what goes into and out of a model. Attestation guards what the code is. Model Armor stops an injection changing what an agent does; attestation stops a redeploy changing what an agent is. Neither substitutes for the other, and a fleet that wants to be trusted in production needs both. |
Findings and learnings: five things that aren't in the docs
1. Google's own Agent Gateway samples set failOpen: true. The field defaults to false,
and the reference is clear that a failure then stops request processing. But every authz
sample in the Agent Gateway docs turns it on. Copy one and your authorization control quietly
becomes a suggestion, because anything that knocks out the callout also switches off the
check. An attacker can arrange that.
2. CONTENT_AUTHZ can't be made fail-closed at all. Under FULL_DUPLEX_STREAMED the docs
say the proxy fail-opens up to the first chunk of body data, and connection and header
failures fail open regardless of what you set failOpen to. Only REQUEST_AUTHZ gives you a
clean guarantee, so that's what I used.
3. Agent Registry silently requires interfaces[].url to be globally unique. This isn't
documented, and the check happens asynchronously inside the long-running operation, so if you
treat the HTTP 200 as success you see nothing wrong. The first registration always works. I
had a fleet sharing one image and one default port, so agent one went in and the other three
vanished. It looked like a race condition for hours. Worse, three agents ended up holding
admission records that pointed at registry entries which didn't exist, which is exactly the
kind of quiet divergence this whole project is meant to catch. The control plane now polls
the operation to completion.
4. Agent Gateway doesn't speak A2A. The protocols enum accepts MCP or
PROTOCOL_UNSPECIFIED. ADK's inter-agent transport is A2A. So Agent Gateway can't currently
sit in front of agent-to-agent calls in an ADK fleet, only MCP traffic. The Fortified
Enterprise Fleet brief pairs "Agent Gateway for unified routing and policy enforcement" with
"a scalable network of institutional agents", and those two things don't meet yet. I only
found this because the import validator prints its whole JSON schema when you give it a bad
document, which turned out to be the most useful error message on the platform.
5. A googleManaged gateway is describable long before it's usable. No state field,
describe finds it straight away, and binding a policy fails with "not yet provisioned in the
tenant project". Still failing four hours later.
Challenges
Confidential Space failures all look the same from outside, which is what made them expensive. The VM boots, reports RUNNING, writes nothing to Cloud Logging, and stops.
Three separate things caused that. A multi-platform image manifest, which the launcher can't
resolve. An env var missing from the image's allow_env_override label, which gets the
workload killed while the launcher logs Workload completed, so it reads like success. And
google-adk[a2a] not declaring sse-starlette, so the app builds fine and then dies during
Starlette's lifespan startup, after everything watching for a crash has decided it's running.
I stopped debugging in VMs after that. The launcher now checks the image is single-platform,
diffs every env var against the launch policy, and boots the container locally before it
spends a VM on it. Twenty seconds of docker run instead of a four-minute cycle that ends in
silence.
One more that cost me an afternoon. A private Cloud Run service answers unauthorised callers
with 404, not 403, which is deliberate so private services don't leak their existence. Pair
that with status.url sometimes holding a stale hostname that gcloud run services proxy
also reads, and you get a perfectly healthy service reporting Ready: True and 404ing on
every URL you try.
What this doesn't do
These live in the code as STANDING_CAVEATS and REGISTRY_CAVEATS and get printed with
every result, including the ones that pass. I didn't write them for this page.
- Attestation proves which image ran. It doesn't prove the code is any good. A TEE running malicious code produces a perfectly valid attestation.
- Pinning an image pins the code, not the behaviour. It says nothing about model weights or prompts pulled in at runtime from somewhere else.
- Revocation isn't checked. The leaf has no CRL distribution point and the intermediate's CRL is served without CORS headers, so a browser can't fetch it. With 60-day leaves and a hard root pin the residual risk is a revoked intermediate. Rare, but not zero.
- The gateway doesn't re-attest on every request. It can't, because only the workload can mint a token and the caller isn't the workload. It checks admission, revocation and freshness, and the digest comparison happens on the heartbeat. An agent that drifts inside the 300-second window stays reachable until it misses a beat.
- Verifying in your browser means you don't have to trust the server running the workload. You do still have to trust whoever served you the JavaScript.
And on scope. The authorization extension is a real registered Google resource and I've
verified it against Envoy's actual ext_authz v3 protos. The gateway exists too. But the
policy binding never completed, so the gateway isn't enforcing anything right now. The
decision it would serve is live at POST /authorize, and the callout holds no policy of its
own, so binding it later needs no code change. I'd rather say that than imply an enforcement
path I haven't actually exercised.
What's next
Bind the policy when tenant provisioning finally goes through. Report the failOpen sample
defaults and the interfaces[].url uniqueness constraint to Google as doc issues. Shorten the
freshness window, which only costs Firestore writes. And if Agent Gateway picks up A2A, the
same extension covers inter-agent hops with nothing to change.
Built With
- a2a
- agent-gateway
- agent-registry
- amd-sev
- artifact-registry
- attestation
- cloud-run
- confidential-computing
- confidential-space
- docker
- envoy
- firestore
- gemini
- google-adk
- google-cloud
- grpc
- node.js
- python
- supply-chain-security
- tee
- typescript
- vertex-ai
- webcrypto
- zero-trust
Log in or sign up for Devpost to join the conversation.