In August 2025, attackers exported Salesforce data from more than 700 companies. Cloudflare, Zscaler, Palo Alto Networks.
Security vendors were breached. Nobody hacked Salesforce itself.
The attackers stole OAuth tokens from Drift, an AI chatbot bolted onto those Salesforce instances, and the tokens quietly kept working for ten days.
That incident is the cleanest preview I've seen of the next few years.
An AI agent gets broad access because it's useful, ends up holding more permissions than any single employee, and its credentials outlive everybody's attention.
Production teams are converging on seven patterns, the same way backend teams converged on resilience patterns once enough things had caught fire.
Share this post & I’ll send you some rewards for the referrals.
1. Give every agent its own identity
Most agents in production borrow someone else's identity.
Either they run as the user (your session, your OAuth grant) or as svc-automation, the shared service account that fourteen other things also use. Both setups fail the same test. Something went wrong at 3 AM, so who did it?
The pattern is to make the agent a first-class principal. Its own ID, its own credentials, its own permissions, and a named human owner. Microsoft went as far as adding a new identity type to Entra ID, next to users and service principals; every agent identity records a "sponsor", the person accountable for it. Google's version gives each agent a SPIFFE ID with certificates that rotate every 24 hours and no way to mint a long-lived key at all.
You don't need either product to apply the idea.
One identity per agent, never shared. An owner you can name. Credentials that belong to the agent itself.
2. Delegate, don't impersonate
Here's the trap almost everyone falls into first. The agent needs to act for the user, so you hand it the user's token. It works. And your audit log now says you deleted those records, approved that PR, sent that email. Your name on actions you never saw.
The standards answer is delegation via OAuth token exchange (RFC 8693). The agent trades the user's token for a new one that keeps the user as the subject and adds itself as the actor:
{
"sub": "petar@example.com",
"act": { "sub": "agent:content-creator" },
"scope": "crm:read calendar:write",
"exp": 1756723200
}One token, both facts. Authority flows from the user. The acting party is the agent.
The log can finally tell the difference between "Petar did this" and "Petar's agent did this for Petar".
You don't have to build the exchange yourself.
Descope's Agentic Identity Hub issues delegated agent tokens as a standard OAuth token exchange, and the exchanged token keeps the delegation chain.
This way, audit logs can trace an action from the user through every agent in between. That's the property this pattern is after.
3. Permissions are an intersection, not a union
An agent that serves many users accumulates access. If the agent's own access is the ceiling, you've built a classic confused deputy. I ask the agent for a document I'm not allowed to see, and the agent, which can see everything, cheerfully fetches it. That's a data leak with extra steps.
The fix is what identity teams call the intersection rule. A delegated call may do only what the agent is provisioned for AND what the requesting user is currently allowed to do. It's evaluated on every call, because the user's permissions can change mid-task.
The same rule applies inside RAG. If your retrieval layer doesn't check document-level permissions for the asking user, the model becomes the leak. That's the problem fine-grained authorization for RAG exists to solve. It filters what the agent retrieves down to what this user may read, before the model sees a word of it.
4. Kill the static API key
Two numbers to sit with. GitGuardian counted 23.77 million new hardcoded secrets pushed to public GitHub in 2024. And 70% of the secrets leaked in 2022 were still valid years later. Nobody rotates what nobody remembers.
The 38TB Microsoft leak is the same story at a bigger scale. One Azure storage token, scoped to the entire account with full-control rights, valid since 2020. Three years live, nobody looking.
The production pattern is a token vault. AWS built one into AgentCore Identity, and HashiCorp does it with dynamic just-in-time credentials. The agent starts a task holding nothing. It asks the vault for the specific credential the task needs and gets a short-lived one that expires on its own.
# Before: the key lives forever in the environment
sf = Salesforce(token=os.environ["SALESFORCE_TOKEN"])
# After: the agent asks the vault per task, scoped and short-lived
token = vault.get_token(
agent_id=AGENT_ID,
resource="salesforce",
scopes=["crm:read"], # just this task's needs
)
sf = Salesforce(token=token) # expires in minutesNothing to grep out of printenv. Nothing to find in a leaked config, nothing still valid three years later.
In Descope, the vault is called Connections. It stores OAuth tokens and API keys per user or per tenant and handles refresh, and their docs add a rule worth stealing whatever you use. Don't cache the token; fetch it whenever a tool runs.
Their recommended setup also puts your MCP server between the agent and the vault, so the server pulls the third-party token at call time and the long-lived secret never reaches the agent at all.
5. One token, one audience
The Drift breach hurt because the stolen tokens kept working: from anywhere, against a high-value target, for days. A token that works everywhere is a skeleton key, and the blast radius of the theft is the whole building.
The countermeasure is audience binding. When a client requests a token, it names the exact resource the token is for, and every other resource rejects it. It got codified in the MCP authorization spec, which is where many of us will meet it first. Since mid-2025, an MCP server is formally an OAuth 2.1 resource server. Clients must name the target server when requesting tokens (RFC 8707), servers must validate the audience, and forwarding a token upstream ("token passthrough") is explicitly forbidden.
Steal an audience-bound token and you've stolen a key to one door. Which, thanks to pattern 4, expires in minutes anyway.
This is the part of the spec nobody wants to hand-roll. Descope's MCP auth puts OAuth 2.1 on your server, with per-tool scopes and client registration through DCR or client ID metadata documents, and it works without replacing the login system you already have. On FastMCP, it's a provider you hand to the server:
import os
from fastmcp import FastMCP
from fastmcp.server.auth.providers.descope import DescopeProvider
auth_provider = DescopeProvider(
config_url=os.environ["DESCOPE_CONFIG_URL"],
base_url=os.environ.get("BASE_URL", "http://localhost:8000"),
)
mcp = FastMCP(name="crm-tools", auth=auth_provider)6. Async approval for the actions that scare you
A human can't approve every agent call; that deletes the point of having agents. Letting the agent wire money unsupervised is the other bad answer.
The middle path is asynchronous authorization, built on a flow called CIBA (client-initiated backchannel authentication). When the agent hits a sensitive action, it fires an approval request out of band. Your phone shows exactly what it wants to do, in structured form (RFC 9396, "rich authorization requests"):
{
"type": "payment_initiation",
"instructedAmount": { "currency": "EUR", "amount": "500.00" },
"creditorName": "AWS EMEA"
}Approve it and the agent continues. It kept working on everything else while it waited. Notice the shape of the approval, too. It's tied to a single action with real detail, never a blanket "your agent wants access, OK?". Identity vendors ship this as a product feature today.
7. Plan the retirement at the birth
OWASP keeps a Top 10 just for non-human identities, and the #1 risk isn't a clever attack. It's improper offboarding. Identities that outlive their purpose because deleting them is nobody's job. Agents multiply the problem, since teams now spin up agents the way they spin up feature branches.
Every agent has a named owner. Every action is traceable through the full chain (user → agent → tool → resource). Revocation is one move, not a scavenger hunt across env files and config repos.
Where to start
Nobody rolls out seven patterns in one sprint. The order I'd actually do them in:
Pattern 4 first. Killing static keys is the biggest risk cut for the least effort, and secrets managers are mature tools.
Pattern 1 next. Separate identities per agent, so your logs start meaning something.
Patterns 2 and 3 the moment an agent acts for other people, not just for you.
Pattern 6 the moment an agent touches money, prod data, or anything irreversible.
You'll mostly assemble rather than build. This space moved fast in 18 months. The big cloud platforms now ship agent identity for their own ecosystems, identity platforms package the protocol-heavy patterns for any stack, and the MCP spec bakes the token rules into a protocol you're probably already using.
Patterns 2, 4, 5, and 6 are the ones Descope's Agentic Identity Hub packages, for when you'd rather not assemble them yourself.
📌 TL;DR
Give every agent its own identity. Never a shared service account, never your session.
Delegate, don't impersonate. The token names who's acting (the agent) and who granted it (the user).
Intersect permissions. A delegated call may do only what both the agent and the user are allowed to do.
No static keys. The agent fetches short-lived credentials from a vault at task time; nothing sits in an env var waiting to leak.
One token, one audience. A stolen token should open exactly one door.
Async human approval. Sensitive actions ping your phone with structured detail; the agent keeps working until you answer.
Lifecycle from day one. Named owner, full-chain audit trail, and a kill switch you've actually tested.
Follow me on LinkedIn | Twitter(X) | Threads
Thank you for supporting this newsletter.
Consider sharing this post with your friends and get rewards.
You are the best! 🙏





