Agent Skills: Salesforce Policy & Guardrails

|

UncategorizedID: jeremylongshore/claude-code-plugins-plus-skills/salesforce-policy-guardrails

Install this agent skill to your local

pnpm dlx add-skill https://github.com/jeremylongshore/claude-code-plugins-plus-skills/tree/HEAD/plugins/saas-packs/salesforce-pack/skills/salesforce-policy-guardrails

Skill Files

Browse the full folder contents for salesforce-policy-guardrails.

Download Skill

Loading file tree…

plugins/saas-packs/salesforce-pack/skills/salesforce-policy-guardrails/SKILL.md

Skill Metadata

Name
salesforce-policy-guardrails
Description
|

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

// 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

// 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

#!/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

// 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

# .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

// 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 |

Resources

Next Steps

For architecture blueprints, see salesforce-architecture-variants.