Static analyzer · Salesforce Agentforce

The Aksu Index defines the measurement. Agent Blast Radius is the reference implementation.

Your agent runs as a user.
Its code does not.

An Agentforce agent is bounded by the permissions of the user it runs as — right up until the Apex behind it runs in system mode. Then it reads rows that user cannot see, and columns that user has no permission on at all. Nothing in the Agent Builder UI shows you this. It is decided by a version number in an XML file.

What it reports is the Aksu Index — four numbers for one agent and one running user, defined by a public specification so that a number carrying its name can be checked by someone who did not produce it.

Agent Blast Radius resolves that, mechanically, for one agent and one running user. It invokes no agent and spends no Flex Credits, because it never runs your agent — it reads the code behind it.

Aksu Index: 6 proven (1 regulated) · 0 unproven boundaries · 1 unresolved
AKSU:1.0/P:6/C:1/B:0/U:1/fp:da237145496c
https://aksuindex.com/spec/v1.0

One real report's result, whole: four numbers, the specification version, and the fingerprint that seals it. A number quoted without them is not this measurement — the way a temperature without its scale is not a temperature.

That fingerprint is this report’s, not yours. It seals what the analysis identity could see, and the published reports were measured against a permission set while measure.py models the agent’s own running user — so a different identity must produce a different seal. The four numbers are the measurement; the fingerprint says which run produced them.

Came here for the reports? Read one it produced

The part nobody measures

Below is the actual resolver, running in your browser. Change the apiVersion from 58 to 67 and watch both axes flip. That single number is the difference between an agent bounded by its user and one that is not — and most orgs have classes on both sides of it.

Set the three things that decide it

Nothing else in the class changes. This is the resolver the analyzer runs, and it is the part of Apex that decides what your agent can reach — before anyone looks at a permission set.

apiVersionin the -meta.xml
sharing declarationon the class
mode clauseon the query
Reader.cls
public with sharing class Reader {
  public List<Invoice__c> go() {
    return [SELECT Id, IBAN__c
            FROM Invoice__c];
  }
}
Reader.cls-meta.xml
<apiVersion>58.0</apiVersion>
Resolves to
record axisenforced

Which rows come back — row ownership and sharing rules.

field axisnot enforced

Which columns come back — object CRUD and field-level security.

becauseAPI v<=66 system-mode default

Sharing is honoured, but object and field security are not — this can read a column the user has no permission on at all.

Rules this puts in play
PS502field read past the user's FLS, for any field in the query this user has no field permission on
PS506regulated field reaches the model, if any such field carries the org's own compliance labels — this is the headline one

“Can fire”, not “fires”. Which of these actually fires needs your org's sharing model and this user's permissions — the two things this page does not have and will not guess.

This is a port of the analyzer's own _resolve(), and the site's build fails if the two ever disagree: all 24 combinations are generated from the Python and compared on every deploy. A widget that taught a rule the tool no longer follows would be worse than no widget.

Evidence

Two real orgs, measured the same day

The four are proven, regulated, unproven boundaries and unresolved. Here are two orgs, measured the same day. The most useful sentence this tool produces is not “your org is legacy” — it is that you cannot know which part matters until it is measured, and it is a far smaller part than you fear.

A retail org

All 113 Apex files pre-v67. Its agent's two actions sit on that legacy code.

6 proven (1 regulated)
0 unproven boundaries
1 unresolved

Read the report
A utility org

182 of 219 Apex files pre-v67 — 83% legacy. Yet all nine of its agent's actions are v67.

0 proven (0 regulated)
0 unproven boundaries
7 unresolved

Read the report

Both links open the actual file the analyzer wrote — not a sample, not a redrawn illustration. Each carries the date it was produced and the fingerprint that seals it, including the digest of the analyzer that made the verdict, so you can tell exactly which build you are reading.

Read the second one carefully, because the tool insists on it: that is not clean. Zero proven beside seven unresolved means zero proven and seven unresolved. The report says so unprompted, in the author's own demonstration org, because a tool that will not decline to pass its own agent has not earned the right to pass yours.

Your turn

Measure your own agent

Everything above needed nothing from you. This is the one step that needs an org — and it runs on your machine, against your own authenticated session. Here is what comes out, and then what produces it.

  Org: TechnoStore

  Agent:        TechnoStore_Revenue_Assistant
  Planner bundle: TechnoStore_Revenue_Assistant_v1   (the org's name for it)
  Agent type:   ExternalCopilot
  Running user: ...@example.ext   (the agent's own, from BotUserId)

  Reading metadata. No agent is invoked and no Flex Credits are spent.

retrieving agent metadata ...
retrieving action sources: ApexClass:GetRevenueSummaryAction, ApexClass:SendPaymentRemindersAction
objects reached: ['Invoice', 'Invoice_Payment_Requested__e']

AKSU INDEX: 6 PROVEN (1 REGULATED) / 0 UNPROVEN BOUNDARIES / 1 UNRESOLVED
ESCALATION GAP: 6 field(s), 1 regulated
  ERROR: 8
  WARN: 4
  INFO: 2
reports written: reports\TechnoStore_Revenue_Assistant_v1.md , reports\TechnoStore_Revenue_Assistant_v1.html
RESOLUTION COVERAGE: 1 OF 2 ACTION(S) FULLY RESOLVED (50%) - 1 CARRY REACH THIS ANALYSIS COULD NOT DETERMINE

The transcript of one run against the first of the two orgs above, cut at both ends and with the running user’s address replaced — it carries an org id, and this site does not publish those. The lines shown are as the tool printed them, including the coverage line saying half the actions could not be fully resolved.

No arguments. It asks the org for your agents and, for each one, the identity Salesforce actually runs it as — BotDefinition.BotUserId, which is a fact rather than a guess.

macOS / Linux
sf org login web --set-default
git clone --branch v1.0.0 --depth 1 https://github.com/aksumustafa1625/agent-blast-radius ~/agent-blast-radius
cd ~/agent-blast-radius
python3 measure.py
Windows
sf org login web --set-default
git clone --branch v1.0.0 --depth 1 https://github.com/aksumustafa1625/agent-blast-radius $HOME\agent-blast-radius
cd $HOME\agent-blast-radius
py measure.py

git will say "You are in 'detached HEAD' state" and name a commit. That is what pinning to a release tag looks like — it is the point of the tag, not a problem. You are not making commits here; you are reading one.

Python 3.12 or newer · git · Salesforce CLI · an authenticated org. Node is optional and nothing above installs it: these four lines always run the regex extractor, and the report says so in its own footer. For the parse-tree backend, run npm install inside blast_radius/ first — the two reports linked above were produced that way. No pip install. Python standard library only.

If your laptop blocks the browser login flow, skip the first line: run sf org list, then measure.py --org <alias> with an alias you already have.

Usually one and a half to three minutes, and it can be longer. Measured on a Windows machine: 97 and 118 seconds against a three-action org, 135 against a two-action one — and on 1 September 2026 the same nine-action org took 167, 185 and 260 seconds on three consecutive runs. Same command, same org, nearly double the spread. These are observations, not a promise. It takes longer the more actions the agent has, the more objects and fields those actions reach, and the more agents and topics your org holds — the first step retrieves your org's topic metadata, so a large org pays for that even when the agent is small. It got slower on purpose. The tool now always retrieves the Apex it analyses instead of reusing whatever sits in the folder, because a stale local copy meant a report about code your org is not running. Almost all the rest is your org answering: the compliance labels have to be asked for one object at a time — the platform refuses an unbounded query. The analysis itself takes under a second; everything else is waiting on Salesforce. The login step is not included; that one is between you and your IT department.

If the first run stops

  • 'py' is not recognized — the py launcher is not on PATH. It ships with the Python 3.12 installer from python.org and gets its own PATH entry; the Microsoft Store build does not provide it. Install Python 3.12 from python.org, or use python measure.py if you already have a real interpreter on PATH.
  • Python was not found — on Windows, python and python3 under WindowsApps are 0-byte Microsoft Store stubs that sit on PATH by default. Use py measure.py: the py launcher ships with the python.org installer and gets its own PATH entry, independent of the Add to PATH checkbox.
  • running scripts is disabled on this system — PowerShell prefers the .ps1 shim npm creates for sf, and the default execution policy refuses it. Run sf.cmd org login web --set-default, or install the Salesforce CLI from its Windows installer rather than from npm.
  • git is not recognized — install git, or skip it entirely: download the v1.0.0 source zip from the GitHub releases page, unzip it, and carry on from the cd line.
  • sf: command not found — the Salesforce CLI is not on PATH. On Windows its own installer avoids the execution-policy problem above; elsewhere, npm install --global @salesforce/cli
  • No default org is set — run sf org login web --set-default. A sandbox or a free Developer Edition is fine; nothing is written to it.
  • This org has no Agentforce agents — then there is nothing to measure here. Take the corpus route below instead.
  • This is an employee agent — an employee agent runs in each logged-in user's context, so there is no single running user. The tool says so rather than picking one, and asks you for a representative identity.

No Agentforce org?

You can still run the public corpus and check the execution-semantics model itself — 28 cases, no org required. It validates the method; it does not measure your organisation. The 28-case corpus

Keeping it, once you have it

Baseline:  AKSU:1.0/P:6/C:1/B:0/U:1/fp:da237145496c
Current:   AKSU:1.0/P:7/C:1/B:0/U:1/fp:9d3ba…
→ FAIL: proven 6 → 7

--baseline asks whether it got worse, not whether it is zero — a gate that asks for zero is one a legacy org deletes in a week. Every bucket ratchets, so making a query dynamic to move a finding from proven into unresolved breaks the gate too.

Method

What it actually does

It answers one question mechanically: given this agent and this running user, which objects and fields can the code behind the agent reach — and where does that exceed what the user is allowed to see?

  • Resolves execution semantics. apiVersion, sharing declaration, mode clauses — for Apex and for Flow, tracked as two separate axes because they fail separately.
  • Diffs against effective permissions. Profile, permission sets, permission set groups and their muting — resolved the way the platform resolves them, not approximated.
  • Intersects with your own labels. It reads the compliance classifications your administrators already applied. Which fields count as regulated is your org's decision, not ours — GDPR, CCPA, HIPAA or internal policy use the identical mechanism.
  • Traces the data → prompt chain. Hop by hop, from the field a query reads to the variable a prompt interpolates.

The output is a report and four numbers: the Aksu Index, defined by a specification published separately so that the number can be checked against a definition its author does not control at the moment you quote it.

Proof

Why you should not simply believe this

Everything above is a claim. These are the things that make it checkable, and every number here is re-run before it is quoted.

28 / 28
a published corpus

Hand-labelled Apex cases, 21 of them adjudicated by a live Salesforce org rather than by the author. Public, sha256-sealed, CC BY 4.0 — run your own analyzer against it.

8 / 8
mutation score

The analyzer is broken on purpose, eight ways. The corpus catches all eight — which is what stops a benchmark from being a mirror.

320
unit tests

Including a control that was verified to fail: drop a field from the fingerprint payload and the test claiming to catch it goes red.

16
in-org experiments

The precedence law is measured, not read. Three separate reviews cited real Salesforce documentation to correct it; all three were disproved in a live org, and all three would have broken correct code.

sha256
deterministic

Same org state, same running user, same tool version produces byte-identical reports. Proven live: two runs, same hash. The fingerprint seals the tool that made the verdict, not just the inputs.

Limits

What it refuses to say

A silent false clean is worse than an honest unknown, so the tool reports what it could not resolve rather than passing it. These limits are stated here for the same reason they are stated in every report.

  • It is not a certificate. It produces evidence for a security review or a data protection impact assessment. It is worth exactly what the measurement behind it is worth, and no more.
  • It counts fields, never records. Record visibility is sharing-dependent; the honest answer is to measure as that user, so row counts are reported as context and marked as an upper bound — “could reach up to N”, never “reaches N”.
  • It is not a runtime scanner. It does not test prompt injection, run adversarial payloads, or review your org's permissions as a whole. Those are different questions answered by different tools; this one resolves what the code behind one agent can reach as one user.
  • It measures reach, not behaviour. It says what the code can reach. It does not say what your agent did in any conversation.
  • The gap is a lower bound. Restriction and scoping rules reduce what a user sees and are not modelled, so the tool may think a user is more privileged than they are. That direction is stated plainly rather than described as conservative, because it is the opposite.
  • Unresolved reach names its owner. A query assembled at runtime is yours to close; a shape the analyzer does not model is ours, and the report says which, bound to the analyzer build that made the claim.

It is yours. MIT, on GitHub.

There is nothing to buy here and nothing to request. Clone it, read it, run it against your own org, and keep the report. If you fork it and do something better, that is a good outcome.

The measurements published on this page were all taken in orgs I control, and that is the honest limit of what any of them prove. The interesting numbers are the ones from orgs I have never seen — so if you run it, I would genuinely like to know what it got wrong, or which sentence in the report did not land. An issue on the repository is enough.

What reaches me: nothing

The analyzer runs on your machine, against your own authenticated session. It reads metadata — the agent’s configuration, only the Apex and Flows its actions actually invoke, and the permission structure. No record contents are retrieved — metadata, and only when you ask for it, aggregate COUNT results, processed locally on your machine. No agent is invoked and no Flex Credits are spent.

Nothing is uploaded and there is no account, because there is no server to upload to. This site is static and collects nothing. That is not a promise you have to take on trust — the source is published, so you can check it before you run it, which is the whole point of publishing it.