← All posts
· 3 min read ·
SecurityCI/CDDevSecOpsSalesforce

Shift-Left Security in Salesforce CI/CD Pipelines

How to move security checks earlier in the Salesforce development lifecycle - before code reaches a sandbox, before a reviewer sees it, before it can cause a problem.

Digital security concept with lock and code

“Shift left” is a well-worn phrase in DevSecOps, but in the Salesforce world it’s still mostly aspirational. Most orgs still do security review as a manual gate before production - one person, one checklist, once per release. That doesn’t scale.

Here’s how to bake security into the pipeline so it runs automatically on every commit.

The Cost of Late Security Findings

A security issue found in a developer’s IDE costs minutes to fix. Found in code review: an hour. Found in UAT: a day. Found in production: a week, a breach notification, and a board conversation.

The Salesforce ecosystem has good tooling for early detection - it’s just not widely used yet.

Static Analysis with PMD

PMD with the Apex ruleset is the baseline. It catches the obvious stuff: SOQL in loops, hardcoded IDs, empty catch blocks, missing with sharing declarations.

Add it to your pipeline as a required check:

- name: Run PMD
  run: |
    pmd check \
      --dir force-app \
      --rulesets category/apex/security.xml,category/apex/performance.xml \
      --format sarif \
      --report-file pmd-results.sarif \
      --minimum-priority 2

Set --minimum-priority 2 to block on critical and high findings only. You’ll tune this down over time as the codebase improves.

Salesforce Code Analyzer

Salesforce’s own Code Analyzer (formerly Scanner) wraps PMD, ESLint, and RetireJS into a single CLI. It catches Apex security issues, LWC XSS vectors, and known-vulnerable JavaScript libraries:

sf scanner run \
  --target "force-app/**/*.cls,force-app/**/*.js" \
  --pmdconfig config/pmd-ruleset.xml \
  --eslintconfig config/.eslintrc.json \
  --severity-threshold 2

The --severity-threshold 2 flag makes the command exit non-zero on high and critical findings, which fails the CI job.

SOQL Injection Detection

PMD catches some SOQL injection patterns, but it misses dynamic queries built across multiple methods. For thorough detection, add a custom rule or use a dedicated scanner:

The key patterns to hunt for:

  • Database.query() where the string argument includes any variable
  • String.format() used to build SOQL strings
  • User-controlled input (from ApexPages.currentPage().getParameters(), REST request bodies, or LWC @wire adapters) flowing into any query

Permission Set and Profile Drift Detection

Security misconfigurations in metadata are just as dangerous as code vulnerabilities. Add a check that fails if any of these appear in a PR’s changed metadata:

  • New Modify All Data or View All Data permission assignments
  • New connected app with full_access OAuth scope
  • Any profile change that elevates API access
  • Removal of field-level security restrictions on PII fields

A simple grep over the metadata diff catches most of this:

git diff HEAD~1 --name-only | xargs grep -l "modifyAllData>true\|viewAllData>true" && \
  echo "Elevated permissions detected - manual review required" && exit 1

Secrets Scanning

Developers commit secrets to Salesforce repos more than you’d think. Connection strings in test classes, hardcoded API keys in custom metadata, auth tokens in anonymous Apex files committed by mistake.

Run a secrets scanner on every push:

- name: Secrets scan
  uses: aquasecurity/trivy-action@v0.35.0
  with:
    scan-type: fs
    scanners: secret
    exit-code: '1'

Making It Stick

The technical implementation is the easy part. The hard part is culture. Security checks that block developers get worked around. The key is:

  1. Fast feedback - the scan must complete in under 3 minutes or developers ignore it
  2. Clear explanations - every failing check must link to a doc explaining why and how to fix it
  3. False positive process - a clear, documented way to suppress a false positive with a required justification comment
  4. Gradual ratcheting - start with only critical findings blocking the pipeline, add high findings next quarter, then medium

Security that ships with the pipeline is security that actually runs. Security that lives in a checklist someone consults before a release is security that gets skipped when you’re under deadline pressure.

← All posts