Skip to content
Back to Insights
PlatformBy KE Engineering Team

Workload Identity for Agents

Workload Identity for AgentsPlatform cover for Workload Identity for Agentsshort-lived credentialsPLATFORMWorkload Identity forAgents// SHORT-LIVED, ATTESTED

Every guide to calling a model API starts with export ANTHROPIC_API_KEY=.... That is fine for a notebook. In production it means a long-lived secret sitting in an environment variable, visible to every process in the container, readable by any code the agent generates, and rotated when someone remembers.

Agents make this worse than a normal service, because the agent's code is partly written at runtime by a model that may have read adversarial content. A credential the code can see is a credential the model can exfiltrate.

Identity instead of secrets

The platform pattern that fixes this is workload identity: the running workload proves what it is to an identity provider, receives a short-lived token scoped to what it needs, and that token is used once and expires. There is no long-lived secret to leak because there is no long-lived secret.

AWS, Google Cloud, and Azure all have this (IAM Roles for Service Accounts or EKS Pod Identity on AWS, Workload Identity Federation for GKE on Google Cloud, and Microsoft Entra Workload ID on Azure), and so does Kubernetes. SPIFFE and SPIRE do it portably. The agent runtime gets an identity document from the platform, exchanges it for a scoped token, and the model API key never exists in the agent's environment at all.

The practical split: if you call models through your cloud (Bedrock, Vertex AI, Azure OpenAI), the call authenticates with that cloud's IAM and no static key is involved. Direct provider APIs still take a static key, and that is what the broker below is for.

The broker pattern

For third-party APIs that only accept static keys, put a broker in the trust boundary.

agent runtime          credential broker           provider API    │                        │                         │    │ "call model, args"     │                         │    │───────────────────────▶│ verify workload identity │    │                        │ check policy for caller  │    │                        │ attach API key           │    │                        │────────────────────────▶│    │                        │◀────────────────────────│    │◀───────────────────────│ return result, not key   │

The agent never holds the key. It holds an identity, asks the broker to act, and the broker decides. Policy lives in one place. Rotation happens in one place. A compromised agent can ask the broker for things, and the broker can say no.

Scope the token to the task

A token that can call the model API isn't the same as a token that can call any tool. Issue per-task tokens with the capabilities that task needs, bound to the principal who initiated it.

typescript
const token = await identity.issue({  workload: "support-agent",  principal: request.principal,       // the user, carried down  scope: ["model:invoke", "tickets:read", "tickets:comment"],  ttl: "5m",  audience: "internal-broker",});

Five minutes is generous. A task that runs longer refreshes. A leaked token is only good for minutes, which turns most leaks into non-events.

Sandboxed code gets nothing

If the agent executes generated code, that sandbox has no identity and no tokens; it asks the runtime to act on its behalf. Sandboxing Agents That Write Code covers the rest.

Audit falls out naturally

Every broker call is logged with workload, principal, scope, and outcome. When an agent did something it should not have, the broker log says which identity asked, on whose behalf, and whether policy allowed it. That is an audit trail you can't get from an API key in an env var, because the key doesn't know who is holding it.