ChatGPT's email plugin now lets you connect multiple accounts. A workspace address, a Gmail address, one after the other, each with an alias, and you pick which one sends by naming it in the tool call. It looks like nothing. That's the whole point of it.
It's a very good integration. I want to be clear about that up front, because everything we're about to say sounds like an accusation and it isn't.
Daniel's been building MCP integrations for two years now, so he knows exactly what's behind a flow like that, because he's built the ugly version himself. Wrong token type, wrong scope, reauthenticating every few days because the refresh token rotation didn't survive a weekend. He wants to know what's actually happening under the hood when we click accept and tick the scope boxes. Why can we never see the service account or the API key? Do apps have to show us that? How does revocation work when we never saw the token in the first place? And is there some scenario, however far-fetched, where a credential gets issued that you can't see and can't revoke. Then the practical one: how do you keep control of your auth surface.
The far-fetched scenario is the one I'd flag. It's not far-fetched.
Let's start with the click. What actually happens in that moment.
The click is the smallest part of it. What's underneath is OAuth 2.0, authorization code flow with PKCE, which is RFC 7636. The client, so ChatGPT in this case, redirects you to the identity provider, Google, with its client ID and the list of scopes it's asking for. You authenticate, you consent, that's your click. Google hands back a short-lived authorization code to the client's redirect URI, and PKCE is what stops that code being intercepted in transit, which matters enormously for public clients like desktop and mobile apps that can't keep a secret. Then the client exchanges the code for an access token, short-lived, and usually a refresh token, long-lived, which is what lets it silently mint new access tokens later without bothering you again. The access token then rides along as a bearer credential on every API call after that.
Bearer meaning whoever holds it is treated as you.
Whoever holds it is you, as far as the downstream service is concerned. There's no further proof required. That's why the whole rest of this conversation is about where that thing lives.
And where it lives is the interesting part.
In anything well built, it never touches the client's UI at all. The gateway doesn't hand the raw OAuth token back to the client. It stores the token internally and returns an opaque session handle. The client presents the session, the gateway looks up the token it has mapped to that session and refreshes it silently when it needs to. So when Daniel asks why he never sees a credential, the first answer is that the architecture is designed so there's nothing to see. It's not hidden behind a menu somewhere. It's server-side and it stays there.
Which is the right design. If ChatGPT handed you a raw bearer token in a settings panel, that would be a bug, not a feature.
Correct, and that's the thing to hold onto, because the next question is where people usually go wrong. They assume invisible means either sloppy or sinister. Sometimes it's neither. Sometimes the credential was never yours to begin with.
Explain that.
There's a two-axis model that makes this clean. One axis is persona: are you an interactive user, or are you an automated non-user, a service account. The other axis is credential type: no auth, static API key, dynamic API key, authorization code with PKCE, client credentials, platform app-context. And here's the line that matters. Client credentials and app-context credentials are available to non-users, and they never carry a user identity. Ever. They're not your credentials wearing a costume. They're the application's credentials, full stop.
So in the service account flow, where does the API key come from?
It's provisioned to the gateway ahead of time, out of band, during server onboarding. Somebody at the vendor sets it up before any user ever touches the product. And when you trigger a tool, the gateway attaches that key when it forwards the request downstream. The key never transits the client. It never passes through your browser or your app. No user identity is involved at any point in the chain.
So the answer to why can't I see it is: it isn't mine.
It isn't yours. You're not the principal of that credential. You're a person who pressed a button that caused a tool to run under the application's identity. There is nothing user-facing to display, because the user isn't a party to that credential at all. And I want to be honest about the shape of this, because it produces the same effect as a dark pattern. A credential you can't see, doing things on your behalf. The effect is identical. The cause is not.
That's the distinction Daniel's poking at, I think. He's not asking whether it's evil. He's asking whether the architecture actually accounts for him.
And in the service account case, it accounts for him as a trigger, not as a principal. Which is a real thing to be uncomfortable about, and I don't think the discomfort is wrong.
Does anything require apps to show you the token?
Nothing I've ever seen. There's no legal or regulatory mandate that says display the raw token to the user, and the standards argue the other way. A bearer token is a secret. Displaying it is a security anti-pattern. The transparency obligation that actually exists is a different one: show the connection, and let the user revoke it. That's what Google does with its security dashboard and third-party access, that's what ChatGPT does in the connector area. The token is deliberately opaque. The relationship is what's surfaced.
And the relationship is the part you can actually act on.
The relationship is the only part you can act on. Which is fine, as long as revocation actually works. And it does, but not the way most people assume.
So if the credential was never yours, what does revocation even mean?
It operates at the authorization grant level, not the token level. That's the key. When you revoke the app's access on Google's side, you're invalidating the grant. The refresh token stops working. The app can no longer mint new access tokens. Any access token already issued is still live until it expires, which is typically minutes to an hour. On the ChatGPT side, disconnecting the connector deletes the stored token or session mapping server-side. Both ends, both doing their own version of the same thing.
Eventually consistent, then.
Eventually consistent is exactly right, and that gap is real. A live access token can keep working until it expires even after you've revoked. It's a short window, but it's a window. And the bigger issue is that revocation only works if both ends honor it. There's a failure mode in enterprise settings where revoking someone's access meant contacting every server team individually. There was no single switch. That's the entire reason centralized gateways exist. Central revoke versus email twelve people and hope.
Twelve people and a spreadsheet.
Twelve people, a spreadsheet, and one guy on leave.
Let me push on that, because I want to make sure I've got the mechanics straight. If I revoke on Google's side, the refresh token dies immediately, but the access token I already handed out is still valid for its remaining lifetime. So there's a period where the app has a working key to a door I just told it to stop opening.
And the length of that period is whatever the access token's TTL is, which is a design decision somebody made, not a law of nature. Fifteen minutes, an hour, whatever. The app can't get new keys, but the old key still turns. That's why short TTLs matter, and it's why the industry keeps pushing them down. The shorter the access token lifetime, the shorter that dead zone.
And on the ChatGPT side, disconnecting the connector, that's a different operation entirely.
Different operation, same goal. It deletes the stored token or the session mapping server-side, so the gateway no longer has anything to present downstream. But notice what it doesn't do. It doesn't reach into Google and revoke the grant. It just stops using it. So if you disconnect on the ChatGPT side and never touch Google's dashboard, the grant is still sitting there, dormant. Which is fine until you reconnect, and then it can silently come back to life without a fresh consent screen, because the grant was never killed.
That's a useful thing to know. Disconnecting the app and revoking the grant are two different acts, and people treat them as the same act.
People treat them as the same act because the UI makes them feel like the same act. And they're not. If you actually want the thing dead, you revoke at the identity provider. That's the source of truth for the grant. Everything else is just the app forgetting.
So the practical advice is: when you're done with an integration for good, go to the identity provider's dashboard, not just the app's settings.
Go to the source. The app's settings are a convenience. The identity provider is the authority. If you only do one, do the one that actually kills the grant. And if you're the kind of person who reconnects things later, this matters more, because that dormant grant is exactly what makes the reconnect frictionless in a way you didn't ask for.
That brings us to the confused deputy, and it's not as far-fetched as it sounds.
It's not far-fetched at all. It's a documented attack class. Hardy described it in 1988, and the User to Service Account flow is the exact scenario. A human triggers a tool, the tool executes under a service account. If the gateway forwards that service account credential without checking anything, then any user who can reach the tool inherits the service account's full privileges. That's a privilege escalation. The paper on enterprise MCP gateways puts it bluntly: letting a user trigger a service-account-backed tool without an explicit authorization check is the confused-deputy escalation in disguise. The mitigation is an entitlement check before the credential gets attached. Does this user have service account access. Not does this user have a session. Does this user have access to the thing the service account can do.
And there's a real case of this.
The Supabase MCP leak in July 2025. The comment that stuck with everyone was that if it's running with credentials that aren't yours, credentials that provide full access, and your own credentials don't, then the bug becomes a privilege escalation vector. Classic confused deputy. That's not a theoretical paper. That's a shipped product with a real hole in it.
So Daniel's far-fetched scenario is just the default failure pattern when nobody puts the check in.
When nobody puts the check in, yes. And bearer tokens make it worse, because they're inherently relayable. There was a good discussion around Agent Passport earlier this year making the point that if Agent A presents its JWT to Skill B, Skill B can reuse that token to call Skill C while pretending to be Agent A. Nothing stops it. The token doesn't know who's holding it. That's what bearer means.
Unless you scope it.
Audience-scoped tokens, the aud claim, so a token minted for one service can't be replayed against another. Proof of possession, DPoP, where holding the token isn't enough, you have to prove you hold the private key too. Short TTLs. Per-request tokens instead of one long-lived sixty-minute thing. All of those shrink the window.
Walk me through DPoP for a second, because I think people hear proof of possession and glaze over. What's the actual mechanism.
The client generates a key pair and keeps the private key. When it makes a request, it signs a little proof with that private key and attaches it alongside the token. The server checks that the proof matches the key that's bound to the token. So even if somebody steals the token in transit, they can't use it, because they don't have the private key that the token is bound to. Holding the token isn't enough anymore. You have to be the thing the token was issued to.
So it converts a bearer token into something closer to a key you actually have to possess.
It converts a bearer token into a proof-of-possession token, yes. The token stops being enough on its own. And that's the whole point. Bearer tokens are convenient precisely because they're dumb. DPoP makes them slightly less dumb at the cost of a little more machinery on both ends.
And if you want to keep control of your actual auth surface, what do you do?
Prefer delegated OAuth over shared service accounts whenever a human is present. RFC 8693 token exchange preserves both identities in one signed token. The subject is the user, the acting subject is the agent. So the audit trail shows who acted and on whose behalf. That's the difference between a log that says the service account did it and a log that says Daniel did it through this tool. Demand audience-scoped tokens. Prefer proof of possession where it's offered. Insist on short TTLs. And verify that revocation actually propagates on both ends, because checking one end and assuming the other is how people get surprised. Then the one that requires no technical knowledge at all: watch for the User to Service Account pattern in anything you approve. If a tool runs under a service account, ask whether the platform checks entitlement before attaching that credential. If nobody can answer that question, that's your answer.
The uncomfortable version of all this is that the polished flow is doing exactly what it's designed to do. It's hiding plumbing that would terrify most users if they saw it.
And the plumbing is hard. The quote from a practitioner discussion that stayed with me was that authentication alone is brutal when you've got dozens of different OAuth implementations, each with their own issues around scopes and refresh logic, and that's where most integrations get bogged down. Daniel has lived that. Wrong token type, wrong scope, brittle rotation. That's not him being bad at it. That's the terrain.
The MCP spec says implementations should support OAuth 2.0 and leaves every detail to the implementer. That's the root of it. Everyone builds their own version of the same hard thing.
The gateway paper's own conclusion is that the hard part was organizational, not cryptographic. The crypto is solved. Getting twelve teams to agree on one revocation path is not.
Which is a weird thing to sit with, right? The math is done. The primitives exist. We know how to do this correctly. And the thing that actually blocks it is a meeting.
It's a meeting, or it's a team that owns one service and doesn't want to change its auth, or it's a vendor who shipped something in 2019 and nobody wants to touch it. The crypto is the easy part. It's been the easy part for a decade. The hard part is that security is a coordination problem wearing a technical costume.
Hilbert: The key works until it doesn't, and then you're rekeying everything. That's what the general manager used to say. I was night auditor at a hotel that ran the whole place on keycards. Front desk could issue master keys to housekeeping. Those cards opened every room in the building. Housekeeping never saw a key. They swiped a card, the door opened, they moved on. And when a master key went missing, you didn't revoke one key. You rekeyed every lock in the building. Took days. Cost a fortune. We had a laminated sheet in the back office listing which cards were master cards, because you had to know.
You had to know which ones were the dangerous ones.
Hilbert: You had to know. That's the part. This is exactly what you're describing. The user gets a card that opens the door, but they don't see the key. And when something goes wrong you're not revoking one key, you're rekeying the whole building. Difference is, at the hotel we at least knew which cards were master keys. Here, you don't even know which integrations are running with service accounts. Nobody's got the laminated sheet.
The laminated sheet is the entitlement check.
Hilbert: We kept ours behind the desk. I don't know where yours is.
Probably nobody's desk.
Hilbert: Anyway. I'm due at the pharmacy before six and I'd rather be early in that queue than late in it.
The thing I keep circling is that the invisible credential problem isn't a bug waiting to be fixed. It's the shape the whole ecosystem is settling into. Every platform that adopts MCP is going to make the same trade: hide the plumbing, surface the relationship, and hope the entitlement checks are where they're supposed to be. The confused deputy isn't a rare edge case. It's becoming the default failure pattern, because the default architecture is a human triggering a tool that runs under somebody else's identity.
The reason it becomes the default is that the alternative is more work for the platform, more friction for the user, and a worse demo. Nobody ships the version with the extra consent screen if the version without it tests better.
Nobody ships it. And to be fair to the platforms, most of them probably do have the check. The problem is you can't tell from the outside. The whole architecture is opaque by design, which is correct for the token and terrible for the trust. You're asked to take the entitlement check on faith because you can't see it, and you can't see it because seeing it would mean exposing the thing you're not supposed to see.
Which leaves the question Daniel actually ended on. If the credential was never yours, what does revocation mean? You're not revoking a key. You're severing a relationship. And how do you audit an integration you can't inspect?
You audit the relationship, not the token. That's the only surface you get.
One more thing from the research that didn't make the main run. The gateway paper's Flow 3, the one where naive designs fail, has a line that basically says the confused deputy escalation is what happens when you skip one check. One if statement. That's the whole distance between a sound architecture and a privilege escalation.
One check, and almost nobody writes it, because in testing everything works.
In testing everything works because the tester is the person who should have access.
The tester is always the authorized user. That's the trap. You test it as yourself, everything passes, you ship it. The check you never wrote is the one that only matters when somebody who shouldn't have access shows up. And by then it's in production.
It's the same reason security bugs cluster at the boundaries. The happy path is the path you walk every day. The unhappy path is the one you never visit until someone else does.
The unhappy path is where the credential you can't see is doing the thing you can't audit on behalf of a user who never knew they were a principal in the first place. Which is a lot of invisible machinery for one click.
Thanks as always to Hilbert Flumingtop, who produces this show and who is currently in a queue somewhere.
This has been My Weird Prompts. If you want to argue with us about bearer tokens, email us at show at my weird prompts dot com.
The next time you click accept on an integration, ask whose credential is actually being used. Then ask whether you could revoke it if you had to.
We'll be back soon.