'Implement Salesforce lint rules, SOQL injection prevention, and API
Scanned 9/2/2026
Install to Claude Code
npx -y skills add jeremylongshore/tons-of-skills-marketplace --skill salesforce-policy-guardrails --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Salesforce Policy Guardrails?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/jeremylongshore-salesforce-policy-guardrails-tons-of-skills-marketplace)More formats (shields.io, HTML) on the badges page.
---
name: salesforce-policy-guardrails
description: 'Implement Salesforce lint rules, SOQL injection prevention, and API
usage guardrails.
Use when enforcing Salesforce integration code quality, preventing SOQL injection,
or configuring CI policy checks for Salesforce best practices.
Trigger with phrases like "salesforce policy", "salesforce lint",
"salesforce guardrails", "SOQL injection", "salesforce eslint", "salesforce code
review".
'
allowed-tools: Read, Write, Edit, Bash(npx:*)
version: 1.7.0
license: MIT
author: Jeremy Longshore <jeremy@intentsolutions.io>
tags:
- saas
- crm
- salesforce
compatibility: Designed for Claude Code
---
# 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
```typescript
// CRITICAL: Never concatenate user input into SOQL strings
// BAD — SOQL injection vulnerability
async function findAccount(name: string) {
return conn.query(`SELECT Id FROM Account WHERE Name = '${name}'`);
// User input: "'; DELETE FROM Account; --"
// Result: SOQL injection (though Salesforce doesn't support DELETE via SOQL,
// user can still extract data with UNION-like techniques)
}
// GOOD — Escape special characters
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}'`);
}
// BEST — Use parameterized queries with jsforce
// jsforce doesn't have native parameterized SOQL, so always use escapeSoql()
// For Apex, use bind variables:
// [SELECT Id FROM Account WHERE Name = :accountName]
```
### Step 2: ESLint Rules for Salesforce
```javascript
// eslint-plugin-salesforce-integration/rules/no-soql-injection.js
module.exports = {
meta: {
type: 'problem',
docs: { description: 'Prevent SOQL injection by detecting string concatenation in query calls' },
},
create(context) {
return {
CallExpression(node) {
// Detect conn.query(`...${variable}...`)
if (
node.callee.property?.name === 'query' &&
node.arguments[0]?.type === 'TemplateLiteral' &&
node.arguments[0].expressions.length > 0
) {
// Check if expressions use the escapeSoql wrapper
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
```bash
#!/bin/bash
# pre-commit-salesforce-check.sh
# Detect Salesforce credential patterns in staged files
PATTERNS=(
'00D[a-zA-Z0-9]{15}' # Org ID (shouldn't be hardcoded)
'005[a-zA-Z0-9]{15}' # User ID (context-dependent)
'force://[a-zA-Z0-9]+' # Salesforce login token
'SF_PASSWORD=.' # Password in code
'SF_SECURITY_TOKEN=.' # Security token in code
'SF_CLIENT_SECRET=.' # OAuth client secret in code
)
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
# Check for .env files being committed
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
```typescript
// Runtime guardrails preventing API limit exhaustion
class SalesforceGuardrails {
private callsThisMinute = 0;
private lastReset = Date.now();
private maxCallsPerMinute = 50; // Conservative limit
async guard(operation: string, estimatedCalls: number = 1): Promise<void> {
// Reset counter every minute
if (Date.now() - this.lastReset > 60000) {
this.callsThisMinute = 0;
this.lastReset = Date.now();
}
// Per-minute throttle (prevent burst)
if (this.callsThisMinute + estimatedCalls > this.maxCallsPerMinute) {
const waitMs = 60000 - (Date.now() - this.lastReset);
console.warn(`SF guardrail: throttling ${operation}, waiting ${waitMs}ms`);
await new Promise(r => setTimeout(r, waitMs));
this.callsThisMinute = 0;
this.lastReset = Date.now();
}
// Check daily limit before proceeding
const conn = await getConnection();
const limits = await conn.request('/services/data/v59.0/limits/');
const usagePercent = (limits.DailyApiRequests.Max - limits.DailyApiRequests.Remaining) / limits.DailyApiRequests.Max;
if (usagePercent > 0.95) {
throw new Error(`SF guardrail: API usage at ${(usagePercent * 100).toFixed(1)}% — blocking ${operation}`);
}
if (usagePercent > 0.80) {
console.warn(`SF guardrail: API usage at ${(usagePercent * 100).toFixed(1)}%`);
}
this.callsThisMinute += estimatedCalls;
}
}
```
### Step 5: CI Policy Checks
```yaml
# .github/workflows/salesforce-policy.yml
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 .gitignore includes sensitive files
run: |
for pattern in ".env" ".env.local" "server.key" "*.pem"; do
if ! grep -q "$pattern" .gitignore; then
echo "ERROR: .gitignore missing '$pattern'"
exit 1
fi
done
```
### Step 6: SOQL Best Practices Enforcement
```typescript
// Automated SOQL quality checks
function validateSoql(soql: string): { valid: boolean; warnings: string[] } {
const warnings: string[] = [];
// Warn on SELECT FIELDS(ALL) — performance anti-pattern
if (soql.includes('FIELDS(ALL)')) {
warnings.push('Avoid FIELDS(ALL) — select only needed fields');
}
// Warn on missing LIMIT
if (!soql.toUpperCase().includes('LIMIT') && !soql.toUpperCase().includes('COUNT(')) {
warnings.push('Missing LIMIT clause — add LIMIT to prevent hitting 50K row limit');
}
// Warn on LIKE with leading wildcard
if (/LIKE\s+'%/.test(soql)) {
warnings.push("Leading wildcard in LIKE '%...' causes full table scan");
}
// Warn on missing WHERE clause
if (!soql.toUpperCase().includes('WHERE') && !soql.toUpperCase().includes('LIMIT 1')) {
warnings.push('No WHERE clause — query may return too many rows');
}
return { valid: warnings.length === 0, 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 |
## Examples
### Block a dynamic SOQL injection path before deployment
Write a unit test using a malicious synthetic filter value, ensure the query builder uses bound variables or a narrowly scoped escape function, and run the custom lint rule in CI. Require the policy failure to include a safe location and remediation hint without printing the payload. If a legitimate query is flagged, document a specific suppression with reviewer approval rather than weakening the global rule or allowlisting unbounded string concatenation.
## Resources
- [SOQL Injection](https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/pages_security_tips_soql_injection.htm)
- [Salesforce Security Guide](https://developer.salesforce.com/docs/atlas.en-us.securityImplGuide.meta/securityImplGuide/)
- [ESLint Plugin Development](https://eslint.org/docs/latest/extend/plugins)
## Next Steps
For architecture blueprints, see `salesforce-architecture-variants`.
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!