AppExchange Security Review readiness checklist
Catch the CRUD/FLS gaps that fail about half of all AppExchange Security Reviews, offline, before you spend on a submission.
Roughly half of first-time AppExchange Security Review submissions fail. Each attempt carries a per-submission fee, and a failed review round-trips through two to three weeks of vendor feedback per revision. The single most common reason a package fails is object and field permission enforcement (CRUD/FLS).
The cheapest fix is to walk the checklist on your own laptop first.
vulkro-sf runs the same classes of check the reviewer applies,
entirely offline: nothing about your unreleased managed package
leaves your machine.
Why CRUD/FLS is the number-one failure cause
Apex runs in system context by default. Unless you explicitly enforce the running user's object and field permissions before a DML or SOQL operation, your package will happily read and write data the user should never be able to touch. Reviewers test for this aggressively because it is the most common real-world data exposure in managed packages.
"Enforce" means one of these, on every DML and SOQL path:
Schema.sObjectType.X.isAccessible() / isCreateable() / isUpdateable() / isDeletable()checks before the operationWITH SECURITY_ENFORCEDon the SOQL querySecurity.stripInaccessible(...)on the recordsas usermode on the operation (Database.queryWithBinds, user-mode DML)- A vetted helper such as
fflib_SecurityUtilsorCanTheUser
Partial enforcement (checking isAccessible but not
isUpdateable, or enforcing on one method but not the helper it
delegates to) is the trap that fails experienced teams. A
pattern-matching linter cannot see the enforcement that happens one
method call away. vulkro-sf builds the intra-class and cross-class
call graph and follows the delegation, so a real gap surfaces and a
false alarm does not.
The readiness checklist
Walk these before you submit. vulkro-sf appexchange-report
renders the same list as an HTML report grouped section by section,
pinned to the checklist version on the day you run it.
1. Object and field permissions (CRUD/FLS)
- Every SOQL query enforces FLS (
WITH SECURITY_ENFORCED,stripInaccessible,as user, or explicit checks). - Every DML operation enforces CRUD for the running user.
- Enforcement holds across method and class boundaries, not just in the entry method.
-
@AuraEnabledand@RestResourcemethods enforce before reaching any DML.
2. Sharing model
- No
without sharingon classes that handle record data without an explicit ownership check. -
inherited sharingused where the caller's context should decide.
3. Injection
- No dynamic SOQL built by string-concatenating request input (use bind variables).
- No SOQL injection across method boundaries (tainted input passed into a query-building helper).
- No Apex / JavaScript injection via dynamic
Type.forName,eval, or unescaped merge fields.
4. External integrations and secrets
- No hardcoded passwords or API tokens in Apex literals, named credentials, or custom settings.
- Named credentials have IP restrictions and use HTTPS endpoints.
- OAuth scopes on connected apps are no broader than the
integration needs (no
Fullwhere a narrower scope works).
5. Lightning (LWC + Aura)
- No
lwc:dom="manual"paired withinnerHTMLon untrusted data. - No secrets written to
localStorage/sessionStorage. -
@wirereturns are treated as untrusted in the DOM.
6. Visualforce
- No
escape="false"on a reflected merge field without aJSENCODE/$Resource/$Labelwrap. - No
<apex:includeScript>with a dynamic URL.
7. Flow
- No
runInMode = SystemModeWithoutSharingwhere user context is required. - No hardcoded org IDs in
<stringValue>elements. - No system-context DML that bypasses the running user's permissions.
8. Profiles and permission sets
- No
View All Data,Modify All Data,Customize Application, orAuthor Apexgranted to non-admin profiles the package ships.
9. Connected apps
- OAuth scope is the minimum the integration requires.
10. Insecure deserialization and mass assignment
- No
JSON.deserializeof caller-controlled input straight into an SObject that is later written via DML.
Run it before you submit
# Render the reviewer-grouped readiness report, offline:
vulkro-sf appexchange-report force-app -o readiness.html
- Open
readiness.htmland walk all 10 sections. - Fix what is flagged. CRUD/FLS first: it is the most common fail.
- Re-run until the report reads READY.
- Only then pay the submission fee and submit to Salesforce.
- If Salesforce flags something new, re-run with the reviewer's notes to find the same pattern elsewhere in the package.
The whole loop runs on your laptop. Air-gap it with
VULKRO_OFFLINE=1 to enforce zero network at the process boundary.
Ready to walk the list?
Run vulkro-sf locally before you spend on a
submission. The AppExchange Submission Ready Pack
bundles 90 days of Pro for one submission cycle, and the
methodology maps every detector to
its checklist section.
See Vulkro for Salesforce
See also: AppExchange submission prep for ISVs, Vulkro for Salesforce vs CodeScan, Vulkro for Salesforce vs DigitSec, AppExchange readiness docs.