Was sich ändert
Apex lief bisher im Systemkontext: Objektrechte, Field-Level Security und Sharing-Regeln des ausführenden Benutzers waren beim Datenzugriff schlicht nicht wirksam. Wer sie berücksichtigen wollte, musste das ausdrücklich hinschreiben. Mit API-Version 67.0 dreht Salesforce zwei dieser Voreinstellungen um:
| Bereich | Bisher | Ab API 67.0 |
|---|---|---|
SOQL, SOSL, DML, Database-Methoden |
System Mode: FLS und Objektrechte werden ignoriert | User Mode: FLS, Objektrechte und Sharing greifen |
| Klassendeklaration ohne Schlüsselwort | verhält sich wie without sharing |
verhält sich wie with sharing |
WITH SECURITY_ENFORCED |
gebräuchlicher Zusatz in SOQL | zurückgezogen, WITH USER_MODE tritt an die Stelle |
Bestehender Code läuft weiter. Apex-Verhalten ist an die API-Version der Klasse gebunden. Eine Klasse, die auf Version 66.0 oder früher steht, verhält sich unverändert. Die neuen Standardwerte greifen erst, wenn du die Version anhebst, was in der Regel dann passiert, wenn jemand die Klasse ohnehin anfasst.
Warum der Schritt richtig ist
Der alte Standard war eine Fehlerquelle mit Ansage: Sicherheit war
Opt-in, und wer das Schlüsselwort vergaß, bekam stillschweigend die
unsichere Variante. Die Salesforce-Dokumentation benennt das seit Jahren
offen: ohne inherited sharing oder with sharing
werden auch Datensätze angezeigt, für die der Benutzer keine
Freigabe hat, und zwar wegen des unsicheren Standardverhaltens.
Ein Standardwert, dessen Vergessen ein Sicherheitsloch erzeugt, ist der
falsche Standardwert.
Dieselbe Logik gilt für FLS. Salesforce empfiehlt schon länger,
Feldberechtigungen über WITH USER_MODE statt über
WITH SECURITY_ENFORCED zu erzwingen, und die Gründe sind
handfest:
-
WITH USER_MODEberücksichtigt polymorphe Felder wieOwneroderTask.whatId. -
Es prüft alle Klauseln der Abfrage, auch
WHERE. Der ältere Zusatz prüfte nur, was inSELECTundFROMsteht. Ein Filter auf einem gesperrten Feld blieb damit unbemerkt. -
Es meldet alle Zugriffsfehler auf einmal. Über
QueryException.getInaccessibleFields()bekommst du die vollständige Liste, statt dich Exception für Exception vorzuarbeiten.
Was das praktisch bedeutet
Integrationsbenutzer brauchen echte Berechtigungen
Das ist der Punkt, der am häufigsten weh tut. Batch-Jobs, Queueables und Integrations-Endpunkte liefen bisher davon, dass der Systemkontext alles durchwinkt. Sobald der Code im User Mode läuft, braucht der ausführende Benutzer die Objekt- und Feldrechte tatsächlich; als Permission Set, nicht als Annahme. Für Automated-Process-Benutzer gilt das ausdrücklich ebenfalls: ohne explizit zugewiesene Permission Sets können sie die Prüfungen nicht bestehen.
Trigger bleiben im Systemkontext
Trigger sind von der Umstellung ausgenommen. Sie umgehen Sharing und FLS weiterhin einheitlich und können keinen Zugriffsmodus deklarieren. Das ist konsequent, denn ein Trigger ist Plattformlogik und keine Benutzeraktion. Es verschiebt die Verantwortung aber in die Service-Klassen dahinter.
Wo du bewusst im System Mode bleiben willst
Es gibt legitime Fälle: eine Genehmigungslogik, die Datensätze prüfen muss, die der Antragsteller nicht sehen darf; eine Aggregation über alle Regionen für ein Reporting. Dafür schreibst du den Modus künftig hin, statt ihn zu erben:
// Ausdrücklich im Systemkontext, mit Begründung im Code
List<Case> alle = [SELECT Id, OwnerId FROM Case WITH SYSTEM_MODE];
Database.insert(datensaetze, AccessLevel.SYSTEM_MODE);
Umgekehrt lässt sich User Mode auch punktuell erzwingen, solange die Klasse
noch auf einer älteren API-Version steht: über WITH USER_MODE
in der Abfrage oder insert as user beziehungsweise
AccessLevel.USER_MODE bei DML.
stripInaccessible bleibt nützlich
Wenn eine Exception die falsche Antwort ist, etwa bei einem Formular, das
auch mit eingeschränkten Feldrechten funktionieren soll, ist
Security.stripInaccessible() weiterhin das passende Werkzeug.
Es entfernt unzugängliche Felder aus Ergebnissen oder vor einem DML, statt
den Vorgang abzubrechen, und eignet sich außerdem zum Bereinigen von
Objekten, die aus einer nicht vertrauenswürdigen Quelle deserialisiert
wurden.
Ein Vorgehen für bestehende Orgs
- Nicht alles auf einmal hochziehen. Die API-Version pro Klasse anheben, nicht per Skript über das ganze Repo. Der Sprung ist ein Verhaltenswechsel, kein Formalakt.
- Mit den Integrationsbenutzern anfangen. Prüfen, welche Permission Sets sie tatsächlich haben. Fast immer ist das die Lücke.
-
Tests gegen echte Benutzerkontexte laufen lassen.
System.runAs()mit einem Benutzer, der dem Produktivprofil entspricht. Ein Test als Administrator beweist hier nichts. -
Verbliebene
WITH SECURITY_ENFORCEDersetzen. Und dabei nicht beides mischen: Salesforce rät ausdrücklich davon ab, Zugriffsmodi und den alten Zusatz in derselben Abfrage zu kombinieren.
Quellen
- Salesforce Summer ’26 Release Notes, Salesforce Help
- Apex Developer Guide, Abschnitte Enforce User Mode for Database Operations, Use the with sharing, without sharing, and inherited sharing Keywords und Enforce Security with the stripInaccessible Method