Salesforce Policy & Guardrails
Overview
Automated policy enforcement for Salesforce integrations: SOQL injection prevention, API key leak detection, governor limit guardrails, and CI pipeline checks.
Prerequisites
- ESLint configured in project
- jsforce TypeScript project
- CI/CD pipeline with policy checks
- Understanding of Salesforce security model
Instructions
Step 1: SOQL Injection Prevention
async function findAccount(name: string) {
return conn.query(`SELECT Id FROM Account WHERE Name = '${name}'`);
}
function escapeSoql(value: string): string {
return value
.replace(/\\/g, '\\\\')
.replace(/'/g, "\\'")
.replace(/"/g, '\\"')
.replace(/%/g, '\\%')
.replace(/_/g, '\\_');
}
async function findAccountSafe(name: string) {
const safeName = escapeSoql(name);
return conn.query(`SELECT Id, Name FROM Account WHERE Name = '${safeName}'`);
}
Step 2: ESLint Rules for Salesforce
module.exports = {
meta: {
type: 'problem',
docs: { description: 'Prevent SOQL injection by detecting string concatenation in query calls' },
},
create(context) {
return {
CallExpression(node) {
if (
node.callee.property?.name === 'query' &&
node.arguments[0]?.type === 'TemplateLiteral' &&
node.arguments[0].expressions.length > 0
) {
for (const expr of node.arguments[0].expressions) {
if (expr.type !== 'CallExpression' || expr.callee?.name !== 'escapeSoql') {
context.report({
node: expr,
message: 'SOQL injection risk: wrap user input with escapeSoql(). Example: `WHERE Name = \'${escapeSoql(userInput)}\'`',
});
}
}
}
},
};
},
};
Step 3: Credential Leak Detection
#!/bin/bash
PATTERNS=(
'00D[a-zA-Z0-9]{15}'
'005[a-zA-Z0-9]{15}'
'force://[a-zA-Z0-9]+'
'SF_PASSWORD=.'
'SF_SECURITY_TOKEN=.'
'SF_CLIENT_SECRET=.'
)
FOUND=0
for PATTERN in "${PATTERNS[@]}"; do
if git diff --cached --name-only | xargs grep -l "$PATTERN" 2>/dev/null; then
echo "ERROR: Possible Salesforce credential found: $PATTERN"
FOUND=1
fi
done
if git diff --cached --name-only | grep -E '\.env$|\.env\.local$|\.env\.prod'; then
echo "ERROR: .env file staged for commit"
FOUND=1
fi
exit $FOUND
Step 4: API Usage Guardrails
class SalesforceGuardrails {
private callsThisMinute = 0;
private lastReset = Date.now();
private maxCallsPerMinute = 50;
async guard(operation: string, estimatedCalls: number = 1): Promise<void> {
if (Date.now() - this.lastReset > 60000) {
this.callsThisMinute = 0;
this.lastReset = Date.now();
}
if (this.callsThisMinute + estimatedCalls > this.maxCallsPerMinute) {
const waitMs = 60000 - (Date.now() - this.lastReset);
console.warn(`SF guardrail: throttling , waiting ms`);
( (r, waitMs));
. = ;
. = .();
}
conn = ();
limits = conn.();
usagePercent = (limits.. - limits..) / limits..;
(usagePercent > ) {
();
}
(usagePercent > ) {
.();
}
. += estimatedCalls;
}
}
Step 5: CI Policy Checks
name: Salesforce Policy Check
on: [push, pull_request]
jobs:
policy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Check for SOQL injection risks
run: |
# Detect raw string interpolation in .query() calls
if grep -rn "\.query(\`.*\$\{" --include="*.ts" --include="*.js" src/ | grep -v "escapeSoql"; then
echo "ERROR: Possible SOQL injection — wrap user input with escapeSoql()"
exit 1
fi
- name: Check for hardcoded credentials
run: |
if grep -rE "(SF_PASSWORD|SF_SECURITY_TOKEN|SF_CLIENT_SECRET)\s*=" --include="*.ts" --include="*.js" src/; then
echo "ERROR: Hardcoded Salesforce credentials found"
exit 1
fi
- name: Check for production org IDs
run: |
if grep -rE "00D[a-zA-Z0-9]{15}" --include="*.ts" --include="*.js" --include="*.json" src/; then
echo "WARNING: Hardcoded Salesforce Org ID found — use environment variables"
fi
- name: Verify
Step 6: SOQL Best Practices Enforcement
function validateSoql(soql: string): { valid: boolean; warnings: string[] } {
const warnings: string[] = [];
if (soql.includes('FIELDS(ALL)')) {
warnings.push('Avoid FIELDS(ALL) — select only needed fields');
}
if (!soql.toUpperCase().includes('LIMIT') && !soql.toUpperCase().includes('COUNT(')) {
warnings.push('Missing LIMIT clause — add LIMIT to prevent hitting 50K row limit');
}
if (/LIKE\s+'%/.test(soql)) {
warnings.push("Leading wildcard in LIKE '%...' causes full table scan");
}
if (!soql.toUpperCase().includes('WHERE') && !soql.toUpperCase().includes('LIMIT 1')) {
warnings.push('No WHERE clause — query may return too many rows');
}
{ : warnings. === , warnings };
}
Output
- SOQL injection prevention with escape function
- ESLint rule detecting injection risks
- Pre-commit hook blocking credential leaks
- Runtime API usage guardrails
- CI pipeline policy checks
Error Handling
| Issue | Cause | Solution |
|---|
| ESLint rule false positive | escapeSoql used but not detected | Update rule to check function name |
| Guardrail blocks valid request | Threshold too low | Tune per-minute and daily thresholds |
| Pre-commit hook slow | Too many files | Use lint-staged for incremental checks |
| SOQL injection detected | String concatenation | Apply escapeSoql() wrapper |
Resources
Next Steps
For architecture blueprints, see salesforce-architecture-variants.