From qa-sast
Runs PMD's built-in Apex security ruleset (`category/apex/security.xml`) against Salesforce Apex source to detect injection, privilege-escalation, cryptographic, and XSS vulnerabilities; configures custom rulesets for regulated-industry Apex codebases; emits SARIF for GitHub Code Scanning upload; integrates `pmd check` as a PR-blocking CI gate. Use when the codebase contains Salesforce Apex and the team needs SAST coverage for ApexSOQLInjection, ApexCRUDViolation, ApexSharingViolations, or the full 10-rule security category.
How this skill is triggered — by the user, by Claude, or both
Slash command
/qa-sast:pmd-apex-rulesThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Per [pmd.github.io - Apex Security Rules][pmd-apex-sec]:
Per pmd.github.io - Apex Security Rules:
PMD ships a built-in category/apex/security.xml ruleset that covers 10
security rules for Salesforce Apex. All 10 rules are present since PMD 5.5.3
and carry Medium (3) priority by default. The ruleset addresses the
Salesforce-specific threat model: SOQL injection, object/field-level security
bypass, sharing-model evasion, hard-coded credentials, insecure endpoints,
XSS through Visualforce, and open redirect.
This skill is the Apex-specific companion to
semgrep-rules and
sonarqube-rules. Those tools cover general
multi-language patterns; this one covers the Salesforce Apex security category
absent from both. The sast-finding-triager agent in
../../agents/sast-finding-triager.md
can unify findings across all five SAST tools.
.cls, .trigger) and requires SAST
gating in CI.Per pmd.github.io - Installation, PMD requires Java 8 or later.
Download the zip from the GitHub releases page, unzip, and add
bin/ to PATH:
# Linux / macOS
unzip pmd-dist-*.zip -d ~/pmd
export PATH="$HOME/pmd/bin:$PATH"
# Verify
pmd --version
For CI, prefer the Docker image or the Maven/Gradle plugin to avoid zip
management. The Docker image is available at ghcr.io/pmd/pmd.
Per pmd.github.io - CLI Reference:
pmd check -d . -R category/apex/security.xml -f sarif -r pmd-apex.sarif
Flag reference (per pmd-cli):
| Flag | Meaning |
|---|---|
-d <path> | Source directory or file to analyze |
-R <refs> | Ruleset path; comma-separated for multiple |
-f <format> | Output format (sarif, text, xml, json, html; default: text) |
-r <file> | Write report to file instead of stdout |
--minimum-priority <n> | Skip rules below priority n (1=High, 5=Info) |
--cache <file> | Enable incremental analysis (per pmd-cache) |
Exit codes (per pmd-cli):
| Code | Meaning |
|---|---|
| 0 | Success, no violations |
| 1 | Unhandled exception |
| 2 | Invalid arguments |
| 4 | Violations detected |
| 5 | Recoverable parsing errors |
Per pmd.github.io - Apex Security Rules:
| Rule | What it detects |
|---|---|
ApexSOQLInjection | Dynamic SOQL/DML built by string concatenation with untrusted input |
ApexCRUDViolation | Missing object/field permission check before SOQL, SOSL, or DML |
ApexSharingViolations | Classes performing DML without an explicit sharing keyword |
ApexBadCrypto | Hard-coded IVs or keys in cryptographic operations |
ApexDangerousMethods | Calls to Configuration.disableTriggerCRUDSecurity() or sensitive System.debug() |
ApexInsecureEndpoint | Plain HTTP (non-HTTPS) callout endpoints |
ApexOpenRedirect | Redirects using unsanitized user-controlled input |
ApexSuggestUsingNamedCred | Hard-coded credentials in HTTP headers; suggests Named Credentials |
ApexXSSFromEscapeFalse | addError() called with escape disabled, exposing raw user content |
ApexXSSFromURLParam | URL parameters used in output contexts without escaping |
Per pmd-apex-sec: "Detects the usage of untrusted / unescaped variables in DML queries."
Non-compliant:
public class Foo {
public void test1(String t1) {
Database.query('SELECT Id FROM Account' + t1);
}
}
Compliant (bind variable - automatically sanitized by the Apex runtime):
public class Foo {
public void test1(String accountName) {
List<Account> accounts = [SELECT Id FROM Account WHERE Name = :accountName];
}
}
Per pmd-apex-sec: "The rule validates you are checking for
access permissions before a SOQL/SOSL/DML operation." Accepted remediation
paths include DescribeSObjectResult system checks, WITH SECURITY_ENFORCED,
or (since Winter '23 / API v56) WITH USER_MODE.
The rule is configurable for custom authorization facades via regex properties
(createAuthMethodPattern, readAuthMethodPattern, etc.) so teams using an
internal ESAPI wrapper can still pass the check per pmd-apex-sec.
Per pmd-apex-sec: "Detect classes declared without explicit
sharing mode if DML methods are used." The three accepted keywords are with sharing, without sharing, and inherited sharing. The intent is to force
a conscious declaration of sharing posture, not to mandate a specific value.
Per pmd.github.io - Making Rulesets:
Use a custom XML ruleset to select a subset, override priorities, or add exclusion patterns for generated code:
<?xml version="1.0"?>
<ruleset name="Apex Security - Regulated"
xmlns="http://pmd.sourceforge.net/ruleset/2.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0
https://pmd.sourceforge.io/ruleset_2_0_0.xsd">
<description>Apex security rules for regulated-industry Apex</description>
<!-- Include the full security category -->
<rule ref="category/apex/security.xml">
<!-- Suppress for auto-generated WSDL stubs -->
<exclude name="ApexSuggestUsingNamedCred"/>
</rule>
<!-- Exclude generated code directories -->
<exclude-pattern>.*/generated/.*</exclude-pattern>
</ruleset>
Run with the custom ruleset:
pmd check -d force-app/main/default/classes \
-R config/pmd-apex-regulated.xml \
-f sarif \
-r pmd-apex.sarif
Per pmd-rulesets, referencing an entire category means the ruleset automatically picks up new rules added to that category in future PMD versions. Pin specific versions in CI to avoid unexpected gate changes.
Per pmd-cache (PMD 5.6.0+):
pmd check -d force-app/main/default/classes \
-R category/apex/security.xml \
-f sarif \
-r pmd-apex.sarif \
--cache .pmd-cache/apex.cache
The cache stores file checksums. Unchanged files reuse cached results; only modified files are re-analyzed. The generated report is identical to a full run (per pmd-cache). Cache is invalidated automatically on PMD version change, ruleset modification, or auxclasspath change.
jobs:
pmd-apex:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Download PMD
run: |
PMD_VERSION=7.7.0
curl -Lo pmd.zip \
https://github.com/pmd/pmd/releases/download/pmd_releases%2F${PMD_VERSION}/pmd-dist-${PMD_VERSION}-bin.zip
unzip -q pmd.zip -d pmd-dist
echo "$PWD/pmd-dist/pmd-bin-${PMD_VERSION}/bin" >> $GITHUB_PATH
- name: Run PMD Apex security scan
run: |
pmd check \
-d force-app/main/default/classes \
-R category/apex/security.xml \
-f sarif \
-r pmd-apex.sarif \
--cache .pmd-cache/apex.cache
# Exit code 4 = violations found (per pmd-cli); gate blocks on non-zero
continue-on-error: false
- name: Upload SARIF to GitHub Code Scanning
uses: github/codeql-action/upload-sarif@v3
if: always()
with:
sarif_file: pmd-apex.sarif
category: pmd-apex
Per pmd-cli, exit code 4 means violations detected. continue-on-error: false blocks the PR on any finding. Use --minimum-priority 2 to gate only on High and Critical findings while still uploading all findings via SARIF.
PMD suppression in Apex (per pmd-apex-sec): annotate the
method or class with @SuppressWarnings:
@SuppressWarnings('PMD.ApexCRUDViolation')
public class VisualforceGetter {
// Visualforce getters auto-enforce FLS; CRUD check is redundant here
public List<Account> getAccounts() {
return [SELECT Id, Name FROM Account];
}
}
Add a comment explaining the justification. Suppressions without rationale are flagged in code review.
| Anti-pattern | Why it fails | Fix |
|---|---|---|
Running against all directories including classes/test | Test classes generate false positives for CRUD/sharing | Exclude test directories with <exclude-pattern> |
No --cache in CI on large orgs | Full re-scan of 500+ classes on every commit is slow | Add --cache .pmd-cache/apex.cache (Step 5) |
Suppressing ApexSOQLInjection globally | Masks real injection risks | Suppress per method with a written justification only |
Floating latest PMD version in CI | Gate breaks when a new rule fires unexpectedly | Pin the PMD_VERSION variable |
Custom auth facade without *AuthMethodPattern config | All CRUD-checked methods still flagged as violations | Configure createAuthMethodPattern etc. per pmd-apex-sec |
codeql-queries.ApexCRUDViolation generates false positives on Visualforce getter methods
where FLS is enforced automatically; suppress with justification per Step 7..js); client-side XSS is out
of scope. Use semgrep-rules with the
p/owasp-top-ten ruleset for LWC JavaScript.-f format options including sarifsemgrep-rules - multi-language pattern SASTcodeql-queries - interprocedural / data-flow SASTsonarqube-rules - semantic-DB SASTsast-finding-triager - unifier across all SAST toolsnpx claudepluginhub testland/qa --plugin qa-sastProvides CDSS development patterns for drug interaction checking, dose validation, clinical scoring (NEWS2, qSOFA), and alert classification integrated into EMR workflows.