9 min readJohnny UnarJohnny Unar

The Vercel Breach Is a Template for How OAuth Sprawl Kills You

A hands-on audit guide for finding the silent OAuth grants, stale tokens, and unencrypted env vars that turn one phished laptop into a full breach.

what actually happened

The Vercel breach in April wasn't a clever zero-day in their edge network or some exotic supply chain attack on their build pipeline. It was an employee, one person, who at some point clicked through a Google OAuth consent screen for a third-party AI tool and granted it full Google Workspace read access. Read access to mail, to Drive, to the whole authenticated surface that token implies. That tool then got compromised by Lumma Stealer, which is a commodity infostealer you can rent for a few hundred dollars a month, and the attackers walked the OAuth token straight into an environment they were never supposed to touch. From there it was lateral movement, internal docs, and eventually customer secrets. The thing that should keep you up at night is how boring every step of this was. No genius required. Just a consent screen nobody reviewed, a token nobody revoked, and an internal environment that trusted Google identity a little too much. We've run security audits for a couple dozen SaaS teams over the past two years, and almost every single one of them has the exact same hole sitting open right now. They just haven't been on the receiving end yet. The reason the Vercel story matters is that it's a perfect template, a step-by-step walkthrough of how OAuth sprawl turns one phished laptop into a breach that ends up in a post-mortem with your customers' names in it.

you have no idea what your OAuth graph looks like

Ask yourself, right now, without checking, how many third-party applications have an active OAuth grant against your company's Google Workspace. You don't know. Nobody knows. That's the actual problem, not any individual app being malicious. Every time an engineer signs up for a new note-taking tool, a calendar scheduler, an AI meeting summarizer, a CRM enrichment widget, a Chrome extension that wants to read your tabs, they hit a consent screen, skim it for half a second, and click allow. Most of those screens ask for scopes that are wildly broader than the feature needs, because product teams default to requesting everything so they never have to ask twice. An AI scheduling assistant doesn't need https://www.googleapis.com/auth/gmail.readonly, but it'll ask for it, and your engineer will grant it because the alternative is reading the scope list and thinking about it, and nobody does that at 4pm on a Tuesday. The fastest way to see the damage is the Admin SDK. Pull the token list with the directory API, you want the tokens.list endpoint scoped per user, and dump it into a spreadsheet. If you've got the Google Workspace API enabled you can hit it programmatically and enumerate every (user, application, scopes) tuple across the whole org. The first time a team runs this they usually find somewhere between sixty and three hundred distinct third-party apps, most of which nobody recognizes, a third of which belong to former employees, and a handful of which have scopes that would make your security person physically wince. That spreadsheet is your attack surface. You cannot defend a graph you've never drawn.

enumerate it for real

Here's the concrete version, not the hand-wavy one. In the Google Admin console go to Security, then Access and data control, then API controls, then Manage Third-Party App Access. That UI gives you the human-readable list, but it's slow and it buries scope detail, so for an actual audit you want the API. Authenticate a service account with domain-wide delegation and the https://www.googleapis.com/auth/admin.directory.user.security scope, then loop over your users and call tokens.list for each one. The response gives you clientId, displayText, the scopes array, and nativeApp boolean per grant. Dump it all to CSV. Now sort by scope sensitivity. Anything with gmail, drive, admin.directory, or cloud-platform scopes goes to the top of the review pile. Cross-reference clientId against a known-good allowlist, and everything that doesn't match becomes a question you have to answer out loud: who authorized this, do we still use it, and what could it read if it got popped tomorrow. For the apps you can't immediately identify, the clientId is a Google project number you can sometimes trace, but honestly most of the time the right move is just to revoke first and let the one person who actually needed it come complain. Revocation is tokens.delete, it's instant, and a revoked grant that someone re-authorizes through a proper approval flow is strictly better than a stale grant rotting in your org for eighteen months. Run this audit on a schedule. Quarterly minimum. We wire it into a small Go script that posts the diff to a Slack channel, so new grants since last run show up automatically and somebody actually looks at them.

flip the default to admin approval

Enumeration tells you where you are. The fix that stops the bleeding is making sure no new uncontrolled grant can happen without a human in the loop. In Google Workspace this lives under API controls, and the setting you want is to switch your app access from Unrestricted to Restricted, then explicitly configure which OAuth scopes are allowed for unconfigured apps. Set the high-risk scope groups, anything touching Gmail, Drive, Calendar with write, the Admin SDK, to Restricted so that any app requesting them gets blocked at the consent screen and routed to an admin approval queue instead of just being granted by a sleepy engineer. The pushback you'll get is that this slows people down, and yeah, it does, by exactly the amount of friction that would have stopped the Vercel breach. An engineer who wants to connect a new AI tool now has to file a request, and an admin looks at the scopes and decides. That's the whole point. Most teams find that the approval queue is quiet after the first month because the genuinely useful tools get approved once and the impulse signups die at the gate. While you're in there, turn on the setting that blocks new sign-ins to apps that aren't on your trusted list, and set a policy that any app with Gmail or Drive read scope requires a security review regardless. If you're on Microsoft 365 instead, the equivalent is Entra admin consent workflow, you disable user consent for unverified publishers and route everything through admin consent requests. Same idea, different console. The principle is that consent is an admin decision, not an end-user reflex.

your env vars are not as safe as your platform tells you

The second half of the Vercel lesson is what the attacker found once they were inside. Customer secrets, sitting where customer secrets sit, in environment variables. Most teams have a two-tier mental model where production secrets get treated carefully and everything else, the analytics keys, the feature flag tokens, the so-called non-sensitive config, gets left in plaintext because it feels harmless. That model is wrong, because lateral movement doesn't respect your sensitivity labels. An attacker who reads your full env var set gets a map of your entire integration surface, and half the keys you labeled non-sensitive are actually write-scoped tokens to third-party services that pivot into more access. On Vercel specifically, mark secrets as Sensitive so they're write-only and can't be read back through the dashboard or the API after they're set, and stop dumping everything into a single shared environment that every preview deployment can read. Preview deployments are a notorious leak path, because a malicious dependency in a PR build can exfiltrate whatever env vars that build environment exposes, and most teams hand previews the same secrets as production without thinking. Scope your secrets per environment. Use a real secrets manager, Doppler or Infisical or AWS Secrets Manager, as the source of truth and sync into the platform, so rotation is one operation instead of a manual hunt. Rotate anything that's been sitting unrotated for more than a year, because you have no way of knowing whether it leaked into a log, a Sentry breadcrumb, or a third-party tool's storage two breaches ago. The env var model you set up on day one to move fast is the exact thing that becomes the loot pile on day one thousand.

the audit you should run this week

If you do nothing else after reading this, do these things, and do them before your next sprint planning swallows the intent. Pull the full tokens.list across your Google Workspace org and look at every grant with a Gmail or Drive scope. Revoke everything belonging to former employees, which you can find by cross-referencing against your offboarding list, and you will find some, everybody does. Switch your Workspace app access from Unrestricted to Restricted for high-risk scopes and stand up the admin approval queue. Walk through your Vercel projects and mark every genuinely secret env var as Sensitive, then split preview and production environments so PR builds can't read production keys. Pick one third-party AI tool your team uses and actually read the scopes it was granted, because that's the Context.ai-shaped hole waiting in your own stack. None of this is hard. It's a Saturday morning of unglamorous work that turns a breach-shaped future into a non-event. We do this kind of audit for clients at steezr, usually as part of a broader security review when a team is getting ready to sign an enterprise customer who's going to send a sixty-question security questionnaire, and the OAuth graph is almost always the part that's been quietly rotting while everyone focused on the things that feel more important. The Vercel post-mortem gave you the exact playbook the attackers used. The least you can do is run it on yourself first.

Johnny Unar

Written by

Johnny Unar

Want to work with us?

A hands-on audit guide for finding the silent OAuth grants, stale tokens, and unencrypted env vars that turn one phished laptop into a full breach.