Specification
The Aksu Index
Version 1.0 · approved 31 July 2026 · this page published 20 August 2026
Definition
The Aksu Index of an (agent, running user) pair is the number of distinct fields the code behind the agent has been proven able to reach beyond what that user is allowed to see — always reported together with what could not be resolved.The Index is stated as four disjoint numbers. Section 04 of this document is the rule that forbids quoting fewer than four of them.
Canonical form
Aksu Index: P proven (C GDPR) · B unproven boundaries · U unresolved
The shape of the line, with placeholders. It is not a measurement; two measured lines appear in section 05.
Freeze notice
Version: 1.0 — approved by the maintainer on 31 July 2026 (the four load-bearing choices: pair-scoped, fields-only, four-bucket reporting, GDPR callout). Freezes permanently at first public reference, and is never silently redefined after that (§8).A gap without a number is an opinion.
- Not a certificate. The Index is evidence for a security review or a data protection impact assessment. It is produced statically, and it is worth exactly what the measurement behind it is worth — no more.
- Not an org score. One Index describes exactly one agent and one running user, at one moment, under one tool version. Anything wider is a derived rollup and has to be labelled as one.
- Not a product page. This document defines a term and the discipline for quoting it. There is nothing here to buy, sign up for, or request. A reference implementation exists and is named once, in section 10.
The gap this measures was not discovered here. Identity and SaaS-security vendors named it first and describe it precisely, and a standards body classifies it as a risk category. Their words follow, with dates and links, because a contribution is only legible once its antecedents are.
- Obsidian Security30 July 2026Securing Salesforce Agentforce
Obsidian Editorial Team (Security Research)
Quoted“Configuration is not reality, and that gap is the whole game. The Salesforce profile view shows theoretical configuration: object permissions, field-level security, sharing settings on paper. What it does not show is effective access, the actual rows and objects the agent can reach once every permission set, sharing rule, role hierarchy, group membership, and Apex context resolves at runtime.”
ReadingThe same page states: “The blast radius equals the effective access of the agent's running user.” It names Apex context as part of effective access. It does not produce a number.
- Nudge Security22 April 2026AI Agent Governance: Complete Guide For Security Teams
Rachel Ritchie
Quoted“The security implication: an agent's effective access can be significantly larger than its stated permissions.”
ReadingStated of MCP-server chaining, not of Salesforce execution semantics. The claim generalises; the mechanism behind it does not.
- Okta4 February 2026AI Agent Security: The Authorization Gap
Kundan Kolhe
Quoted“AI agents retrieve data using the permissions of whoever they authenticate as (checked), but output to shared workspaces where recipients have mixed permissions (not checked).”
ReadingOkta's “authorization gap” is the output-side audience problem: retrieval is checked, the destination is not. That is adjacent to, and not the same as, the difference between stated and effective permissions. Cited for what it says.
- OWASP Gen AI Security Project2026Top 10 for Agentic Applications 2026 — ASI03: Identity & Privilege Abuse
OWASP Gen AI Security Project
ReadingCited as a category name only. The risk class exists and is described as the agentic evolution of Excessive Agency (LLM06:2025). It is a taxonomy, not a metric. No sentence is quoted here because the primary document was not fetched.
Only sources verified against the live page are listed here. Pages were fetched and the quoted sentences matched byte for byte on 20 August 2026. Where a source was checked and did not say what it is often reported to say, that is recorded in the reading note rather than smoothed over.
One numbered over-privilege metric is already published, and it is not this one:
OPUR — Over-Privilege Use Rate — When Lower Privileges Suffice: Investigating Over-Privileged Tool Selection in LLM Agents, arXiv 2606.20023, 18 June 2026.
OPUR scores model behaviour — how often a language model selects a higher-privilege tool when a lower-privilege one would do — across a synthetic benchmark. It measures a different object: not a specific agent in a specific org against a specific running user's effective permissions. The two are not competing definitions.
What this document adds
The gap has a name. This gives it a number.
Stated narrowly, so it can be checked: the gap is named by identity and SaaS-security vendors and classified by OWASP. What has not existed is a published specification for a number that can be quoted, and then checked, by someone who did not produce it.
Specification §1, §2, §3
The Aksu Index of an (agent, running user) pair is the number of distinct fields the code behind the agent has been proven able to reach beyond what that user is allowed to see — always reported together with what could not be resolved.The three quantities
Let, for the (agent, running user) pair:
- outer
- the set of qualified fields (Object.Field; relationship paths kept verbatim) reachable by the agent's resolved code chain. “Resolved” means execution semantics are computed per operation under the precedence law: explicit mode clause ▸ apiVersion default (≥ v67 → user mode) ▸ sharing declaration (record axis, system mode only).
- inner
- the subset of outer the running user could read anyway under their effective permissions.
- gap
- outer \ inner, restricted to operations proven to execute not bounded by the running user (the FLS axis resolved False).
Invariant, enforced by the tool: outer = inner + gap (the concentric circles).
The four buckets
The Index then reports four disjoint quantities. They are disjoint by construction: the most severe verdict wins across an agent's actions, so a field cannot land in two buckets.
| bucket | meaning | evidence level |
|---|---|---|
| P — proven | fields in gap whose escalation is proven (ERROR findings, PS502/PS506) | proven |
| C — classified | subset of P carrying the org's own compliance labels (GDPR/PII — PS506) | proven |
| B — unproven boundaries | same rules at WARN: a real boundary the analysis could not prove crossed (e.g., a sanitizer present but the path unprovable) | bounded, unproven |
| U — unresolved | reach that could not be determined at all: dynamic SOQL, SOSL without RETURNING, undetermined sharing context (PS504) | honest unknown |
P is the headline number. C is why it matters. B and U are why the headline can be trusted — they are printed, never absorbed into P and never dropped.Scope
One Index describes exactly one agent × one running user, at one moment, under one tool version. It is not an org score.A hypothetical holder of a single permission set may be substituted, but the report must label it as a hypothesis, not a person.
Specification §1 · ADR-013
This is the discipline the specification exists to impose. It is the one clause most likely to be broken by someone quoting the number in a hurry.
Specification §1
Quoting the proven number alone while unresolved > 0 is a violation of this specification. An unknown never reads as clean.ADR-013, accepted 19 August 2026
The headline is P — the count of fields proven reachable beyond the running user — rendered large, first, and always with C, B and U beside it. No ratio is computed, stored or displayed.Two layout consequences
- 01
The band renders four numbers as one visual object. C is rendered inside P, never as a peer tile that could read as P + C.
- 02
When P = 0 and U > 0, the not-clean statement renders inside the band, adjacent to the zero, and may not be styled as a pass. This is the design's primary test case, not an edge case.
Why no ratio
A single-decimal ratio was proposed and rejected. It is worth recording why, because the ratio is the obvious thing to reach for and it fails in exactly the case that matters:
No ratio, percentage, grade, threshold or letter rating is computed, stored or displayed anywhere in this specification.
Measured 31 July 2026 · Salesforce Summer '26
Both agents below run in the author's own demonstration orgs. There are no customers, and no third-party org is described. Both runs were taken on 31 July 2026 against Salesforce Summer '26.
TechnoStore Revenue Assistant
Two actions, both at an API version below 67, so both default to system mode. Six fields are proven reachable beyond the running user, one of which carries the org's own compliance label. One operation could not be resolved at all, which is why the fourth number is not zero.
Aksu Index: 6 proven (1 carrying the org's own compliance labels); 0 unproven boundaries; 1 unresolved.
Aksu Index: 6 proven (1 GDPR) · 0 unproven boundaries · 1 unresolved
- actions
- 2
- legacy
- 2 / 2 pre-v67
- fields
- 8 reachable
- fingerprint
- 09789b487c84
HW Energy Agent
Nine actions, none below API version 67. No field is proven reachable beyond the running user. Two operations use dynamic SOQL, so their reach could not be determined at all.
Aksu Index: 0 proven (0 carrying the org's own compliance labels); 0 unproven boundaries; 2 unresolved.
Aksu Index: 0 proven (0 GDPR) · 0 unproven boundaries · 2 unresolved
- actions
- 9
- legacy
- 0 / 9 pre-v67
- fields
- 4 reachable
- fingerprint
- 464add656d38
ProvenanceAnalyzer fingerprint d3a0cb4d683c · parser 5.1.0. The per-report fingerprint identifies which tool produced each number; see section 07.
Specification §4, verbatim
Six statements the Index does not make. They are reproduced from §4 of the specification without alteration.
- 01
It counts fields, never records. Record-level visibility is sharing-dependent; the honest answer is “run as the user to measure”, so the record axis is disclosed in prose (PS501) and never as a number in the Index.
- 02
Org row counts are context, not the Index. A COUNT() is an upper bound — “could reach up to N rows”, never “reaches N”.
- 03
Index = 0 with U > 0 is not clean. It is “0 proven, U unresolved”, and must be said that way.
- 04
It measures authority, not behaviour. The Index says what the code chain can reach as built — not what the agent has done in any conversation.
- 05
It is not a certificate. It is evidence for a security review / DPIA, produced statically, with zero agent invocations and zero Flex Credits.
- 06
It is version-bound. A number is comparable only under the same tool version; the report's fingerprint (a sha256 over the rule/extractor source, parser version, analysis coverage, backend, and each class's own apiVersion) says exactly which tool produced it.
Specification §7
The measurement is published so that a number can be checked by people who did not produce it. The procedure is short.
Same org state + same running user + same tool version ⇒ byte-identical reports (proven live: two runs, equal sha256). To verify a Index someone quotes: run the tool twice on the same inputs, hash both outputs, compare fingerprints. The live COUNT() context lines are outside the deterministic fingerprint and say so.Procedure
- 01
Establish the three inputs the number depends on: the org state, the running user, and the tool version.
- 02
Run the analysis twice on those same inputs.
- 03
Hash both outputs. Byte-identical reports produce equal hashes; anything else means one of the three inputs moved.
- 04
Compare the fingerprint in the report against the fingerprint quoted with the number. A number is comparable only under the tool version its fingerprint names.
- 05
Read the live row-count context lines separately. They sit outside the deterministic fingerprint and say so, because a COUNT() is an upper bound and an upper bound is not a measurement.
Method
Specification §5
Condensed from §5 of the specification — enough that an implementer can follow it, and enough that a reader can tell what would have to be true for a number to be wrong.
- 01
Resolve execution semantics — per operation — the precedence law above, on both axes separately: enforces_fls (object CRUD + field security) and enforces_sharing (record visibility). Each is True / False / None = undetermined, and None is reported, never assumed safe.
- 02
Extract reach — SOQL (subqueries are reads of their own), SOSL, DML, async hand-offs (Queueable/Batch/@future, EventBus.publish as a write), trigger cascades, Flow runInMode, prompt-template merges — via two backends (AST and regex) feeding the same precedence core.
- 03
Resolve the user — effective permissions from a snapshot: profile + permission sets + PSG aggregates (muting measured to apply).
- 04
Intersect with the org's own compliance labels — (FieldDefinition.ComplianceGroup), including single-target relationship fields; polymorphic lookups are honestly skipped.
- 05
Subtract and classify — into P / C / B / U per §3.
Specification §9 · in-org measurements
The precedence law the method rests on is measured in a real org, not inferred from documentation. Where documentation and a live org disagreed, the org was believed.
From §9 of the specification
- The escalation is real: the same read, system mode vs user mode — 5 records vs 0, at both the CRUD and the sharing layer (E1).
- The version cliff is real: identical without sharing source — v58 reads 5, v67 reads 0, and at v67 the FLS read is BLOCKED, not silently stripped (E2, E2b).
- The headline case is real: a GDPR-labelled field, invisible to the running user, reaching the model (PS506) — found by intersection with the org's own compliance labels, not by our opinion of what is sensitive.
The in-org measurements
Sixteen experiments are recorded. Fifteen returned a result; one (E12, formula-field inputs) could not be deployed and is reported as open rather than as a finding.
| id | what it measured | result |
|---|---|---|
| E1 | System-mode read versus user-mode read, on both axes. | 5 records vs 0 |
| E2 | The same without sharing source at two API versions. Also: no declaration is not the same as without sharing. | v58 = 5, v67 = 0 |
| E2b | The two-axis proof at v67: the record axis enforced, and the FLS read blocked rather than silently stripped. | 0 rows; read BLOCKED |
| E3 | WITH USER_MODE on a without sharing class. The operation clause beats the declaration. | 0 |
| E4 | FieldDefinition.ComplianceGroup is readable without cost, but the query must be bounded per EntityDefinition, and FieldDefinition is itself FLS-gated. | — |
| E5 | Flow runInMode is declarative and static, so Flow actions are analysable without parsing Apex. | — |
| E6 | A trigger's DML runs in the mode of the trigger's own apiVersion. The v58 half of a matched pair with E13. | v58 child write lands |
| E7 | Agent Script apex:// syntax is vendor-validated. | sf agent validate → success |
| E8 | Permission Set Groups: the platform-computed aggregate's ObjectPermissions equalled the union of its components. | exact match |
| E9 | Muting is applied by the aggregate, and the runtime agrees: WITH USER_MODE is blocked while a pre-v67 class still reads the muted value. | aggregate has no row |
| E10 | Three pre-v67 invocables differing only in the sharing declaration, same caller, same admin-owned rows on a Private object. | without sharing = 5, no declaration = 0, with sharing = 0 |
| E11 | Platform-event publish by a user holding no ObjectPermissions row at all. | v58 publish lands, v67 publish blocked |
| E12 | Formula-field inputs. The fixture that would settle whether a formula carries a value past the running user's FLS could not be deployed. Reported as open, not as a result. | BLOCKED — not measured |
| E13 | A v67 trigger's own DML is bounded by the running user. Re-measured with controls on 2026-08-04; the org named the child entity and the trigger's own line in the exception. | v58 writes / v67 blocks |
| E14 | WITH SECURITY_ENFORCED does not compile at v67, with the v66 control. Same source, only the apiVersion differs. | v66 deploys / v67 fails |
| E15 | The record axis inside a trigger body, read two ways: a plain read (which reads the operation's mode) and WITH SYSTEM_MODE (which exposes the ambient context). | v58: PLAIN=5 SYS=5 · v67: PLAIN=0 SYS=5 |
| E16 | A trigger body does not inherit its caller's sharing declaration, in the form an Apex caller can express. Three call shapes at two versions. | all six readings identical |
A reference implementation exists — Agent Blast Radius — and it produced the two worked examples in section 05. It is scored against a separately published corpus, the Agent Authority Benchmark v1.1 of 19 August 2026: 28 Apex cases, of which 21 are adjudicated by a real org and 7 are labelled not adjudicable. The specification is published independently of it, so that a number it produces can be checked against the definition rather than against the tool.
Specification §8, verbatim
This document freezes at its first public reference. That reference is this page.
- This specification freezes at its first public reference. After that, any change to §3 (the formula) or §4 (the non-claims) is a new major version, published side by side — an existing number is never silently redefined.
- The tool fingerprint identifies which specification version produced a number.
- Extensions that only add reporting (new sub-buckets, new labels) are minor versions and must not change P/C/B/U for an unchanged input.
The v1.0 source document contains figures and one formulation that this page does not reproduce. They are listed rather than silently corrected: a specification that quietly edits itself is the same failure it exists to prevent, one level up. The clauses that define the measurement — §1, §3, §4, §7 and §8 — are unaffected.
- where · §9, third bullet
The source text describes one measured org as “all-v67 and reports clean”. Both halves are wrong and this page does not reproduce the sentence. The org in question measures 182 of 219 Apex files pre-v67 — only its agent's nine actions are v67 — and “clean” contradicts §4.3 of this same specification, which states that 0 proven with unresolved reach is not clean. The tool's own report prints that qualifier. Corrected in the source specification on 20 August 2026, before this page was published, so the two agree.
- where · §6
The source text says “a 23-case labelled benchmark” and “in-org experiments (E1–E13)”. Both counts have moved: the corpus is 28 cases, and the experiment series runs to E16 with E2b as a sub-identifier and E12 blocked and unmeasured. The precision, recall and mutation figures it quotes still hold.
- where · §9, third bullet (second figure)
The source text says “100 % pre-v67 across 106 classes”. The committed report says 113 of 113.
- where · §7
The source sentence reads “To verify a Index someone quotes”. Reproduced here unaltered, because a normative sentence is not silently edited. The intended article is “an”.
- where · closing line
The source file signs off as “Specification draft” while its own header states version 1.0, approved, and frozen at first public reference. The header governs.
Mustafa Aksu, Aksu Software.
The Aksu Index specification is published so that any number carrying its name can be checked against a definition its author does not control at the moment of quotation.
Licence
The reference implementation's repository carries an MIT licence (Copyright © 2026 Mustafa Aksu), whose grant covers the software and its associated documentation files. Whether this specification, as published here, is offered under that licence or under a separate documentation licence is not stated in the source material. The licence position is therefore to be confirmed, and is not asserted here.