Zero-credential agents — Victor Agnew

Victor Agnew

Staff AI Platform Engineer · Vancouver, BC

Boring deploys. work/ Safe AI. ai-lab/ Happy teams. virtual-office.md

PIPELINE
>20 min → <5 min
DEPLOYS
weekly → hundreds/week
FAILED PIPELINES
<1%
CREDENTIAL ROTATION
6 months → 24 h
REVIEW ENV
~$9/day
GCP SPEND
−40% YoY
MCP TOOLS
160+ → 23
VIRTUAL OFFICE
~1 h 40 min
virtual-office.md Virtual office three.js · Cloudflare Workers · LiveKit Built in a day; 26 days later, 1,600+ commits and 12 coworkers committing.
Close case-studies/zero-credential-agents.md Back

case-studies/zero-credential-agents.md

Zero-credential agents

Agents on the Claude Agent SDK that hold no credentials, where every call leaves through one gateway that checks a short-lived run token and injects the real credentials at the edge.

ArborXR · Staff Platform Engineer

Credentials in an agent pod
zero
Control-plane calls per request
none
Refresh run time
55 min → 6–9 min
  • Claude Agent SDK
  • Envoy AI Gateway
  • HashiCorp Vault
  • Go
  • Kubernetes
  • MCP
  • Helm

The problem

Our agents read content we don't control. A scheduled one refreshes an internal dashboard from several data sources. A prompt injection in any of them could make the agent leak its credentials, so the agent pod must hold none.

All egress therefore goes through one policy enforcement point, which injects upstream credentials at the edge. That needs least privilege per run, checked offline at the gateway with no callback to a control plane. Runs in flight must also survive a Vault or control-plane outage.

Options

Mesh identity alone is per service account, not per run. Our Istio mesh already gives pods SPIFFE identities, but Envoy Gateway can't authorize on them.

SPIRE JWT-SVIDs can carry custom claims, but SPIRE takes no per-request input from the caller. It can't put a run's connections in the token.

Off-the-shelf OAuth issuers (Keycloak, Hydra, Zitadel, dex) can't mint caller-supplied, per-request claims under token exchange.

A credential sidecar per pod was lighter, but one shared gateway gave a single place for credential injection, rotation and token metering.

A custom token service is what I built: Vault-Transit-signed ES256 run tokens with a connections allow-list, validated by Envoy's jwt_authn filter against a local JWKS.

What I built

I built zero-credential agents on the Claude Agent SDK.

One runtime

The runtime is one config-driven image on the Claude Agent SDK, behind a harness interface so another agent SDK can be added without a rewrite. Each run is a Kubernetes Job. Data sources are MCP servers, which we call connections. The pod gets a service account with no RBAC.

One way out

Every model and tool call leaves through Envoy AI Gateway. It validates the run token, strips it and injects the real upstream credential. Model calls go to Claude, with a second route as failover. Each MCP connection has its own route, which denies any token whose connections claim doesn't name it. BigQuery needs no key: its MCP server runs in the cluster under Workload Identity. The gateway also labels token usage per run.

[ FIG. 1 ] Every model and tool call leaves through one gateway
Every model and tool call leaves through one gateway Three columns. Left: an "agent pod" box containing "Claude Agent SDK runtime", with two small tags beneath it: "no credentials", "zero-RBAC service account". One arrow from the agent pod to the middle column, labelled "run token". Middle: an "Envoy AI Gateway" box listing three numbered steps in mono: "1 validate the run token (local JWKS)", "2 check the connections claim", "3 strip the token, inject the upstream credential". Right: two groups. "LLM": "Claude (primary route)" above "Claude (failover route)", with a dashed arrow between them labelled "failover". "MCP connections": "SaaS MCP server" (tag "API key injected") and "BigQuery MCP server" (tag "in cluster · Workload Identity · no key"). Arrows from the gateway to the LLM group labelled "LLM route" and to each MCP server labelled "one route per connection". agent pod Claude Agent SDK runtime no credentials zero-RBAC service account run token Envoy AI Gateway 1 validate the run token (local JWKS) 2 check the connections claim 3 strip the token, inject the upstream credential LLM Claude (primary route) failover Claude (failover route) LLM route MCP connections SaaS MCP server API key injected BigQuery MCP server in cluster · Workload Identity · no key one route per connection
Run tokens

Each run gets a short-lived ES256 JWT. Its subject is the run ID, its audience is the gateway, and its connections claim lists what the run may reach. Vault Transit signs it, so the key never leaves Vault. The gateway validates it against a local JWKS and never calls Vault or the registry per request. When the registry launches a run, the token expires five minutes after the job's deadline.

If Vault is down, no new run starts, but runs in flight finish. There's no fallback signer: an identity issuer has to fail closed.

The agent registry

The registry is the control plane, a Go service. It defines agents and their versions, each pinned to an image digest, and grants each agent connections with an exact list of tools. Its token service writes every mint to a ledger before the token goes out. It launches runs as Kubernetes Jobs and never proxies agent traffic.

[ FIG. 2 ] How a run gets its token
How a run gets its token A left-to-right sequence of six numbered steps in mono: 1 run requested (API client or webhook) → 2 agent registry: active version and enabled grants → 3 token service mints the run token (ledger row written first) → 4 Vault Transit signs it (ES256, key stays in Vault) → 5 Kubernetes Job on the pinned image digest → 6 gateway validates the token against a local JWKS, no call back. Under step 4, a small note: "Vault down: no new runs; runs in flight finish." 1 run requested (API client or webhook) 2 agent registry: active version and enabled grants 3 token service mints the run token (ledger row written first) 4 Vault Transit signs it (ES256, key stays in Vault) 5 Kubernetes Job on the pinned image digest 6 gateway validates the token against a local JWKS, no call back Vault down: no new runs; runs in flight finish.
Acting for a person

Later I added RFC 8693 token exchange, for runs that act on someone's behalf. A client signs a short-lived subject token, and the token service verifies it offline against that client's keys only, with no shared secret. Only clients registered to assert users can name a person. The run token then carries the person, their organisation and an actor chain for the audit trail. The gateway forwards the person and organisation upstream as headers, and strips inbound copies.

[ FIG. 3 ] Acting for a person, through subject-token exchange
Acting for a person, through subject-token exchange Left: a "client service" box that signs a short-lived "subject token" (ES256) and publishes a JWKS. Arrow to a middle "token service" box listing three checks in mono, each failing closed: "1 signature against that client's keys only", "2 caller registered for that issuer", "3 issuer may assert users". Arrow to a "run token" card listing claims in mono: "sub: run id", "connections", "obo_sub: person", "obo_org: organisation", "act: actor chain (audit)". Arrow from the card to a "gateway" box, which sends the run, person and organisation upstream as headers, with a small note: "inbound copies stripped". client service publishes a JWKS short-lived subject token (ES256) token service 1 signature against that client's keys only 2 caller registered for that issuer 3 issuer may assert users each failing closed run token sub: run id connections obo_sub: person obo_org: organisation act: actor chain (audit) gateway the run, person and organisation upstream as headers inbound copies stripped
A hidden timeout

Envoy AI Gateway writes a 60-second request timeout into every route it generates unless you set one, and that caps each retry too. Under it, a refresh run took 55 minutes and cost $3.49. With 600 seconds on every rule, runs took 6 to 9 minutes and cost $1.40 to $2.30.

Results

  • Agents hold no credentials. Every call leaves through a gateway that injects credentials at the edge.
  • Each run gets a short-lived signed token, scoped to the connections it may use and validated offline at the gateway.
  • An agent registry defines the agents, grants their access and issues the tokens.
  • The platform is built, deployed and tested internally.
next case study Dynamic credentials and just-in-time access