Agent Blast Radius — die Daten, die ein Agentforce-Agent wirklich erreichen kann
Ein statischer Zero-Credit-Analyzer, der die echte Datenzugriffsfläche eines Agenten auf der Ebene der Ausführungssemantik berechnet — apiVersion-Defaults, USER_MODE-Klauseln, Sharing-Deklarationen, Flow-Run-Modes —, sie gegen die eigenen Berechtigungen des laufenden Benutzers difft und die Lücke mit den GDPR-Labels der Org schneidet. Bei einem Agent-Script-Agenten geht er einen Schritt weiter und verfolgt ein sensibles Feld von der Datenbankspalte bis in den Prompt des Modells, mit Zeilennummer. Die Kopfzahl: der Escalation Gap.
- Jahr
- 2026
- Rolle
- Salesforce Developer — Agent Security Research
- Technologien
- Agentforce (authority analysis)Agent Script (.agent) — Salesforce's own parserReal Apex AST (ANTLR apex-parser)Source→sink taint (Authority Path)USER_MODE / apiVersion precedenceFlow runInMode analysisPermission & FLS resolutionGDPR / ComplianceGroup intersectionRuntime oracle — a live org is the judgeSFGE differential (org-refereed)CI gate (fail-on ERROR)307 unit tests
Eckdaten
- Die Frage, die kein Tooling beantwortet: nicht was ein Agent darf (Config-Ebene — AppOmni, Zenity, Security Center auditieren genau das), sondern was sein Code wirklich erreichen kann — denn auf Salesforce entscheidet der Ausführungsmodus, nicht der laufende Benutzer
- Die Kopfzahl ist der Escalation Gap: die Felder, die der Code des Agenten über seinen eigenen laufenden Benutzer hinaus erreicht — geschnitten mit den GDPR/PII-ComplianceGroup-Labels der Org
- Über die Erreichbarkeit hinaus beweist es den Pfad: bei einem Agent-Script-Agenten verfolgt PS522 ein DSGVO-Feld von der SOQL-Spalte über den Apex-@InvocableVariable-Output bis ins Prompt-Template — mit Zeilennummer gemeldet (nicht 'kann das Modell erreichen', sondern 'gelangt in den Prompt, in Zeile 125'). Diese Kette fehlt nachweislich in den kompilierten Metadaten, ein metadatenbasierter Scanner kann sie also gar nicht erzeugen
- Das Vorrang-Gesetz (explizite Klausel → apiVersion-Default → Sharing-Deklaration) wurde von Hand in einer Live-Org bewiesen, bevor es programmiert wurde: sechs Experimente, drei verbreitete Annahmen widerlegt — ein versionsblinder Scanner schlägt bei modernem Code falschen Alarm
- Die Apex-Reichweite wird auf einem echten Parse-Tree gelesen (ANTLR apex-parser), mit dem Regex-Extraktor als ehrlichem Fallback; der Report-Header nennt, welches Backend lief
- Läuft gegen einen Live-Agenten, der in Agent Script geschrieben wurde — vom Validator von Salesforce selbst kompiliert (sf agent validate) und in die Org veröffentlicht (sf agent publish) — und fängt PS501 (System-Mode-Read auf einem Private-Objekt) und PS506 (ein GDPR-gelabeltes Diagnose-Feld, das am FLS des Benutzers vorbei das Modell erreicht), für null Flex Credits
- Ehrliche Unbekannte sind Findings, kein Schweigen: dynamisches SOQL und opake Managed Actions werden markiert (PS504/PS507), nie als sauber geraten; Record-Zahlen sind 'n/a', nie geschätzt, wenn die Sichtbarkeit vom Sharing abhängt
- Beim Einbinden des offiziellen Agent-Script-SDK von Salesforce kam ein Packaging-Bug ans Licht, der drei veröffentlichte npm-Pakete unbrauchbar macht — mit Reproduktion und Ursachenanalyse gemeldet (Issue #71) und mit einem Post-Publish-Smoke-Test behoben (PR #72, CLA signiert, Checks grün, in Review)
- Die Plattform, nicht der Autor, ist die Grundwahrheit: ein Runtime-Oracle deployt jeden Benchmark-Fall als Wegwerf-Benutzer in eine echte Org und prüft die eigene Vorhersage des Analyzers — ein roter Test heißt, die Org sagt, der Analyzer irrt — und ein Differential gegen Salesforces eigene Graph Engine wird von derselben Org refereed. Eine bewusste Negativkontrolle (FLS auf ein Feld gewähren, es unter demselben USER_MODE lesen, der ein anderes blockiert) hält jedes Grün davon ab, leer zu sein
- 307 Unit-Tests, alle grün; ein CI-Gate lässt den Build bei ERROR fehlschlagen und kommentiert den Escalation Gap am Pull Request
Funktionen im Detail
Es läuft gegen eine echte Org — null Credits, echter Parse-Tree
Die Konsole, kein Mock-up: eine echte TechnoStore-Developer-Edition-Org über die sf-CLI abgefragt, dann blast_radius.cli gegen den Live-TechnoStore-Revenue-Assistant. Das Apex wird auf einem echten Parse-Tree gelesen (AST, mit Regex-Fallback bereit); nichts wird ausgeführt, keine Daten verlassen die Org. Escalation Gap: 6 Felder, 1 GDPR-gelabelt — ERROR 8 · WARN 4 · INFO 2 — deterministisches Markdown + HTML geschrieben. Null Flex Credits.

Die Kopfzahl — der Zwei-Kreise-Escalation-Gap
Das Bild, bei dem ein Architekt aufhört zu scrollen. Der Code des Agenten erreicht 8 Felder; sein laufender Benutzer sieht 2; der rote Ring ist die Lücke — 6 Felder, die der Code über den Benutzer hinaus erreicht, eines davon GDPR-gelabelt. Die Klassifikationsabdeckung ist 100 %, also ist dieses Personendaten-Ergebnis eine echte Messung, kein blinder Fleck. Beide Actions laufen im System-Mode.

Der Beweis — Findings mit bestätigtem Authority Path
Acht ERROR-Findings, jedes mit Regel, Grund und Fix. Das Kopf-Finding PS506: das GDPR/PII-Feld Stripe_Payment_Status__c wird im System-Mode gelesen, und der laufende Benutzer hat kein FLS darauf — und 'Authority Path CONFIRMED: der Wert des Feldes fließt in den @InvocableVariable-Output der Action, erreicht also das Modell.' Nicht 'kann erreichen' — bewiesen erreicht, auf dem Parse-Tree verfolgt. Der Fix ist konkret: WITH USER_MODE oder Security.stripInaccessible.

Ehrliche Unbekannte — eine Grenze, kein bewiesenes Leck
Der WARN-Abschnitt ist, wo das Tool sich weigert, falschen Alarm zu schlagen. Ein Pre-v67-Trigger feuert bei einem Write (PS509) — als zu prüfende Grenze markiert, weil kein eskalierender Write bewiesen wurde. Eine dynamische Reichweite lässt sich nicht vollständig bestimmen (PS504) — ein stilles False-Clean ist schlimmer als ein ehrliches Unbekannt. Ein Platform-Event-Publish (PS514) — der wahre Blast Radius kann größer sein als dieser Report, eine ehrliche Unbekannt-Kante, kein bewiesenes Leck. Ein Security-Tool, das seine blinden Flecken versteckt, ist schlimmer als keins.

Jenseits des Agenten — und ein Fingerprint, der das Tool selbst siegelt
Der Gesamt-Org-Kontext: 113 von 113 des org-eigenen Apex sind pre-v67 (standardmäßig System-Mode), 2 Permission Sets gewähren Modify All Data — eine Karte, wo der nächste Agent eskalieren wird, bevor er überhaupt gebaut ist. Und der Footer schließt den Kreis: kein Agent aufgerufen, 0 Flex Credits, gebunden an einen Fingerprint, der sowohl die Eingaben ALS AUCH das Tool siegelt, das das Urteil erzeugt hat — den Analyzer, die Parser-Version und die apiVersion jeder Klasse. Ein Urteil ist nur gegen genau das Tool reproduzierbar, das es erstellt hat.

Das Problem
Salesforce sagt: Gib Agenten Least Privilege — und eine ganze Produktkategorie (AISPM) auditiert Agenten-Berechtigungen, auf der Konfigurationsebene: was der Agent darf. Nichts davon liest, was der Code des Agenten wirklich tun kann. Auf Salesforce unterscheiden sich die beiden tatsächlich, denn der Ausführungsmodus — nicht der laufende Benutzer — entscheidet, ob eine Action diesen Benutzer respektiert: das Apex einer Action kann vor API v67 stammen und standardmäßig im System-Mode laufen; eine WITH-USER_MODE-Klausel überschreibt eine without-sharing-Deklaration; der runInMode eines Flows kann still System-Kontext gewähren; ein sauberes User-Mode-DML kann einen Legacy-Trigger feuern, der trotzdem eskaliert. Und ein naiver Scanner, der 'without sharing' markiert, ohne diesen Vorrang zu verstehen, produziert False Positives, die seine eigene Glaubwürdigkeit zerstören.
Der Ansatz
Erst die Physik beweisen, dann das Instrument bauen. Sechs von Hand ausgeführte Experimente in einer Live-Org (null Credits, in sich geschlossene Fixtures) etablierten das Vorrang-Gesetz — explizite Klausel schlägt apiVersion-Default schlägt Sharing-Deklaration — und widerlegten dabei drei verbreitete Annahmen: eine fehlende Sharing-Deklaration ist nicht 'without sharing'; der User-Mode-Default von v67 überschreibt für einfache Operationen selbst ein explizites without sharing; und der DML-Modus eines Triggers folgt der API-Version des Triggers, nicht der Access-Level der Action. Erst dann die Pipeline: ein Apex-Introspektor, der den Ausführungsmodus pro Operation auflöst, ein Flow-Introspektor, der runInMode deklarativ liest, ein Permission-Resolver für das effektive CRUD/FLS des laufenden Benutzers (inklusive der View-All-Records-aber-nicht-FLS-Nuance) und ein Authority-Analyzer, der alles zu PS5xx-Findings verbindet — mit den ComplianceGroup-Labels der Org, die markieren, welche entkommenen Felder GDPR/PII sind. Das Apex wird auf einem echten Parse-Tree gelesen (ANTLR apex-parser), und eine intra-methodische Source→Sink-Taint-Analyse beweist, ob ein abgefragtes Feld tatsächlich zum @InvocableVariable-Output der Action fließt, statt nur erreichbar zu sein; bei einem Agent-Script-Agenten, gelesen mit dem Parser von Salesforce selbst, reicht dieser Beweis eine Station weiter, bis ins Prompt-Template. Reports sind deterministisch und fingerprint-gebunden; unbestimmte Reichweite wird als ehrliches Unbekannt gemeldet, nie als stilles Bestanden, und das Tool degradiert ehrlich auf ein Regex-Backend, wenn Node fehlt. 307 Unit-Tests decken die Kette ab.
Das Ergebnis
Gegen einen Live-Agenten gelaufen, der in Salesforces Open-Source-Sprache Agent Script geschrieben, vom Validator von Salesforce selbst kompiliert und mit sf agent publish in die Org veröffentlicht wurde, rechtfertigt der Report das ganze Tool in einer Zeile: Escalation Gap — 1 Feld, 1 GDPR-gelabelt. Eine Pre-v67-Action-Klasse liest ein Private-Objekt im System-Mode (PS501); das Diagnose-Feld — ComplianceGroup PII;GDPR;HIPAA — erreicht das Modell, obwohl der laufende Benutzer keinen Feldzugriff darauf hat (PS506); und der Wert wird bis in den Prompt des Modells in Zeile 125 verfolgt (PS522) — ein Beweis, der in den kompilierten Metadaten nachweislich fehlt, ein metadatenbasierter Scanner kann ihn also gar nicht erzeugen. Standard-Managed-Actions werden ehrlich als nicht statisch analysierbar ausgewiesen statt als sicher angenommen. Alles statisch, bei jedem Commit, für null Flex Credits, mit einem CI-Gate, das den Build bei ERROR fehlschlagen lässt. Unterwegs brachte das Einbinden des Agent-Script-SDK von Salesforce einen Packaging-Bug ans Licht, der drei veröffentlichte npm-Pakete unbrauchbar macht — mit Reproduktion und Ursachenanalyse gemeldet (Issue #71) und mit einem Fix-PR, der upstream offen ist (PR #72 — CLA signiert, Checks grün, in Review). Ehrlich eingeordnet: Das ist Design-Time-Begrenzung des möglichen Blast Radius — sie ergänzt Runtime-Monitoring, statt es zu ersetzen; die Taint-Analyse ist intra-methodisch, das Permission-Modell ein Grant-Snapshot, und inter-prozeduraler Fluss und tieferes Call-Graph-Following sind als nächste Schritte dokumentiert, nicht behauptet.