Salesforce Hardening Guide
CRM platform security for MFA enforcement, Connected Apps, and Shield Event Monitoring
Salesforce Editions Covered: Enterprise, Unlimited, Performance (some controls require Shield add-on)
Overview
This guide provides comprehensive security hardening recommendations for Salesforce, organized by control category. Each recommendation includes both ClickOps (GUI-based) and Code (automation-based) implementation methods.
Intended Audience
- Security engineers configuring Salesforce security controls
- IT administrators managing Salesforce instances
- GRC professionals assessing Salesforce compliance
- Third-party risk managers evaluating integration security
How to Use This Guide
- L1 (Baseline): Essential controls for all organizations
- L2 (Hardened): Enhanced controls for security-sensitive environments
- L3 (Maximum Security): Strictest controls for regulated industries
Scope
This guide covers Salesforce-specific security configurations. For infrastructure hardening (AWS, Azure where Salesforce runs), refer to CIS Benchmarks.
Table of Contents
- Authentication & Access Controls
- Network Access Controls
- OAuth & Connected App Security
- Data Security
- Monitoring & Detection
- Third-Party Integration Security
1. Authentication & Access Controls
1.1 Enforce Multi-Factor Authentication (MFA) for All Users
Profile Level: L1 (Baseline) CIS Controls: 6.3, 6.5 NIST 800-53: IA-2(1), IA-2(2)
Description
Require all Salesforce users to use MFA for authentication, eliminating single-factor authentication vulnerabilities.
Rationale
- Attack Prevented: Credential stuffing, password spray, phished passwords
- Incident Example: Okta support breach (2023) - attackers used stolen credentials without MFA
ClickOps Implementation
- Navigate to: Setup → Identity → Multi-Factor Authentication
- Enable: “Require Multi-Factor Authentication (MFA) for all direct UI logins”
- Configure allowed authenticator types:
- ☑ Salesforce Authenticator (recommended)
- ☑ TOTP-based apps (Google Authenticator, Authy)
- ☐ SMS (NOT recommended - vulnerable to SIM swapping)
- Set enforcement date and communicate to users
- Verify in Login History: Setup → Security → Login History (check for MFA column)
Code Implementation
# Using Salesforce CLI
sf org login web --alias prod-org
# Query current MFA settings
sf data query --query "SELECT Id, UserName, MfaEnabled FROM User WHERE IsActive = true" \
--target-org prod-org
# Enable MFA requirement via Session Settings (requires API)
curl -X PATCH "${SF_INSTANCE_URL}/services/data/v59.0/sobjects/SessionSettings/SYSTEM" \
-H "Authorization: Bearer ${SF_ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"requireMfa": true,
"requireMfaForAllLogins": true
}'
Compliance Mappings
- SOC 2: CC6.1 (Logical Access)
- NIST 800-53: IA-2(1), IA-2(2)
- PCI DSS: 8.3
2. Network Access Controls
2.1 Restrict API Access via IP Allowlisting for Third-Party Integrations
Profile Level: L1 (Baseline) CIS Controls: 13.3, 13.6 NIST 800-53: AC-3, SC-7
Description
Configure Salesforce Network Access to restrict API calls from third-party integrations (like Gainsight, Drift, HubSpot) to their documented static egress IP addresses. This prevents compromised integrations from accessing your data from attacker-controlled infrastructure.
Rationale
Attack Prevented: Supply chain compromise via OAuth token theft
Real-World Incidents:
- Gainsight Breach (November 2025): Attackers exfiltrated data from 200+ Salesforce orgs using stolen OAuth tokens from compromised Gainsight infrastructure
- Salesloft/Drift Breach (August 2025): 700+ orgs compromised via stolen OAuth tokens
- Okta Survival: Okta was targeted but protected because they had IP allowlisting configured
Why This Works: Even if integration’s OAuth tokens are stolen, attackers cannot use them from infrastructure outside the integration’s documented IP ranges.
2.1.1 IP Allowlisting: Restricting Gainsight
Prerequisites
- Salesforce Enterprise Edition or higher
- Gainsight’s current static egress IP addresses
- System Administrator access
Gainsight IP Addresses
As of 2025-12-12, Gainsight uses these production egress IPs:
35.166.202.113/3252.35.87.209/3234.221.135.142/32
⚠️ Verify before implementing: Contact your Gainsight CSM or check Gainsight IP Documentation
ClickOps Implementation
Step 1: Navigate to Network Access
- Setup (gear icon) → Quick Find: “Network Access” → Network Access
Step 2: Add Gainsight IP Ranges For each IP address:
- Click “New” in Trusted IP Ranges section
- Enter:
- Start IP Address:
35.166.202.113 - End IP Address:
35.166.202.113 - Description:
Gainsight Production 1 - verified 2025-12-12
- Start IP Address:
- Click “Save”
- Repeat for remaining IPs (
52.35.87.209,34.221.135.142)
Step 3: Test Integration
- Trigger Gainsight manual sync
- Verify data flows correctly
- Check Login History for blocked attempts: Setup → Security → Login History
Time to Complete: ~10 minutes
Code Implementation
Option 1: Salesforce API (bash/curl)
ORG_URL="https://your-instance.salesforce.com"
ACCESS_TOKEN="your_oauth_token"
# Gainsight production IPs
GAINSIGHT_IPS=(
"35.166.202.113:35.166.202.113:Gainsight Production 1"
"52.35.87.209:52.35.87.209:Gainsight Production 2"
"34.221.135.142:34.221.135.142:Gainsight Production 3"
)
for ip_entry in "${GAINSIGHT_IPS[@]}"; do
IFS=':' read -r start end description <<< "$ip_entry"
curl -X POST "${ORG_URL}/services/data/v59.0/sobjects/LoginIpRange" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-d "{
\"StartAddress\": \"${start}\",
\"EndAddress\": \"${end}\",
\"Description\": \"${description} - verified $(date +%Y-%m-%d)\"
}"
done
# Verify
curl -X GET "${ORG_URL}/services/data/v59.0/query?q=SELECT+StartAddress,Description+FROM+LoginIpRange+WHERE+Description+LIKE+'%Gainsight%'" \
-H "Authorization: Bearer ${ACCESS_TOKEN}"
Option 2: Terraform
# terraform/salesforce/network-access.tf
locals {
gainsight_ips = {
"prod_1" = { start = "35.166.202.113", end = "35.166.202.113" }
"prod_2" = { start = "52.35.87.209", end = "52.35.87.209" }
"prod_3" = { start = "34.221.135.142", end = "34.221.135.142" }
}
}
resource "salesforce_login_ip_range" "gainsight" {
for_each = local.gainsight_ips
start_address = each.value.start
end_address = each.value.end
description = "Gainsight ${each.key} - verified 2025-12-12"
}
Option 3: Python Script
#!/usr/bin/env python3
# automation/scripts/salesforce/configure-gainsight-ips.py
from simple_salesforce import Salesforce
import os
from datetime import date
sf = Salesforce(
username=os.environ['SF_USERNAME'],
password=os.environ['SF_PASSWORD'],
security_token=os.environ['SF_SECURITY_TOKEN']
)
GAINSIGHT_IPS = [
{"start": "35.166.202.113", "end": "35.166.202.113", "name": "Production 1"},
{"start": "52.35.87.209", "end": "52.35.87.209", "name": "Production 2"},
{"start": "34.221.135.142", "end": "34.221.135.142", "name": "Production 3"},
]
today = date.today().isoformat()
for ip in GAINSIGHT_IPS:
try:
sf.LoginIpRange.create({
'StartAddress': ip['start'],
'EndAddress': ip['end'],
'Description': f"Gainsight {ip['name']} - verified {today}"
})
print(f"✓ Added: {ip['start']} (Gainsight {ip['name']})")
except Exception as e:
print(f"❌ Failed to add {ip['start']}: {e}")
Monitoring & Maintenance
Quarterly Review Checklist:
- Verify Gainsight IPs haven’t changed (contact CSM or check documentation)
- Update description fields with new verification date
- Review Event Monitoring logs for blocked attempts
- Test integration after any changes
Alert Configuration: If using Salesforce Shield Event Monitoring:
-- Query for blocked Gainsight login attempts
SELECT Id, LoginTime, SourceIp, Status, Application
FROM LoginHistory
WHERE Application = 'Gainsight'
AND Status = 'Failed'
AND LoginTime = LAST_N_DAYS:7
Operational Impact
- User Experience: None (users don’t interact with integration directly)
- Integration Functionality: Low risk if IPs verified with vendor
- Rollback: Easy - remove IP ranges from trusted list
Compliance Mappings
- SOC 2: CC6.6 (Boundary Protection)
- NIST 800-53: AC-3, SC-7, SC-7(5)
- ISO 27001: A.8.3 (Supplier relationships)
2.1.2 IP Allowlisting: Restricting Drift
Drift IP Addresses
As of 2025-12-12:
52.2.219.12/3254.196.47.40/3254.82.90.31/32
Source: Drift IP Allowlist Documentation
Implementation: Follow same process as Gainsight (Section 2.1.1), replacing IP addresses.
2.1.3 IP Allowlisting: Restricting HubSpot
HubSpot IP Addresses
HubSpot publishes their IP ranges at: https://knowledge.hubspot.com/integrations/what-are-hubspot-s-ip-addresses
Note: HubSpot has a larger IP range and may change more frequently. Consider:
- More frequent verification (monthly vs quarterly)
- Monitoring HubSpot status page for infrastructure changes
2.2 Restrict Login Hours by Profile
Profile Level: L2 (Hardened)
Description
Limit when users can log into Salesforce based on their role/profile, reducing attack surface during off-hours.
ClickOps Implementation
- Setup → Users → Profiles → [Select Profile]
- Click “Login Hours” button
- Configure allowed hours per day of week
- Save and test with affected users
Use Cases
- Restrict contractor access to business hours only
- Limit admin account login times to reduce exposure
- Geographic-based restrictions (e.g., US-only profiles during US business hours)
3. OAuth & Connected App Security
3.1 Audit and Reduce OAuth Scopes for Connected Apps
Profile Level: L1 (Baseline) CIS Controls: 6.2 (Least Privilege) NIST 800-53: AC-6
Description
Review all Connected Apps (third-party integrations) and ensure they only have minimum required OAuth scopes. Over-permissioned apps increase breach impact.
Rationale
Attack Impact: When Gainsight was breached, attackers had full OAuth scope, allowing complete data exfiltration. Scoped permissions would have limited damage.
ClickOps Implementation
Step 1: Audit Current Connected Apps
- Setup → Apps → Connected Apps → Manage Connected Apps
- Review each app’s “Selected OAuth Scopes”
- Document current scopes and business justification
Step 2: Identify Over-Permissioned Apps Look for apps with:
full- Complete access (almost never needed)api- Full API access (often too broad)refresh_token, offline_access- Persistent access (risk if breached)
Step 3: Reduce Scopes For each over-permissioned app:
- Click app name → Edit Policies
- Modify Selected OAuth Scopes to minimum required:
- Example: Change
fullto specific scopes likechatter_api,custom_permissions
- Example: Change
- Save
- Test integration to ensure functionality maintained
Step 4: Enable OAuth App Approval
- Setup → Security → Session Settings
- Enable: “Require user authorization for OAuth flows”
- This forces users to explicitly approve OAuth apps
Recommended Scope Restrictions by Integration Type
| Integration Type | Recommended Scopes | Avoid |
|---|---|---|
| Customer Success (Gainsight) | api, custom_permissions, specific objects |
full, refresh_token with long expiry |
| Marketing (HubSpot, Drift) | api, chatter_api, limited objects |
full, manage_users |
| Support (Zendesk, Intercom) | api, chatter_api, Case object only |
full, access to all objects |
| Analytics (Tableau) | api, read-only specific objects |
Write access, full |
Code Implementation
Audit Script:
# automation/scripts/salesforce/audit-connected-apps.py
from simple_salesforce import Salesforce
import os
sf = Salesforce(
username=os.environ['SF_USERNAME'],
password=os.environ['SF_PASSWORD'],
security_token=os.environ['SF_SECURITY_TOKEN']
)
# Query all Connected Apps
query = """
SELECT Id, Name, CreatedDate, LastModifiedDate
FROM ConnectedApplication
WHERE IsActive = true
"""
apps = sf.query(query)
print(f"Found {apps['totalSize']} active Connected Apps:\n")
for app in apps['records']:
print(f"- {app['Name']}")
print(f" Created: {app['CreatedDate']}")
print(f" Last Modified: {app['LastModifiedDate']}")
print()
# For detailed scope analysis, requires Tooling API
# (OAuth scopes stored in PermissionSetAssignment)
Compliance Mappings
- SOC 2: CC6.2 (Least Privilege)
- NIST 800-53: AC-6, AC-6(1)
- ISO 27001: A.9.2.3
3.2 Enable Connected App Session-Level Security
Profile Level: L2 (Hardened)
Description
Configure Connected Apps to inherit session security policies (IP restrictions, timeout) from user’s profile.
ClickOps Implementation
- Setup → Apps → Connected Apps → [App Name]
- Edit Policies
- Session Timeout: Set to “2 hours” or less (not “Never expires”)
- Refresh Token Policy: “Expire after 30 days” (not “Refresh token valid indefinitely”)
- Enable: “Enforce IP restrictions”
4. Data Security
4.1 Enable Field-Level Encryption for Sensitive Data
Profile Level: L2 (Hardened) Requires: Salesforce Shield
Description
Encrypt sensitive fields (SSN, credit card, health data) at rest using Salesforce Shield Platform Encryption.
ClickOps Implementation
- Setup → Security → Platform Encryption
- Generate tenant secret (store securely!)
- Select fields to encrypt:
- Custom fields marked as sensitive
- Standard fields: SSN, Credit Card, etc.
- Enable encryption per field
- Test: Encrypted fields show lock icon
Limitations
- Encrypted fields cannot be used in:
- WHERE clauses (except equality)
- ORDER BY
- Formula fields (in some cases)
- Requires Shield add-on (~$25/user/month)
5. Monitoring & Detection
5.1 Enable Event Monitoring for API Anomalies
Profile Level: L1 (Baseline) Requires: Salesforce Shield or Event Monitoring add-on
Description
Enable Salesforce Event Monitoring to detect anomalous API usage patterns that could indicate compromised integrations.
ClickOps Implementation
- Setup → Event Monitoring → Event Manager
- Enable these event types:
- API (all API calls)
- Login (authentication events)
- URI (page views)
- Report Export (data exfiltration indicator)
- Configure storage: EventLogFile (24hr) or Event Monitoring Analytics (30 days)
Detection Use Cases
Anomaly 1: Bulk Data Export from Integration
-- Query EventLogFile for large API responses
SELECT EventTime, Username, SourceIp, ClientId, RequestedEntities, CPU_TIME
FROM EventLogFile
WHERE EventType = 'API'
AND CPU_TIME > 10000 -- High CPU indicates large query
AND EventTime = LAST_N_HOURS:24
ORDER BY CPU_TIME DESC
Anomaly 2: API Access from Unexpected IPs
-- Detect API calls from IPs NOT in allowlist
SELECT EventTime, SourceIp, ClientId, Username, Status
FROM LoginHistory
WHERE LoginType = 'Application'
AND SourceIp NOT IN ('35.166.202.113', '52.35.87.209', '34.221.135.142') -- Gainsight IPs
AND EventTime = LAST_N_DAYS:7
Anomaly 3: Unusual Time-of-Day Access
-- Detect API activity during off-hours
SELECT EventTime, ClientId, Username, SourceIp
FROM EventLogFile
WHERE EventType = 'API'
AND HOUR(EventTime) NOT IN (9,10,11,12,13,14,15,16,17) -- Outside 9am-5pm
Alert Configuration
Using Salesforce Shield:
- Create custom report with anomaly query
- Subscribe to report with alert threshold
- Configure email/Slack notification
Using Third-Party SIEM: Export EventLogFile daily to:
- Splunk
- Datadog
- Sumo Logic
- AWS Security Lake
6. Third-Party Integration Security
6.1 Integration Risk Assessment Matrix
Before allowing any third-party integration, assess risk:
| Risk Factor | Low | Medium | High |
|---|---|---|---|
| Data Access | Read-only, limited objects | Read most objects | Write access, full API |
| OAuth Scopes | Specific scopes only | api scope |
full scope |
| Session Duration | <2 hours | 2-8 hours | >8 hours, refresh tokens |
| IP Restriction | Static IPs, allowlisted | Some static IPs | Dynamic IPs, no allowlist |
| Vendor Security | SOC 2 Type II, recent audit | SOC 2 Type I | No SOC 2 |
Decision Matrix:
- 0-5 points: Approve with standard controls
- 6-10 points: Approve with enhanced monitoring
- 11-15 points: Require additional security measures or reject
6.2 Common Integrations and Recommended Controls
Gainsight (Customer Success Platform)
Data Access: High (needs Account, Contact, Case, Custom Objects) Recommended Controls:
- ✅ IP allowlisting (Section 2.1.1)
- ✅ Reduce OAuth scopes from
fulltoapi+ specific objects - ✅ Enable Event Monitoring for bulk queries
- ✅ 30-day refresh token expiration
Drift (Marketing/Chat Platform)
Data Access: Medium (needs Lead, Contact, Account) Recommended Controls:
- ✅ IP allowlisting (Section 2.1.2)
- ✅ Read-only access to Lead/Contact
- ✅ Restrict to marketing team profile
- ⚠️ Note: Drift was breached in 2025 - high-risk integration
HubSpot (Marketing Automation)
Data Access: Medium-High Recommended Controls:
- ✅ IP allowlisting (Section 2.1.3) with monthly verification
- ✅ Bidirectional sync monitoring (alert on unexpected write operations)
- ✅ Field-level restrictions (don’t sync SSN, financial data)
7. Compliance Quick Reference
SOC 2 Trust Services Criteria Mapping
| Control ID | Salesforce Control | Guide Section |
|---|---|---|
| CC6.1 | MFA for all users | 1.1 |
| CC6.2 | OAuth scope reduction | 3.1 |
| CC6.6 | IP allowlisting | 2.1 |
| CC7.2 | Event Monitoring | 5.1 |
NIST 800-53 Rev 5 Mapping
| Control | Salesforce Control | Guide Section |
|---|---|---|
| AC-3 | IP restrictions | 2.1 |
| AC-6 | Least privilege OAuth | 3.1 |
| IA-2(1) | MFA enforcement | 1.1 |
| AU-6 | Event monitoring | 5.1 |
Appendix A: Edition Compatibility
| Control | Professional | Enterprise | Unlimited | Performance | Shield Required |
|---|---|---|---|---|---|
| MFA | ✅ | ✅ | ✅ | ✅ | ❌ |
| IP Allowlisting | ❌ | ✅ | ✅ | ✅ | ❌ |
| OAuth Scoping | ✅ | ✅ | ✅ | ✅ | ❌ |
| Event Monitoring | ❌ | Add-on | Add-on | Add-on | ✅ |
| Field Encryption | ❌ | ❌ | ❌ | ❌ | ✅ |
Appendix B: References
Official Salesforce Documentation:
Integration Vendor IP Documentation:
Supply Chain Incident Reports:
Changelog
| Date | Version | Maturity | Changes | Author |
|---|---|---|---|---|
| 2025-12-12 | 0.1.0 | draft | Initial Salesforce hardening guide with focus on integration security | Claude Code (Opus 4.5) |
Next Steps:
- Review your current Salesforce configuration against L1 (Baseline) controls
- Implement IP allowlisting for high-risk integrations (Gainsight, Drift, HubSpot)
- Audit Connected App OAuth scopes and reduce over-permissions
- Enable Event Monitoring for API anomaly detection
- Establish quarterly review process for integration security
Questions or Improvements?
- Open an issue: GitHub Issues
- Contribute: Contributing Guide