AppExchange Top-20 vulnerabilities (Vulkro coverage)
Salesforce Dev Relations publishes the empirical ranking of the Top 20 vulnerabilities found in the AppExchange Security Review, ordered by real-world submission failure rate. This page maps every entry to the Vulkro detector that catches it.
The order below is the canonical Salesforce ordering: failures #1 (most common) through #20 (least common, still material).
1. CRUD / FLS enforcement
Failure to check object and field-level accessibility before a query or DML. Catches the entire class of "the controller queried a sensitive object the user does not have read access to."
Detector: detectors/apex. The CRUD / FLS
enforcement rule (apex_crud_fls) flags missing WITH USER_MODE /
WITH SECURITY_ENFORCED, missing isAccessible() / isCreateable()
checks, and DML calls that omit AccessLevel.USER_MODE.
2. SOQL injection
Untrusted input concatenated into a dynamic SOQL string. Catches the
Database.query('... WHERE Name = \'' + name + '\'') shape.
Detector: detectors/apex. The SOQL injection
rule flags string concatenation into Database.query /
Database.queryWithBinds arguments without String.escapeSingleQuotes
and without a bind variable.
3. Sharing violations
Apex class with no sharing declaration (or without sharing) reachable
from user input. The class runs in system context and bypasses
record-level access.
Detector: detectors/apex. The sharing
violation rule flags without sharing on a user-reachable class, and
the absence of any sharing keyword on a class that performs DML or SOQL
against an sObject containing user-supplied input.
4. Mass assignment via JSON.deserialize
JSON.deserialize(payload, Account.class) lets a caller set fields they
should not be able to write (OwnerId, RecordTypeId). Equivalent of
Rails mass assignment.
Detector: detectors/apex. The mass-assignment
rule flags JSON.deserialize (and JSON.deserializeStrict) into an
sObject type without a subsequent Security.stripInaccessible filter.
5. IDOR via record-ID parameter
Controller accepts an Id parameter and queries the record without
re-verifying the user can see that ID. Classic insecure-direct-object
-reference shape.
Detector: detectors/apex. The IDOR rule
flags constructor / @AuraEnabled method arguments of type Id that
flow into a SOQL WHERE Id = :param without an accompanying access check.
6. Flow system-context DML
A Flow declared in System Context performs DML on a user-reachable trigger. Bypasses sharing rules and CRUD/FLS.
Detector: detectors/flow. The flow rule
parses the .flow-meta.xml file, identifies RunInMode = SystemModeWithSharing
/ SystemModeWithoutSharing declarations, and flags writes to
sensitive standard objects when reachable from a public guest entry point.
7. Visualforce controller CSRF (DML in constructor on GET)
A Visualforce controller performs DML in its constructor, which runs on HTTP GET. An attacker constructs a URL that triggers the DML; the victim's browser sends the request with the victim's session cookie.
Detector: visualforce. The CSRF rule flags
DML statements (insert, update, delete, upsert,
Database.insert / Database.update / etc.) inside a constructor
method of a class bound to a *.page file.
8. Crypto misuse
MD5 or SHA-1 for authentication; static IV; Math.random() for tokens;
hardcoded crypto keys.
Detector: detectors/apex. The crypto rule
flags Crypto.generateDigest('MD5', ...), 'SHA-1', AES with a static
IV (literal bytes in source), Math.random() used as a token source,
and hardcoded Blob.valueOf keys passed to Crypto.encrypt.
9. External script include over HTTP
<apex:includeScript value="http://cdn.example.com/foo.js" />. The
script bypasses CSP and runs in the page's origin.
Detector: visualforce. The external-include
rule flags <apex:includeScript> whose value is a literal URL (not
{!$Resource.Foo}) and whose protocol is http:// or https:// (the
Top-20 #9 wants StaticResource references, not external CDNs).
10. LWC DOM XSS
lwc:dom="manual" plus direct innerHTML assignment from a
@wire-bound value. Or template.innerHTML = userContent. Any path
from external data into the DOM without sanitisation.
Detector: detectors/lwc-aura. The LWC
DOM-XSS rule traces @wire / @api / @track fields into innerHTML,
outerHTML, and insertAdjacentHTML sinks without an intervening
DOMPurify call.
11. Aura DOM XSS
aura:unescapedHtml rendered with a user-controlled value, or
Component.set('v.html', userData) into an aura:unescapedHtml body.
The Aura equivalent of #10.
Detector: detectors/lwc-aura. The Aura
DOM-XSS rule flags aura:unescapedHtml attributes whose value
expression traces back to a controller call returning user input.
12. PostMessage handler missing origin check
window.addEventListener('message', e => { ... }) with no e.origin
check. Any cross-origin frame can post into the handler.
Detector: detectors/lwc-aura. The
postMessage rule flags addEventListener('message', ...) callbacks
that reference event.data without first comparing event.origin
against a literal or known-good list.
13. Stack trace disclosure
Exception.getStackTraceString() returned to a user-facing surface
(an @AuraEnabled method response, a Visualforce <apex:outputText>,
a REST resource response body).
Detector: visualforce and the Apex URL-safety
rule. Any call to Exception.getStackTraceString() is flagged: the
API itself is the smell, because stack-trace strings should never reach
a user-facing surface.
14. SecuritySettings hardening
The org's SecuritySettings.settings metadata: session timeout, HTTPS
required, clickjack protection, CSRF on GET / POST, password policy
length / complexity / expiration / lockout. Any of these set to a
weak value fails review.
Detector: detectors/security-settings.
Ten distinct checks on the SecuritySettings file: each weak setting
is its own finding with the exact pillar it violates.
15. LightningMessageChannel isExposed=true
A .messageChannel-meta.xml file with <isExposed>true</>. The
channel becomes reachable across packages, including from any LWC the
package consumer installs.
Detector: detectors/security-settings.
The LMC inventory rule scans every *.messageChannel-meta.xml in the
package and flags <isExposed>true</> at Medium severity (admin should
confirm the cross-package surface is intended).
16. PageReference / setEndpoint untrusted concatenation
new PageReference(userInput). HttpRequest.setEndpoint(baseUrl + userInput).
Any URL construction that concatenates untrusted input. Catches
open-redirect and the "credentials in URL query string" smell when the
untrusted input is itself a sensitive identifier.
Detector: visualforce. The URL-safety rule
flags new PageReference(...) constructors and HttpRequest.setEndpoint(...)
calls whose argument is a string-concatenation expression that includes
a variable not bound to a Named Credential.
17. Aura action CSRF
Aura @AuraEnabled action invoked from a custom DOM element (not the
Aura framework) without the framework-injected CSRF token. Rare but
material on custom-rendered components.
Detector: detectors/lwc-aura. The Aura
CSRF rule flags $A.enqueueAction patterns that bypass the standard
form-level CSRF token, and XmlHttpRequest-style direct posts to
Aura endpoints inside .cmp controllers.
18. Hardcoded secrets in Apex / LWC
API keys, OAuth client secrets, AWS access keys, Slack webhooks pasted
into Apex string literals, LWC .js, or .resource content. Catches
the entire AppExchange "rejected for hardcoded secret" bucket.
Detector: detectors/apex. The secrets rule
covers six credential patterns (AWS Access Key + Secret, Slack webhook,
GitHub token, generic API-key shape, Bearer token), plus the engine's
entropy-based string-literal scan. Visible across .cls, .trigger,
.js, and .resource content.
19. System.debug of sensitive variables
System.debug(password). System.debug('OAuth token: ' + accessToken).
The debug log is preserved across every transaction and is readable by
any user with View All Data; treating it as a public surface is the
right mental model.
Detector: Apex logging detector (sf_apex_logging).
The rule flags System.debug(...) calls whose argument expression
references a variable whose name matches a sensitive-identifier
heuristic (password, secret, token, api_key, session_id, etc.).
20. Agentforce class-bypass (ForcedLeak)
A GenAiFunction Apex action whose target class is declared
without sharing. The Apex action runs in the agent's runtime user
context, so a without sharing class leaks data across users via the
agent. Disclosed in 2025 as CVSS 9.4.
Detector: detectors/agentforce. The
ForcedLeak rule is a two-pass walk: pass one identifies every
GenAiFunction Apex action, pass two opens the referenced .cls file
and confirms the source-level sharing keyword. Fires High when the
target class is without sharing.
What about #X (the ones not in this list)?
Every Top-20 entry has an explicit Covered / Partial / Out of scope status on the methodology page (section 10.1: "AppExchange Top-20 vulnerability coverage"). The handful of Salesforce entries marked Out of scope (cosmetic Lightning CSS load, Classic-only JavaScript, password-echo runtime checks, username-enumeration DAST-probes) are documented there with the architectural reason.
The full coverage matrix on the methodology page also names the rule
ID for every Covered entry, so a finding emitted by vulkro-sf can be
traced back to the row that justifies it.