What an agent can really do with your OAuth token

by Corneliu Dumitru6 min readSecurity

You click Allow on a Google consent screen and a token comes back. What did you just authorise?

Most people answer with the scope names they were shown, which is the right instinct and an incomplete answer. The gap between the two is where the interesting risk lives.

Scopes are broader than they read

A scope is a category, not a rule. gmail.modify sounds like editing a message. It also covers moving mail between labels, marking things read, and trashing threads. drive is not "the files you meant" — it is your Drive.

Vendors design scopes for the widest plausible use of an API, because a scope that is too narrow generates support tickets and a scope that is too broad generates nothing. The incentive runs one way.

So the honest reading of any consent screen is: this application can do anything in this category, to anything in this account, at any time until I revoke it.

Which is fine when the application is a mail client whose behaviour you can predict. An agent is not that.

Why the model is not the boundary

The instinct is to write the rule into the prompt. "You may read from the CRM but you must never delete a record."

That is a suggestion. A system prompt is one input among several, competing with the user's message, the tool descriptions and whatever the last tool call returned — and it does not automatically win.

You do not need prompt injection for this to fail. Ordinary ambiguity does it:

  • Someone asks the agent to "clean up the duplicate contacts". Deleting duplicates is the task. Which instruction should win?
  • A tool is called contacts.merge. It deletes one of the two records. Nothing in its name says so.
  • The conversation runs long, early turns fall out of the window, and the rule goes with them.

None of those are attacks. And once you add tool results from the outside world — a support email, a web page, an issue someone filed — you also have text arriving in the model's context that was written by someone who is not you. Any server handing back that kind of content should be labelling it as untrusted data rather than letting it read as instruction.

Where a real boundary can go

Between the agent and the API. Every call has to pass through somewhere, and that somewhere can decide.

The useful shape is to classify tools by what they do to the world, then set rules per application:

TierWhat it meansExamples
ReadFetch and search. Cannot change anything.GMAIL_LIST_MESSAGES, GITHUB_SEARCH_ISSUES
WriteCreate and update. Changes data but does not remove it.SLACK_SEND_MESSAGE, NOTION_CREATE_PAGE
DestructiveDelete, revoke, or overwrite. Hard or impossible to undo.GMAIL_TRASH_MESSAGE, GITHUB_DELETE_REPO

"This agent may read anything and delete nothing" is then one rule per app rather than one rule per tool, and it is checked when the call arrives rather than hoped for while it is being decided.

Two details make the difference between this working and merely existing. Blocked tools should not be offered — an agent that never sees a delete tool does not try to use one. And a refusal should name the rule, so the agent can report why it stopped instead of retrying blindly or quietly doing something else.

Why there is no "ask me first"

The obvious missing option is a middle setting: allow reads, block deletes, prompt me for writes.

Over a stateless HTTP protocol there is nowhere to put that question. There is no session holding a conversation open while a human considers a dialog, and no channel back to whoever should be asked. A call is allowed or refused when it arrives.

That constraint is worth accepting rather than engineering around. A rule decided calmly in advance is a better rule than one answered under time pressure by whoever happened to be watching — and an approval prompt that appears forty times an hour is a prompt everyone clicks through.

The questions actually worth asking

Before you connect anything to an agent:

Who is the agent acting as? A browser authorisation means it acts as the person who approved it, and every action carries their name. A shared key means everything is attributable to the key and nothing to a person. The first is better, and it is worth using the clients that support it.

What happens when someone leaves? If access is re-checked on every call, removing a person cuts their agent off immediately. If it is checked when a token is issued, their agent keeps working until that token expires.

Can you see what it did? Not "did it succeed" — which tool, which arguments, which connection, as whom, and what came back. If the answer is a log line saying tool_call ok, you cannot investigate anything.

How long is that kept, and who can read it? Arguments and responses are the most sensitive thing in any audit trail. They should have a shorter retention than the metadata, that retention should be enforced by something that runs rather than by a policy document, and reading them should be an administrator's privilege rather than a member's.

The short version

The token is broader than the consent screen implied. The prompt is not a boundary. The boundary goes between the agent and the API, it is enforced on every call, and it is decided in advance — because the moment you are deciding it in the moment, you have already lost the argument with a system that is faster than you.

This is what Spinrun is

We built the gateway described above, so read that as a pitch — but it is also just the shape of the answer.

Every tool is classified read, write or destructive from the verb in its name. You set a rule per app, two states, allow or block. The check runs before anything reaches the vendor, blocked tools are never offered to the agent, and a refusal names the rule. Clients that can open a browser authorise as a person rather than as a shared key, membership is re-checked on every call, and every call lands in an activity log with its arguments, its outcome and who it acted as — with a shorter retention on the payloads than on the metadata, enforced by a job that runs nightly rather than by a policy document.

None of that requires you to trust us about the model's behaviour, which is the point. The rules hold regardless of what the model decides.

Next

Before anything loads

We would like to switch on analytics, so we can see which pages are worth keeping. Nothing has loaded yet and nothing will until you choose. The cookies that keep you signed in and remember your language are not part of this.

Read the Cookie Policy