Someone asked me a question I could not answer, and I have thought about it most weeks since.

The question was: what can your agent see? I did what anyone would do — opened the permission set assigned to the user the agent runs as, and started scrolling. Object settings. Field permissions. Row after row of tick boxes, all accurate, all beautifully maintained.

A few minutes in, I realised I was reading the wrong document. That screen answers a question about a person: what this user has been granted. My question was about code: what the Apex behind my agent actions actually resolves to when it runs. No amount of scrolling turns one into the other.

The distance between them turned out to be measurable. This post is about measuring it.

Granted is not reached

Start with the two words, because everything else follows from keeping them apart.

Granted is what the permission model says the running user may touch. It lives in profiles and permission sets, and in most orgs it is carefully looked after.

Reached is what the code executing on that user’s behalf can actually pull back and carry onward. It lives in your Apex.

For years these two mostly agreed, because Apex ran behind screens, and a screen only shows the fields you put on it. What has changed is the destination. An Agentforce action’s outputs go to a model, which reads all of them — summarises, paraphrases, restates them when the next turn calls for it. There is no page layout at the end of the path where the extra fields quietly fall away.

The mechanism underneath is unglamorous. In Apex written below API version 67.0, a plain SOQL query runs in system mode: field-level security and object CRUD are not enforced by the Apex layer, so a field the running user cannot see is still readable by the code that user triggered. At 67.0 and above the default flipped to user mode — but that version is stamped on each file, so “which rules apply?” is a per-file question.

Granted is a fact about a person. Reached is a fact about a file. Only the second one decides what the model gets.

Naming the number

Once I had those two words, the useful move was to stop describing the difference and start counting it. That is what the analyser I built does: it reads Apex and agent metadata as data — statically, without running the agent — and works out, for each path an agent action can take, which fields the code resolves to. Then it subtracts what the running user was granted. The headline number that falls out is what I call the Escalation Gap: the reach that belongs to the code and not to the person.

I am deliberately not quoting a figure from any particular org: the number only means something relative to the org it was measured in. What makes it worth having is not its size — it is that it is a number rather than a feeling, so it can be watched over time, compared between two versions of the same action, and argued about with evidence.

The tool is open source under the MIT licence — github.com/aksumustafa1625/agent-blast-radius — and the metric ships with a written specification, so anyone can check whether the number means what I say it means. A measurement nobody can reproduce is not a measurement. It is a claim with a decimal point.

Why static analysis suits this question

There is an obvious alternative: talk to the agent and see what it says. I do that too, in a separate attack bench. But for this question it cannot give you the answer, for three reasons.

Conversation only proves the paths it walked. If the model never phrased a request the way that reaches the fourth branch of your action, you have learned nothing about the fourth branch — absence of evidence, dressed up as a green run.

Static analysis costs no credits. It never invokes the model, so it burns no Flex Credits — the usage credits Agentforce is billed in. A check that costs nothing can go in every build; a check that costs money gets run before demos.

Code is a better witness than behaviour. The reach is a property of the query, not of the phrasing that triggered it. Read the query and you have the answer for every phrasing at once.

What one finding actually looks like

The finding that convinced me the number was worth building sat in my own work.

On one agent path, the analyser followed a single field along its whole journey: out of a SOQL column, into an Apex @InvocableVariable output, and from there into the prompt template. The field carried a compliance label.

Two details make that report useful rather than merely alarming. First, it does not say “this field could reach the model” — it says the path exists, and gives the line number. Line 125. You can open the file and read it. Second, because the path is static, it existed whether or not any conversation had ever walked it; no sequence of polite test prompts would have shown its absence.

And I will be plain about the rest: this was my code. I write about permissions, and I still shipped it. That is not a confession for effect — it is why I stopped trusting careful reading as a method.

The labels your org already carries

That compliance label came from somewhere ordinary. Salesforce orgs already hold classification on fields — the ComplianceGroup metadata, with values like PII, GDPR and HIPAA — usually filled in during a data-governance exercise years ago.

In my experience almost nobody reads them back. That is a shame: reading them back turns a developer’s finding into a sentence a business colleague can act on. “This action returns a lot of fields” is a shrug. “The org’s own metadata calls this field GDPR, and it reaches the model at line 125” is a meeting. You did not have to invent the classification — your org made it already.

Doing a rough version by hand

You do not need my tool to get most of the value. For one agent action, on one afternoon:

  1. Open the Apex behind the action and write down every field in every SELECT — including the queries in helper methods it calls.
  2. Mark which of those fields leave the method as declared outputs.
  3. Log in as the user the agent runs as and check, field by field, which of the marked ones that user could actually see in a report.
  4. The leftovers are your gap — fields the code reaches, the model receives, and the person was never granted.

Then take the shortest next step: add WITH USER_MODE to those queries and run the tests. If they pass, you closed the gap for free. If one fails, the failure is the finding.

Your next step

Pick the agent action you would be least worried about explaining to an auditor, and run those four steps against it. Not the scary one — the boring one. Boring actions are where assumptions go unexamined longest, and a gap you find yourself on a quiet afternoon is the cheapest one you will ever find.

Mustafa Aksu

Salesforce developer & ISV builder focused on Revenue Cloud, Agentforce, and Data Cloud. I write from real, shipped work.