Least privilege and privilege escalation
These checks cover profiles, permission sets and session policy that grant more than needed, and Apex that lets a caller act above their own access.
vulkro-sf scan ./force-app
vulkro-sf org least-privilege -o <alias> # live org recommender
Profile and permission set checks
| Rule ID | Severity | What it finds |
|---|---|---|
sf-lp-fls-overgrant | Medium | Edit on a sensitive (PII-shaped) field granted as broadly as read, or edit without read. |
sf-lp-toxic-combo | Critical | One profile or permission set granting Author Apex, Modify All Data and API Enabled together. |
sf-lp-custom-perm-broad | Medium | A custom permission granted by a standard, admin-shaped or powerful profile or permission set. |
sf-lp-session-not-high-assurance | Medium | Sensitive permissions with no high-assurance session required. |
sf-lp-integration-user-no-ip | Medium | An integration-user profile with no login IP ranges. |
Per-user effective field access
These compose each user's profile and assigned permission sets (from the metadata plus a User and PermissionSetAssignment export) and flag a sensitive field reachable by too many users. Each set can look narrow while their union is wide.
| Rule ID | What it finds |
|---|---|
| SF-FLS-USER-001 | A sensitive field effectively readable by more users than the bar. |
| SF-FLS-USER-002 | A sensitive field effectively editable by more users than the bar. |
VULKRO_SF_FLS_USER_BROAD_BAR sets the bar (default 5 distinct users).
Apex and guest checks
| Rule ID | What it finds | Fix |
|---|---|---|
| SF-GUEST-AURA-001 | An @AuraEnabled method in a class granted to the site guest user that performs DML, starts an async job (System.schedule, enqueueJob, Database.executeBatch) or runs dynamic SOQL with no authorization check (CWE-862). | Add WITH USER_MODE, Security.stripInaccessible, a CRUD check or a custom-permission check, or remove the guest grant. |
| SF-PRIVESC-SYS-001 | A without sharing class doing DML with request data and no CRUD/FLS check. | Check isUpdateable() / isCreateable() and strip inaccessible fields before DML, or use with sharing. |
| SF-PRIVESC-SYS-002 | Apex inserting a PermissionSetAssignment or PermissionSetGroupAssignment from request-controlled input. | Remove runtime assignment of privileged sets; allow only a vetted list; never take the target user from the request. |
Example
Non-compliant (SF-PRIVESC-SYS-002):
public without sharing class AccessGrantController {
@AuraEnabled
public static void grant(Id userId, Id permSetId) {
insert new PermissionSetAssignment(AssigneeId = userId, PermissionSetId = permSetId);
}
}
Compliant: the caller edits only their own record, under sharing, with an FLS check.
public with sharing class SelfServiceController {
@AuraEnabled
public static void updateMyPhone(String phone) {
User u = [SELECT Id FROM User WHERE Id = :UserInfo.getUserId()];
if (Schema.sObjectType.User.fields.Phone.isUpdateable()) {
u.Phone = phone;
update u;
}
}
}
See also: Profiles and permission sets, Sharing leakage