UI Bundles run application logic in the user’s browser, introducing client-side security risks. To pass AgentExchange Security Review, follow the rules in this topic to avoid these risks and protect customers.
This guide covers supported frameworks, third-party code validation, and secure coding practices. Apply these rules to your client-side components and supporting Apex controllers.
Submission Gateways and Framework Requirements
Before your package enters Security Review, you must meet specific requirements. This section covers the required scans, reports, and framework rules.
Required Salesforce Code Analyzer (SFCA) scan
Salesforce Code Analyzer checks your code for known issues. Before you submit a managed package with UI bundles for AgentExchange Security Review, scan your code with Salesforce Code Analyzer version 5 or higher. Use the uibundle engine plugin. Add the report to your submission. For details, see Salesforce Code Analyzer.
Setup requirements:
SFCA version: Use Salesforce Code Analyzer version 5 or higher.
Workspace structure and bundle roots: The engine scans your workspace for a dist/ directory and analyzes the compiled output there. It finds a bundle root by looking for a ui-bundle.json file or a *.uibundle-meta.xml file. If it finds neither, it uses any parent directory of dist/. You also need a src/ directory next to dist/ so the engine can run rules that check your source code.
No configuration needed: The engine finds the targets on its own, so you don’t have to set anything up.
Framework Versions and Emergency Security Actions
Older framework versions no longer receive security fixes. Use a current version of React. If a security vulnerability occurs, Salesforce protects customers if you don’t take action.
React 18 or higher: Build your React UI bundles with React 18 or higher. Older versions are not actively maintained and lack security patches. Packages using unsupported React versions fail Security Review.
package.json file required: Include a valid package.json file that lists all project dependencies.
Response to critical framework vulnerabilities: If a critical vulnerability or zero-day CVE affects React or your dependencies, release a patched package within 7 days. If you don’t, Salesforce may disable compromised UI bundles in subscriber orgs to prevent platform-wide vulnerability exploitation.
Front-End Code and Dependency Security
Because your code runs in the user’s browser, malicious code or data can cause harm. Avoid pulling in external scripts at run time, and sanitize untrusted content.
Managing third-party scripts
Add all third-party JavaScript into the compiled package during the build process. Don’t load scripts from the internet while the app runs.
Don’t inject scripts dynamically. For example, don’t use document.createElement('script') to pull in code at run time. Loading unverified scripts from a content delivery network (CDN) creates a supply-chain risk. If an attacker changes that external file, they can execute malicious code inside your app.
DOM injection and sanitization (cross-site scripting)
UI bundles run in isolated subdomains (*.my.salesforce.app). A strict Content Security Policy (CSP) limits code execution and data access.
These boundaries protect the main host DOM (*.my.salesforce.com). However, your component code must still render the DOM safely.
Never pass unsanitized input to dynamic rendering targets like dangerouslySetInnerHTML or unchecked URI attributes. Clean dynamic HTML with a trusted library like DOMPurify before rendering it. Also, limit URI attributes to secure https:// endpoints.
1// Incorrect Implementation: Rendering un-sanitized user input directly into DOM targets2import React from "react";34const CommentView = ({userHtml, trackingUrl})=>{5 return(6<div>7{/* Unsafe DOM injection */}8<div dangerouslySetInnerHTML={{__html: userHtml}} />9{/* Unsafe URL protocol binding */}10<a href={trackingUrl}>View Details</a>11</div>12);13};
Ensure a direct user action triggers data changes. Don’t trigger changes from an automatic page load. This practice prevents unexpected updates.
State-changing operations and lifecycle hooks (CSRF protection)
Don’t run state-changing operations (POST, PUT, DELETE) inside setup hooks like useEffect(). Tie every state change to a user action, such as a button click.
If you put a change() call inside useEffect(), it runs automatically when the page loads or the user switches tabs. This rule prevents unexpected updates that the user didn’t initiate.
1// Incorrect Implementation: Automatic state mutation executed during component lifecycle initialization2import React, {useEffect}from "react";34const RecordDeleter = ({recordId})=>{5 useEffect(()=>{6 // Executes state modification automatically on load without user confirmation7 fetch("/services/data/v58.0/sobjects/Account/" + recordId, {8 method: "DELETE",9 headers:{Authorization: "Bearer ..."},10});11}, [recordId]);12 return<div>Processing record...</div>;13};
1// Correct Implementation: State modification explicitly bound to user interaction event2import React from "react";34const RecordDeleter = ({recordId, onDeleteConfirm})=>{5 const handleDelete = async()=>{6 // Requires explicit user interaction to trigger database mutation7 await onDeleteConfirm(recordId);8};9 return(10<button type="button" onClick={handleDelete} className="slds-button slds-button_destructive">11 Delete Record12</button>13);14};
Backend data access and API security
Your bundle’s data access methods determine user visibility permissions. This section explains how to access data, when the platform enforces security, and when you must enforce it yourself.
Data access patterns: Salesforce Data SDK vs. custom Apex
Standard data access (Salesforce Data SDK and UI API)
For standard data operations, use the Salesforce Data SDK to access the platform’s UI API. The platform automatically enforces record-level sharing, object-level security (CRUD), and field-level security (FLS) based on the user. For details, see User Interface API.
Custom Apex runs in system mode by default, meaning standard UI API security guarantees do not apply. As with Lightning Web Components (LWC) or Aura, you must explicitly enforce security controls inside your Apex controllers:
Enforce record-level security by declaring classes as with sharing.
Enforce FLS and CRUD directly, using WITH USER_MODE.
Limit who can run the class by assigning it through granular permission sets tied to the right user personas.
If you run custom Apex operations without sharing rules, the UI bundle can read or change restricted records.
1// Incorrect Implementation: Custom Apex bypassing sharing rules and field-level permissions (runs in system mode)2public without sharing class AccountBundleController {34 @AuraEnabled5 public static List<Account> getAccounts() {6 // Vulnerable: Executes in system mode, returning records/fields the user shouldn't see7 return [SELECT Id, Name, Revenue, SSN__c FROM Account];8 }9}
Declare custom Apex classes with sharing, and enforce user-mode security on every SOQL and DML operation.
1// Correct Implementation: Custom Apex explicitly enforcing sharing rules and Object/Field Level Security2public with sharing class AccountBundleController {34 @AuraEnabled(cacheable=true)5 public static List<Account> getAccounts() {6 // Enforces Object and Field Level Security at the database layer7 return [8 SELECT Id, Name, AnnualRevenue9 FROM Account10 WITH USER_MODE11 LIMIT 10012 ];13 }14}
External backend integrations and web vulnerability scanning
If your React UI bundle connects to a non-Salesforce endpoint, that application is in the scope of the AgentExchange Security Review process. Run a web vulnerability scan against the external service using OWASP ZAP or Burp Suite. Include the reports in your review package.