Cross-method Apex taint engine
These checks follow the workspace-wide Apex call graph, so a guard in a helper two calls away counts for the entry method that calls it.
vulkro-sf scan ./force-app
Checks
| Rule ID | Question asked of every method reachable from the entry | Markers that count |
|---|---|---|
| AP-018 | Invocable / AuraEnabled validation: does any reachable helper carry a guard? | if (...), String.isBlank, Pattern.matches, Schema.sObjectType.X.fields.Y.isAccessible() |
| AP-044 | REST endpoint auth: does any reachable helper check identity? | UserInfo, Auth., FeatureManagement, PermissionSetAssignment, getSessionId(, checkPermission( |
| AP-024 | Partial-success DML: does any reachable helper inspect the SaveResult? | .getErrors(), .isSuccess(), .getId(), helpers named processSaveResults / handleSaveResults / assertSavedOk / auditSaveResults |
| AP-042 | HTTP callout retry: does any reachable helper retry? | for / while / catch, helpers named withRetry / executeWithRetry / retryUntil / retryWrapper |
| AP-060 | Dynamic dispatch: Type.forName(<variable>).newInstance() with no access check anywhere | method body: isUpdateable(, isAccessible(, WITH SECURITY_ENFORCED, Security.stripInaccessible, AccessLevel.USER_MODE; class header: with sharing / inherited sharing |
Marker matching is case-insensitive, so a renamed helper counts when its body carries a marker.
Request sources
A value is untrusted when it comes from:
@AuraEnabledand@RestResource(@HttpGet,@HttpPost, ...) method parameters.@InvocableMethodparameters and@InvocableVariablewrapper fields.globalandwebservicemethod parameters.
Bind variables (:term) and WITH SECURITY_ENFORCED still clear a query from any of these sources.
AP-060 example
Type handlerType = Type.forName(handlerName);
IHandler h = (IHandler) handlerType.newInstance();
h.process(payload);
If handlerName comes from a request, the caller picks which class runs. AP-060 fires when the argument is a variable and no method or class in the workspace carries an FLS, CRUD or sharing marker. Type.forName('LiteralName') stays silent.
Notes
@isTestclasses are out of scope.- Calls into a managed package (
acme__SecurityHelper.assertCan(...)) cannot be followed and neither grant nor deny coverage. Document such a delegation with an inline suppression.
| Variable | Effect |
|---|---|
VULKRO_SF_AP018_LEGACY=1, VULKRO_SF_AP024_LEGACY=1, VULKRO_SF_AP042_LEGACY=1, VULKRO_SF_AP044_LEGACY=1 | Use the older single-method version of that check. |
VULKRO_SF_AP060_LEGACY=1 | Turn AP-060 off. |
See also: Apex, Agentforce