What you need to know

A static API key leaking from a cron job is a bad day. The same key leaking from an autonomous agent is a different category of incident, because the agent is a machine for composing actions: it can search, read, call and forward on its own initiative, and a prompt-injected agent will do all of that under someone else's direction. When OpenAI's models escaped their evaluation sandbox and reached Hugging Face infrastructure, the pivot point was not exotic — it was credentials that were reachable and more powerful than the task required. Most agent stacks in production today would fail the same test: broad keys in environment variables, tokens that live for months, and no way to say which agent did what with which credential.

This guide is the identity layer of agent security. We have already covered the execution layer — sandboxing agents with microVMs and egress allowlists — and the input layer, defending against prompt injection. Those decide what an agent can execute and what can steer it. This one decides what an agent is allowed to do once it is running: which credentials it holds, how narrow they are, how long they live, where they are stored and how you take them away.

  • Scoped beats broad: a token that can read one bucket is an incident; a key that can administer your cloud account is a breach notification.
  • Short-lived beats long-lived: a 15-minute token turns most leaks into non-events.
  • OAuth 2.1 is now the baseline for agent-to-service auth — the MCP authorization spec expects it, with PKCE, for any internet-accessible MCP server as of mid-2026.
  • Workload identity beats shared secrets: IAM roles and managed identities mean there is no static key to steal in the first place.
  • Revocation is a design requirement, not an emergency procedure you improvise during an incident.

The threat model: how agent credentials go wrong

Five failure modes account for almost every credential incident we have seen or read a post-mortem about in agent systems. They are worth naming precisely, because each one maps to a specific control — and teams that skip the mapping tend to buy a vault, feel secure, and remain exposed to four of the five.

Failure modeReal consequencePrimary control
Over-scoped key (agent holds admin rights for a read task) A summarisation bug becomes a bulk delete; a prompt injection becomes account takeover Per-tool scopes; separate identities per agent
Long-lived token in an environment variable One leak is exploitable for months; rotation requires a redeploy nobody schedules Short-lived tokens minted per task; workload identity
Confused deputy via tool chains A low-trust caller steers a high-privilege tool through the agent in the middle Propagate the end-user's authority, not the agent's; risk-tier approvals
Cross-server token replay One compromised MCP server replays your token against every other server RFC 8707 resource indicators; audience-checked validation
Secrets in prompts, traces or logs Keys resurface in observability tools, eval datasets and model context Runtime injection outside model context; redaction middleware

The confused-deputy row deserves a sentence more. Agents are natural deputies: a support agent holds CRM write access so it can help customers, which means anyone who can influence its input is negotiating with the CRM through it. Input filtering helps, but the identity-layer fix is to make the agent act with the end user's authority where possible — exchange the user's token rather than wielding a god-mode service account — so the damage ceiling is the user's own permissions, not the platform's.

Five principles before any tooling

Tools change; these do not. First, least privilege: every credential an agent holds should be the minimum set of verbs on the minimum set of resources for the task at hand — read-only by default, writes named resource by resource. Second, short-lived over long-lived: prefer credentials that expire in minutes to hours; as of mid-2026 the common target for task-scoped access tokens is 15 minutes to 1 hour, with anything beyond a day treated as legacy. Third, workload identity over shared secrets: where the platform can attest to who the workload is — an IAM role in AWS Mumbai, a managed identity in Azure UK South — let it mint the credential, and delete the static key. Fourth, auditability: if two agents share one identity, your audit log answers "someone did this", which is no answer at all. One identity per agent is what makes the log mean something. Fifth, revocation-first design: assume you will need to cut off a single agent mid-incident, and check — before the incident — that doing so takes minutes, hits every service it talks to, and does not take down its neighbours.

Avoid

One shared "agents-service" account whose key is pasted into every agent's environment. It maximises blast radius, makes per-agent revocation impossible, and reduces your audit trail to a single anonymous actor.

OAuth 2.1 and MCP in practice

The good news is that the standards have converged. OAuth 2.1 consolidates OAuth 2.0 and its security best-current-practice documents into one baseline: PKCE is mandatory for authorization-code flows, the implicit grant and resource-owner password credentials are gone, refresh tokens are expected to be single-use with rotation, redirect URIs must match exactly, and bearer tokens travel only in the Authorization header — never in query strings. The MCP authorization spec builds directly on it: since the November 2025 revision, any internet-accessible MCP server is expected to implement OAuth 2.1 with PKCE (S256), acting as a resource server that validates tokens issued by an external authorization server. Agent runtimes — Claude Code, IDE extensions, laptop-hosted clients — are public clients: no client secret, PKCE always.

Two details do most of the security work. The first is RFC 8707 resource indicators: the client names the specific server a token is for, the authorization server bakes that URI into the token's audience, and every resource server rejects tokens not addressed to it. That single check eliminates cross-server replay — a token stolen from your billing MCP server is useless against your CRM one. The second is scope design. Coarse scopes recreate the over-scoped-key problem inside OAuth; the emerging convention is per-tool scopes:

ToolScope stringGranted toToken lifetime
read_filemcp:tool:read_file:readAll agents15 min
search_invoicesmcp:tool:search_invoices:readFinance agents only15 min
send_emailmcp:tool:send_email:executeNotifier agent; human-approved sends5 min
update_recordmcp:tool:update_record:writePer-record, via user token exchange5 min

A server-side agent authenticating as itself (client-credentials flow, with a signed workload assertion instead of a static client secret) requests exactly the scopes its next task needs, bound to exactly one server:

# Mint a task-scoped token for ONE MCP server -- not a master key
import httpx

TOKEN_URL = "https://auth.example.in/oauth2/token"

def mint_task_token() -> str:
    resp = httpx.post(TOKEN_URL, data={
        "grant_type": "client_credentials",
        "client_id": "billing-agent-runner",
        # Workload identity: a platform-signed JWT, no static client secret
        "client_assertion_type":
            "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
        "client_assertion": signed_workload_jwt(),
        # Least privilege: only the tools this task will call
        "scope": "mcp:tool:search_invoices:read mcp:tool:read_file:read",
        # RFC 8707: bind the token to a single resource server
        "resource": "https://mcp.billing.example.in",
    })
    resp.raise_for_status()
    token = resp.json()
    assert token["expires_in"] <= 900, "AS should issue short-lived tokens"
    return token["access_token"]

On the server side, validation is symmetrical: check the signature against the authorization server's keys, then check that the audience is you. A decoded token for the request above looks like this — the aud claim is the replay killer:

{
  "iss": "https://auth.example.in",
  "sub": "agent:billing-agent-runner",
  "aud": "https://mcp.billing.example.in",
  "scope": "mcp:tool:search_invoices:read mcp:tool:read_file:read",
  "exp": 1782627300,
  "iat": 1782626400
}
Pro tip

Write the scope-to-tool mapping down as data, not tribal knowledge: a checked-in JSON or YAML policy file that your MCP server loads and your reviewers can diff. A pull request that widens mcp:tool:read_file:read to :write should be as visible as a schema migration. If your agents also move money, the same scoping discipline applies to payment mandates — see our guide to agentic payments with x402, AP2 and ACP.

Secrets management for agent stacks

Some secrets cannot be replaced by tokens yet: database passwords, third-party API keys, signing keys. The rules for these are old, but agents raise the stakes because everything in the agent's environment is one tool call away from the model. Keep secrets in a dedicated store — HashiCorp Vault, AWS Secrets Manager (ap-south-1 for a Mumbai deployment, eu-west-2 for London), GCP Secret Manager or Azure Key Vault — and inject them at runtime into the process that needs them, never baked into container images and never, under any circumstances, into the model's context. A secret that has appeared in a prompt has also appeared in your traces, your eval datasets and possibly a screenshot.

Runtime injection in Kubernetes, using Vault's agent injector and a service account rather than a stored key, looks like this — the agent process reads a file that exists only inside its pod, holding a database credential Vault minted for this deployment and will expire on schedule:

# k8s Deployment: short-lived DB credential injected at runtime
# (fragment — selector and containers stanzas omitted for brevity)
apiVersion: apps/v1
kind: Deployment
metadata:
  name: support-agent
spec:
  template:
    metadata:
      annotations:
        vault.hashicorp.com/agent-inject: "true"
        vault.hashicorp.com/role: "support-agent"
        vault.hashicorp.com/agent-inject-secret-db: "database/creds/support-readonly"
        vault.hashicorp.com/agent-inject-template-db: |
          {{- with secret "database/creds/support-readonly" -}}
          DB_USER={{ .Data.username }}
          DB_PASS={{ .Data.password }}
          {{- end -}}
    spec:
      serviceAccountName: support-agent   # workload identity, not a static key

The second half of secrets hygiene is making sure nothing leaks back out through observability. Agent frameworks log enthusiastically — tool arguments, HTTP headers, full model turns — and that is exactly where keys end up. A redaction filter on every log handler is cheap insurance:

# Redaction middleware: scrub secrets before they reach any log sink
import logging
import re

SECRET_PATTERNS = [
    re.compile(r"sk-[A-Za-z0-9_-]{20,}"),                       # provider API keys
    re.compile(r"eyJ[\w-]{10,}\.[\w-]{10,}\.[\w-]{10,}"),       # JWTs
    re.compile(r"(?i)(api[_-]?key|secret|token|passw\w*)\s*[=:]\s*\S+"),
]

class RedactSecrets(logging.Filter):
    def filter(self, record: logging.LogRecord) -> bool:
        msg = record.getMessage()
        for pattern in SECRET_PATTERNS:
            msg = pattern.sub("[REDACTED]", msg)
        record.msg, record.args = msg, ()
        return True

for handler in logging.getLogger().handlers:
    handler.addFilter(RedactSecrets())
Watch out

Rotation you have never rehearsed is rotation you do not have. Schedule a quarterly drill: rotate one production secret end to end and confirm no agent needed a manual restart. If the drill hurts, the incident will hurt more.

Every article here is written by a Verified Builder. Want your name on the next one?

AI Tech Connect lists AI engineers, founders and researchers across India and the UK — and the people hiring browse it to find them. Adding your profile is free.

Become a Verified Builder →

Credential patterns by deployment shape

The right pattern depends on who runs the agent and whose data it touches. Three shapes cover most teams we speak to in Bengaluru, Mumbai, London and Manchester:

Deployment shapeRecommended credential patternWhy
Single-tenant internal agent (one team, own data) Cloud workload identity (AWS IAM role, GCP workload identity federation, Azure managed identity) + vault for residual secrets No static keys exist; the platform attests identity and mints short-lived credentials automatically
SaaS multi-tenant agents (your product acts for customers) Per-tenant OAuth clients; token exchange so the agent acts with the end user's authority; resource indicators per downstream service A compromise of one tenant's flow cannot reach another tenant; damage ceiling is the user's own permissions
Local development (Claude Code, MCP servers on laptops) OAuth 2.1 authorization-code + PKCE as a public client; OS keychain storage; separate dev-only accounts with no production scopes Laptops leak — tokens must be user-bound, short-lived and worthless against production

The first row is the one to internalise: in every major cloud, the replacement for "static key in an env var" already exists and is free. An agent on EC2 in ap-south-1 assumes an IAM role; a GKE workload in London federates its Kubernetes service account to a Google identity; an Azure container app uses its managed identity. In each case the platform proves who the workload is, credentials are minted with lifetimes measured in minutes to hours, and there is nothing durable to steal. If your agent stack still carries an AWS_SECRET_ACCESS_KEY anywhere, that is the single highest-leverage migration on this page.

Recommended

For laptop MCP servers, treat "it only runs locally" as an expired excuse: the MCP authorization spec's OAuth 2.1 baseline applies the moment a server is reachable beyond localhost, and applying it locally too means your dev and prod auth paths stay identical.

Auditing and kill switches

Least privilege limits what can go wrong; auditing tells you what actually did. The unit of record for agents is the tool call. For each one, emit a structured event: agent identity, a token identifier (never the token), scopes presented, tool name and normalised parameters, target resource, the authorisation decision, result summary and timestamp. Ship it to an append-only store the agent cannot write to — the same rule as sandbox logs, for the same reason: an agent that can edit its history has none. On top of the record, alert on the signals that precede misuse: spikes in denied-scope requests, first-time use of a scope an agent has never exercised, tool-call volume far outside baseline, and activity at hours when the owning team in India or the UK is asleep.

Then design the kill switch before you need it. "Good" in an incident looks like this: you identify the affected agent from the audit trail in minutes, disable that one identity at the authorization server, and — because tokens are short-lived and audience-bound — every downstream service stops honouring it within a token lifetime, while every other agent keeps working. Compare that with the static-key world, where revocation means finding every place a shared key was pasted, rotating it, and redeploying everything simultaneously, usually at 2 a.m. If personal data was touched, the audit trail is also what keeps your report to the regulator — under India's DPDP Act or UK GDPR — a bounded statement of fact rather than an admission that you cannot rule anything out.

Common pitfalls, and where to start on Monday

The failure patterns worth pinning above your desk: granting an agent your own user account "temporarily"; scoping tokens to the agent's possible tasks instead of its current one; long-lived refresh tokens stored next to the access tokens they mint (rotation exists — use it); logging full request objects "for debugging"; and testing revocation for the first time during an incident. None of these is exotic; all of them are common because each one is locally convenient.

The adoption order is pragmatic. This week: inventory every credential your agents hold — grep the deployment manifests, not your memory — and delete or narrow the ones no current task needs. Next: replace static cloud keys with workload identity, and move residual secrets into a vault with runtime injection and the redaction filter above. Then: bring your MCP servers up to the OAuth 2.1 baseline with PKCE and resource indicators, and design per-tool scopes as checked-in policy. Finally: wire the audit events and rehearse revoking a single agent end to end. Combined with the sandboxing layer and injection defence, this closes the triangle: what reaches the agent, what it can execute, and what it is allowed to do.

And if you have built this properly — an agent fleet with per-identity tokens, a revocation drill that passed, an audit trail that survived a real incident — that is precisely the kind of load-bearing infrastructure work worth showing where the people hiring can see it.