Three hops, no zero-day. The Vercel breach started in an AI tool, pivoted through OAuth, and ended in plaintext environment variables. Every failure mode in the chain has a direct architectural counter.
On April 19, 2026, Vercel disclosed that an attacker had read non-sensitive environment variables out of a subset of customer projects. The follow-up reporting kept widening. Hundreds of accounts. Many organizations. AWS keys, Stripe keys, database credentials, signing keys, whatever a developer hadn't bothered to mark as sensitive in the dashboard.
The attacker did not exploit a Vercel zero-day. They did not exploit an Ivanti or Citrix appliance, did not chain a CVE through a perimeter device, and did not crack any cryptography. They walked across three trust boundaries that an enterprise Google Workspace, an OAuth grant, and a SaaS platform's environment variable model had quietly stitched together.
Hop one: a Context.ai employee got hit with Lumma Stealer in February 2026, reportedly from downloading game-exploit scripts. The infostealer harvested Google Workspace credentials, OAuth tokens, and keys for Supabase, Datadog, and Authkit. Two months of dwell time, undetected.
Hop two: a Vercel employee had previously signed up for a Context.ai product with their enterprise Google Workspace account and granted “Allow All” OAuth permissions. The attacker replayed the stolen OAuth token from their own infrastructure. MFA did not fire because OAuth bearer-token use is not gated by re-authentication, and the consuming app had no continuous-evaluation signal to act on.
Hop three: from inside a Vercel employee's Workspace, the attacker pivoted into Vercel itself, called GET /v9/projects/{idOrName}/env, and got back a JSON array. Variables not explicitly flagged as “sensitive” came back as plaintext.
The shape of the breach
An AI productivity tool was the initial vector. An OAuth grant was the bridge. A platform secret-storage model was the payout. None of those layers individually did anything obviously wrong. The architecture, in aggregate, did.
If you only remember one thing
Move the runtime boundary from the platform to the token. A secret that doesn't exist until a valid token mints it cannot be stolen by hijacking the identity that could have read it.
If you have read the earlier posts in this series, you know I have been beating one drum for months: secrets do not belong sitting at rest. Not in .env files, not baked into container images, not in deployment pipelines as build-time constants, and not in a SaaS platform's environment variable store. The Vercel breach is that pattern moved up one layer of abstraction.
It is worth being precise about what Vercel built and where the gaps actually sit, because the answer is not “the platform was negligent.” The Vercel environment variable model has two tiers. Variables marked sensitive are stored encrypted and cannot be read back through the API or the dashboard once saved; that is a real security control, and the implementation works as advertised. Variables not marked sensitive decrypt to plaintext on read by anyone with project access.
The two-tier design is a defensible product decision, not a flaw. Defaulting every variable to sensitive eliminates the readback path entirely, but it also means a developer cannot view the value to debug it, cannot rotate it by editing in place, and cannot export it cleanly for a local environment. Plaintext-by-default optimizes for developer velocity. Sensitive-by-default optimizes for blast radius. Reasonable platforms split this differently, and Vercel's pre-incident split sat within the industry's normal range. Post-incident, Vercel changed the default to sensitive, a reasonable response to learned data, not an admission that the original choice was indefensible.
The user configuration gap is a separate problem. Marking a variable sensitive is a checkbox, and humans miss boring security checkboxes: predictably and consistently, especially in preview-branch deployment flows where the cost of speed is lower than the cost of careful configuration. The breach exposed exactly the variables you would expect from that failure mode: the ones developers thought were “just config” until they were not.
Platform Boundary fallacy
Developers assumed that because the platform was secure, anything inside the dashboard was safe. The dashboard is not the boundary. The session cookie in the developer's browser is the boundary. The OAuth token granted to a third-party AI tool is the boundary. Once those move, “sensitive” vs “not sensitive” becomes a distinction the attacker enforces, not the platform.
The architectural critique is not that Vercel chose the wrong default or that developers were careless on a specific Tuesday. It is that the entire framing (pick your tradeoff between developer convenience and credential exposure) only exists because the credentials are sitting at rest in the first place. Move the credential out of rest entirely, mint it per request, scope it by token, and the tradeoff disappears. There is nothing to read back, sensitive or otherwise, because there is nothing stored.
The architectural hinge of a working agentic runtime is this: the secret is scoped by the token, not by the user identity.
At Vercel, secrets were bound to the user or team identity. Hijack the user, get every secret bound to that user. Hijack the team, get every secret bound to that team. The blast radius of a compromised identity is everything that identity could ever read, because the credentials are sitting at rest waiting to be retrieved.
In a token-scoped runtime, the credential does not exist until it is asked for, and the request itself carries the intent. The agent presents an access token. The token carries authorization_details describing what is being attempted: the path of the secret being requested and the business operation being performed. The credential engine reads those routes from the token in a trusted way and uses them to determine the permissions on the dynamic secret it issues.
The architectural inversion
Secrets are not stored with permissions; they are minted with permissions, per request, derived from the token presented at the moment of issuance. Steal the user identity, get a session. Steal the session, get the ability to ask. The thing you want (the credential itself, scoped to the operation you intend) only comes into existence when the runtime mints it, and it is gone again when the lease expires.
What this would have meant for Vercel: the attacker would have hijacked the Workspace, pivoted into the Vercel account, and found nothing to read. The dashboard does not display the credentials. The API does not return them. There is no plaintext to enumerate.
Each downstream call would require minting a fresh credential bound to a specific intent, signed by a runtime the attacker does not control, and the runtime would not mint one because the attacker cannot present a valid intent.
The mechanism that makes token-scoped credentials work is “two routes, one token.” Every request the agent makes carries an authorization_details array (RFC 9396, Rich Authorization Requests) with two entries: a business route describing the operation, and a path route describing the secret being touched.
Concretely, when an agent asks the runtime to issue a credential for a banking transfer, the wrapper builds a fresh RAR array per request:
// Per-request RAR composed by the wrapper from the actual tool args
[
{
"type": "urn:smt:agent:banking",
"operationDetails": { "action": "transfer_funds" },
"instructedAmount": { "amount": 1000, "currency": "USD" }
},
{
"type": "vault:path_access",
"path_constraint": "verify-rar/creds/banking-transfers",
"action": "update"
}
]
RFC 9396 · authorization_details
The first entry is the business RAR: type urn:smt:agent:banking, action transfer_funds, the actual amount and currency the agent resolved from the user's prompt. The second entry is the path RAR: derived programmatically from the Vault path the wrapper is about to read. The wrapper knows it is about to hit verify-rar/creds/banking-transfers, so it appends a constraint for exactly that path.
Different tools build different shapes: a SCIM add_user_to_group call builds {type:"scim_group_update", groupId, userId}, an update_user builds something different again.
The wrapper posts the array to IBM Verify's Token Exchange endpoint with grant_type=urn:ietf:params:oauth:grant-type:token-exchange, the user's access token as subject_token, the workload's SPIFFE SVID as actor_token, and the JSON-encoded RAR array as authorization_details.
Verify mints an on-behalf-of token with a fresh jti and grant_id that ride with the RAR back to the runtime. Vault's plugin walks the array, pulls out the business RAR, matches type|action against a configured role mapping, and issues a credential scoped accordingly.
Why this matters here
Zero hardcoded RAR strings anywhere downstream. Every credential, every audit row, every Vault lease is a 1:1 fingerprint of this specific tool call by this specific user with these specific arguments. The attacker cannot replay because there is no static credential to replay. They cannot mint a new one because they cannot synthesize a valid business RAR from outside the agent runtime.
Map each failure mode in the Vercel attack chain against the corresponding control in a token-scoped agentic runtime, and the picture is uncomfortable for any platform that still stores secrets at rest:
GET /v9/projects/{idOrName}/env.authorization_details (type, action, parameters) bound to a single transaction.authorization_details, not the requester's identity profile.jti, transaction id, and the RAR that authorized it. Audit join across systems is a key lookup, not an investigation.Six failure modes in the breach. Six controls in the runtime. None of them are theoretical: every one is a working component of the agent security runtime IBM and HashiCorp shipped together. The architectural answer to a Vercel-class breach is not a better checkbox. It is a different secrets model entirely.
The OAuth grant that the Vercel employee approved was the moment the trust boundary moved silently from the enterprise to a third-party AI tool. The grant said, in effect, this app can do anything I can do, indefinitely, until I revoke it, which I will not, because I will forget I installed it.
OAuth scopes describe categories of access. https://www.googleapis.com/auth/drive. https://www.googleapis.com/auth/gmail.readonly. They do not describe this specific action with these specific parameters at this specific moment. Once granted, the scope is the permission, and the permission lasts until the refresh token cycles or the user revokes the app.
RAR is the standard answer to this gap. Every action carries structured authorization_details describing the intent of the action. The token is no longer a generic capability; it is a transaction-bound assertion that the bearer is authorized to do this thing, with these arguments, right now. The credential issuance system can refuse to mint a credential when the RAR does not match what is being attempted.
Why most IDPs in market today cannot do this
Most consumer-focused IDPs that focus on SSO and human MFA cannot generate OAuth tokens granular enough for transactional context. They lack RAR support, lack actor + subject token semantics for delegation, and cannot prove on-behalf-of cleanly enough to satisfy compliance for what an agent did on behalf of a user. The standards are public. Production-grade implementations of all of them, in a single platform, are not common.
This is why the “why do I need both Verify and Vault” question gets answered with attribution and correlation, not with a feature checklist. Verify mints the OBO token carrying RAR. Vault honors the RAR when minting the credential. Both sides agree on the transaction id. Neither side can be replayed without the other.
Token-scoped credentials are bounded by what the target system can enforce. This is worth saying out loud because the architectural ambition can outrun the downstream reality.
The honest framing for customers is that we shrink the blast radius everywhere, and we eliminate it on systems that support fine-grained authorization. The breach economics still change (a stolen Postgres credential with a 60-second TTL is a fundamentally different risk than one that lives in .env for 18 months), but the model does not pretend every target system is RACF.
Architecture solves the runtime side of the problem. Discovery solves the side everyone forgets: knowing the AI integration was even there in the first place.
The Vercel breach has at least four discovery surfaces that the IBM and HashiCorp partnership illuminates without deploying another agent on the endpoint or in the browser. IBM Verify Identity Protection (VIP) covers the identity fabric. HashiCorp Vault Radar covers code and configuration. Together they give a security team coverage on both sides of the secret lifecycle, identity exposure on one console and code/config exposure on the other:
The VIP differentiator is method. Most discovery tooling depends on browser agents, endpoint sensors, or vendor-supplied APIs that ask the AI vendor “tell me which agents the customer has.” VIP does not need any of that. It infers from the identity fabric (the OAuth grants, the directory traffic, the anomalies in user-to-app behavior) what is actually running and where it sits in the trust topology.
The before-during-after
Before: VIP would have flagged the Context.ai OAuth grant as a broad-scope consent on an enterprise account, the kind of finding that lands in the weekly identity risk review. During: VIP would have raised the OAuth replay anomaly into the CAEP stream the moment the stolen token was used from new infrastructure. After: Vault Radar's code and configuration scan would have surfaced the plaintext environment variables across the SaaS portfolio, giving the security team a defensible scope for the rotation work and a clear picture of which secrets actually needed to be replaced.
When Vercel engaged Mandiant, the hard part was not finding the attacker's footprints; that work happened. The hard part was scoping what was actually accessed. Which secrets? On behalf of which user? For which downstream system? The platform logged the reads. It did not log the intent behind them, because there was no intent to log; the secrets were just sitting there to be read.
In a token-scoped runtime, every credential issuance has a 1:1 fingerprint:
jti on the OBO token, generated by Verify per requestgrant_id bound to the RAR that authorized the issuanceAudit join across systems becomes a key lookup. Show me every credential issued in the last 24 hours that carried a banking-transfer RAR for amounts over $10,000, by which user, with which agent, against which Vault path. That query runs in seconds because the identifiers ride together through every layer.
Compliance reframed
Customers cannot prove compliance for what an agent did on behalf of a user without delegation semantics that survive audit. Most IDPs cannot generate that. The identity story for agentic AI is not “can you SSO me into the AI tool.” It is “can you produce a defensible audit trail for every action the AI tool took, with the user it acted for, the policy that allowed it, and the credential that was used.”
Vercel is not the first platform to lose customer credentials this way, and it will not be the last. The pattern repeats because the architecture repeats:
Each one of these is a platform that stored customer credentials at rest, was breached through identity rather than infrastructure, and had to ask its customers to rotate everything afterward. The defense pattern that actually works against this class of attack is architectural: stop storing the credentials at rest, mint them per request, scope them by token, and bind them to the workload that issued the request.
That is not a Vercel-specific solution. It is the industry-wide answer to an industry-wide failure mode.
The breach didn't need a zero-day.
It needed a checkbox and a stolen cookie.
IBM Verify and HashiCorp Vault ship this architecture today.
Architecture is the only durable answer.