Skip to main content

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.