Skip to main content

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 IDQuestion asked of every method reachable from the entryMarkers that count
AP-018Invocable / AuraEnabled validation: does any reachable helper carry a guard?if (...), String.isBlank, Pattern.matches, Schema.sObjectType.X.fields.Y.isAccessible()
AP-044REST endpoint auth: does any reachable helper check identity?UserInfo, Auth., FeatureManagement, PermissionSetAssignment, getSessionId(, checkPermission(
AP-024Partial-success DML: does any reachable helper inspect the SaveResult?.getErrors(), .isSuccess(), .getId(), helpers named processSaveResults / handleSaveResults / assertSavedOk / auditSaveResults
AP-042HTTP callout retry: does any reachable helper retry?for / while / catch, helpers named withRetry / executeWithRetry / retryUntil / retryWrapper
AP-060Dynamic dispatch: Type.forName(<variable>).newInstance() with no access check anywheremethod 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:

  • @AuraEnabled and @RestResource (@HttpGet, @HttpPost, ...) method parameters.
  • @InvocableMethod parameters and @InvocableVariable wrapper fields.
  • global and webservice method 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​

  • @isTest classes 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.
VariableEffect
VULKRO_SF_AP018_LEGACY=1, VULKRO_SF_AP024_LEGACY=1, VULKRO_SF_AP042_LEGACY=1, VULKRO_SF_AP044_LEGACY=1Use the older single-method version of that check.
VULKRO_SF_AP060_LEGACY=1Turn AP-060 off.

See also: Apex, Agentforce