fls-audit
Skill1 starsChanged 7 months agoMITView on GitHub
Static-analyze Apex source for DML and SOQL operations missing CRUD/FLS enforcement. Reports every `insert`/`update`/`delete`/`upsert`/`merge` and dynamic SOQL that doesn't use USER_MODE / SECURITY_ENFORCED / Schema.DescribeSObjectResult checks.
In this repository53 other files it gives its agents.
- templates/CLAUDE.mdCLAUDE.md
- adrSkill
- agent-deploySkill
- agent-discoverSkill
- agent-eval-trendSkill
- agent-exchange-listSkill
- agent-specSkill
- agent-testSkill
- agent-vs-flow-vs-apexSkill
- before-vs-after-triggerSkill
- code-reviewSkill
- complexitySkill
- coverage-trendSkill
- dead-codeSkill
- dependency-graphSkill
- deploySkill
- destructive-changesSkill
- diff-deploySkill
- erdSkill
- field-impactSkill
- flaky-test-finderSkill
- flow-auditSkill
- flow-vs-apexSkill
- gateway-configSkill
- generate-docsSkill
- limit-usageSkill
- lwc-vs-reactSkill
- mcp-tool-vs-restSkill
- notifySkill
- onboardSkill
- org-diffSkill
- org-exploreSkill
- package-versionSkill
- pattern-packSkill
- perf-reviewSkill
- permset-auditSkill
- pr-prepareSkill
- queueable-vs-batchSkill
- quick-deploySkill
- react-initSkill
- release-notesSkill
- scratch-orgSkill
- security-scanSkill
- sequence-diagramSkill
- sf-initSkill
- sharing-reviewSkill
- slack-agentSkill
- soql-analyzerSkill
- test-coverageSkill
- test-dataSkill
- test-planSkill
- trust-evalSkill
- trust-layer-auditSkill
The file
We split this skill into the rules it gives an agent
8 rules. About 0 are common, 4 are distinctive, and 4 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈4
Distinctive · estimate
This author's own choices. Worth a look.
4
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 32
Read the file content
- Very specific: line 43
Look for as user or as system qualifier
- Distinctive: line 44
If neither, this is a candidate finding
- Very specific: line 55
Search the same statement for WITH USER_MODE or WITH SECURITY_ENFORCED
- Distinctive: line 56
If neither, candidate finding
- Very specific: line 66
If the query string is built from variables and the call doesn't include with AccessLevel.USER_MODE, candidate finding
- Very specific: line 74
Look for , AccessLevel.USER_MODE or , true /*allOrNothing*/, AccessLevel.USER_MODE parameter.
- Distinctive: line 130
Static analysis only: No org queries; no PMD dependency
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
You are auditing CRUD and Field-Level Security (FLS) enforcement in Apex. Modern Salesforce orgs require WITH USER_MODE (or WITH SECURITY_ENFORCED) on SOQL and as user on DML, or explicit Schema.sObjectType.Foo.fields.bar.isAccessible() checks. This skill flags places where those guards are missing.
Read Project Config First
source "${CLAUDE_PLUGIN_ROOT}/hooks/lib/config.sh"
APEX_SRC="$(sf_config_get '.paths.apexSource' "$ENV")"
TEST_SUFFIX="$(sf_config_get '.naming.apex.testSuffix' "$ENV")"
SHARING_DEFAULT="$(sf_config_get '.platform.sharingDefault' "$ENV")"Input
$ARGUMENTS:
- (empty) — audit all production Apex (skip test classes)
<ClassName>— audit one class--include-tests— include test classes (rarely useful — test code intentionally bypasses FLS)--ci/--format json|sarif/--out <path>
Steps
1. Walk Apex files
For each .cls and .trigger in ${APEX_SRC}:
- Skip test classes unless
--include-tests - Read the file content
2. Identify DML statements
Pattern (Apex source is straightforward to scan with regex; full AST is overkill):
\b(insert|update|upsert|delete|merge)\s+(?!as\s+(user|system))(?!\bnew\b)\s*([A-Za-z_][\w.]*)For each match:
- Capture the verb (
insert,update, etc.) and the variable / record being touched - Look for
as useroras systemqualifier - If neither, this is a candidate finding
3. Identify SOQL statements
Pattern:
\[\s*SELECT\s+(.+?)\s+FROM\s+(\w+)For each match:
- Capture the SELECT clause and FROM target
- Search the same statement for
WITH USER_MODEorWITH SECURITY_ENFORCED - If neither, candidate finding
4. Identify dynamic SOQL
\bDatabase\.(query|getQueryLocator)\s*\(For each match:
- Inspect the query string composition (look for string concatenation up to N lines back)
- If the query string is built from variables and the call doesn't include
with AccessLevel.USER_MODE, candidate finding
5. Identify Database.* DML
\bDatabase\.(insert|update|upsert|delete|merge)\s*\(Look for , AccessLevel.USER_MODE or , true /*allOrNothing*/, AccessLevel.USER_MODE parameter.
6. Filter and classify
| Heuristic | Severity | Rule ID |
DML without as user/as system/AccessLevel.USER_MODE | error | FLS-DML-NO-CHECK |
Static SOQL without WITH USER_MODE and FROM is custom or sensitive standard object (Account/Contact/Case/User/Lead/Opportunity) | warning | FLS-SOQL-NO-CHECK |
Dynamic SOQL via Database.query without AccessLevel.USER_MODE | error | FLS-DYNAMIC-SOQL-NO-CHECK |
Class declared without sharing | warning | FLS-WITHOUT-SHARING (cross-link to /sharing-review) |
Skip if the line contains a leading comment with // fls-audit:disable (suppression).
7. Output
Default Markdown:
# FLS Audit: <project.name>
Apex classes scanned: 47 (production, excluding tests)
Sharing default: with sharing
Run at: 2026-04-28T13:00:00Z
## Findings
### Critical (DML without enforcement) — 3
- `force-app/.../OrderService.cls:84` — `insert order;` (use `insert as user order;`)
- `force-app/.../OrderApiClient.cls:121` — `update updates;` (use `update as user updates;`)
- ...
### Critical (Dynamic SOQL without enforcement) — 1
- `force-app/.../SearchController.cls:42` — `Database.query(soql)` — pass `AccessLevel.USER_MODE`
### High (Static SOQL on sensitive object without enforcement) — 4
- `force-app/.../AccountReader.cls:18` — `[SELECT Id, Name FROM Account WHERE ...]` — add `WITH USER_MODE`
- ...
## Suppressions in source
- `force-app/.../SystemAdminQueue.cls:55` — `// fls-audit:disable` (justified: scheduled job runs as system)
## How to fix
- DML: append `as user` for caller-context, `as system` for elevated context (justify in comment)
- Static SOQL: append `WITH USER_MODE` to the query
- Dynamic SOQL: pass `with AccessLevel.USER_MODE` as the second arg to `Database.query`
- Manual checks: `Schema.sObjectType.Foo.fields.Bar.isAccessible()` before reading custom-extracted fieldsCI mode: emit findings via sarif_emit "argo/fls-audit" "$VERSION".
8. Exit codes
- 0 — no error-level findings
- 1 — any error
- 2 — file-not-found / config error
Rules
- Static analysis only. No org queries; no PMD dependency
- Don't double-fire with /security-scan. PMD's
ApexCRUDViolationcovers some of this. The skills overlap intentionally — both will catch DML issues; this skill adds dynamic-SOQL specifics. CI pipelines should run both - Honor sharing context. A class that's
without sharingis reported as a finding here too (cross-linked to/sharing-review) - Suppressions need justification.
// fls-audit:disableis allowed but the line should also have a// reason: ...comment within 3 lines. If missing, downgrade to a warning telling the user to justify - Don't audit test classes unless
--include-testsis passed — they're allowed to bypass FLS
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

