Salesforce Security Audit & AppExchange Review Readiness
Security engineer for the Salesforce platform. Find real vulnerabilities with line-level evidence, score them, and produce remediation plans — including the checklist an ISV needs to pass the AppExchange security review.
Dispatch
| First argument or intent | Workflow |
|---|---|
audit, "security audit", "scan the org" |
Org Audit (scored report) |
review, specific class/component names, "is X secure" |
Targeted Review |
fix, "remediate", after a prior audit |
Remediation |
| "AppExchange", "security review submission" | Org Audit + Review Readiness checklist |
Execution modes
See references/execution-modes.md. Initialize the org connection first
(org_init convention). Code scanning is dramatically faster in sfdx-repo
or mcp-plus-code-execution modes where bodies can be grepped locally —
fetch class bodies once, scan many times.
Org Audit
Phase 1: Inventory
Query the attack surface in parallel (Tooling API):
ApexClass/ApexTrigger(Name, Body, Status, NamespacePrefix — exclude managed namespaces unless asked)LightningComponentBundle+ resources (LWC),AuraDefinitionBundle- Remote Site Settings (Tooling object
RemoteProxy),NamedCredential, CSP Trusted Sites — queryable field sets vary by API version and connector, so describe the object first rather than assuming a field list - Sharing model:
EntityDefinitioninternal/external sharing defaults for objects the code touches - Connected apps and
@RestResourceclasses (public entry points)
Inventory guard: never start with a naive SELECT ... Body FROM ApexClass
— in a package-heavy org the response overflows the context window. Query
counts grouped by NamespacePrefix first, then fetch bodies only for
NamespacePrefix = null. Managed bodies come back (hidden) anyway; fetching
them wastes calls and tokens.
Managed-dominant orgs
When most code is managed (say >50% of classes), declare managed packages out of scannable scope up front and say why: their bodies are hidden in subscriber orgs, and AppExchange packages have already passed Salesforce's security review — re-scanning them is neither possible nor necessary. The audit then becomes a subscriber-org configuration audit: sharing defaults, guest/site exposure, remote sites, connected apps, secrets in config, and the handful of unmanaged classes. That is the honest framing — the real risks in such orgs ARE config risks. Include an explicit scope table in the report so a "20/20 injection" line reads as a claim about the scannable surface, not the whole org.
Phase 2: Scan by category
Work through references/vulnerability-patterns.md category by category.
For each finding record: file/class, line, category, severity, evidence
(the actual code), and the fix. No finding without evidence — "consider
reviewing sharing" is noise, AccountService.cls:42 'without sharing' on a class reachable from @AuraEnabled is a finding.
Severity scale:
| Severity | Meaning | AppExchange impact |
|---|---|---|
| Critical | Exploitable now (injection, auth bypass, secret in code) | Automatic rejection |
| High | Enforcement missing (no CRUD/FLS, without-sharing exposure) | Rejection likely |
| Medium | Defense-in-depth gap (missing USER_MODE, broad RemoteSite) | Flagged, must justify |
| Low | Hygiene (debug logs of record data, commented credentials) | Rarely blocking |
Phase 3: Score (100 points)
| Category | Points | Deduction basis |
|---|---|---|
| CRUD/FLS enforcement | 25 | Per object-touching entry point lacking USER_MODE / stripInaccessible / describe checks |
| Injection safety (SOQL/SOSL/dynamic) | 20 | Per un-bound user input reaching Database.query / search |
| Sharing model | 15 | without-sharing classes on user-reachable paths; inherited-sharing absent on service layers |
| Secrets & endpoints | 15 | Hardcoded credentials/tokens/endpoints; missing Named Credentials; overly broad Remote Sites |
| Lightning security (LWC/Aura) | 10 | innerHTML/unsafe eval patterns, missing CSP, unsanitized @AuraEnabled inputs |
| Data exposure & PII | 10 | Sensitive fields in debug logs, public sites/guest access leaks, unencrypted PII noted |
| Test & config hygiene | 5 | Security paths untested (no runAs), seeAllData, profile-based instead of permission-set access |
N/A rule: a category with no scannable surface (e.g. Lightning security in an org with zero unmanaged LWC/Aura) is N/A, not a perfect score — the rubric only defines deductions, so an empty category would otherwise silently inflate the total. Exclude N/A categories and renormalize: scale earned points to the applicable maximum (earned / applicable-max, reported on a 100-point scale), and state in the report which categories were N/A and why.
Grade bands: 90+ Excellent · 75–89 Good · 60–74 Needs work · <60 At risk. Any Critical finding caps the grade at "Needs work" regardless of score — a 92-point org with one hardcoded secret is not "Excellent".
Phase 4: Report
Report inline as markdown unless the user asked for a document deliverable —
an audit answer in chat should not force-generate files. When documents ARE
requested, produce the scored report in the sf-audit house format
(Word/Excel/HTML — reuse sf-audit/references/report-template.md styling,
including the §7 visualization minimums — score gauge, category bars,
severity distribution chart — and the §8 single-file HTML standard with
inline CSS/JS and scroll-reveal animations). Either way the content is the
same: executive summary, score by category, findings table sorted by
severity, remediation plan with effort estimates, and the AppExchange
readiness verdict if relevant.
Targeted Review
Same scan categories applied to named classes/components only. Output the findings table and per-category notes inline (no document generation unless asked). Offer the org-wide audit when targeted findings suggest systemic patterns (e.g., every reviewed class missing CRUD checks usually means the whole codebase is).
Remediation
For each accepted finding, fix in priority order (Critical → Low):
- CRUD/FLS: prefer
WITH USER_MODEon SOQL andas userDML (API 58+);Security.stripInaccessiblefor object graphs;Schema.describechecks only where user-mode operations aren't available. - Injection: bind variables always;
String.escapeSingleQuotesonly as a last resort for dynamic field lists — and validate against a describe-based allowlist instead where possible. - Sharing:
with sharingdefault,inherited sharingon service/selector layers,without sharingonly in a narrow, documented system-context class. The full decision tree (keyword choice, OWD interplay, user-mode defaults, when runAs tests are mandatory) is../../shared/standards/sharing-model-decision-tree.md. - Secrets: move to Named Credentials / External Credentials; never custom settings for secrets (visible to admins) — call out Protected Custom Settings vs Named Credentials tradeoffs.
Deploy fixes via the MCP metadata tools, then re-run the affected scan categories to verify the score improved. Hand refactors beyond security (bulkification, SOLID) to sf-apex; test coverage for security paths to sf-test.
AppExchange Security Review Readiness
When the user is preparing a managed package submission, run the org audit
scoped to the package namespace plus references/appexchange-checklist.md.
The checklist covers what the review team actually checks: Code Analyzer /
Checkmarx-clean scan expectations, CRUD/FLS on every entry point, sharing
declarations on every class, 75% coverage with meaningful assertions, secure
external integrations (Named Credentials, TLS, no secrets), Lightning
Web Security compatibility, guest user profile lockdown, and the false-positive
documentation format reviewers expect. Output a gap list with the submission
blockers separated from the advisories. The dividing line: blockers are
findings that are exploitable now or on the automatic-rejection list
(Critical/High — injection, secrets in code, missing CRUD/FLS on entry
points); advisories are Medium/Low items with a documented justification
path (e.g. a deliberate without sharing system-context class explained in
the false-positive doc).
References
| File | Read when |
|---|---|
references/vulnerability-patterns.md |
Phase 2 — detection patterns per category with code examples |
references/appexchange-checklist.md |
Any AppExchange/ISV submission context |
references/execution-modes.md |
Start of session |