rgoussu@goussu: ~/library/java/testing
~/library/java/testing cat security-testing.md

Security testing — SCA, SAST, DAST & fuzzing on the JVM

# Security testing for Java builds — dependency scanning, static and dynamic analysis, JVM fuzzing, secrets scanning, and CI wiring.

Conceptsaved 2026-08-09 #java#testing#security#ci#build

Overview

Most Java vulnerabilities ship in dependencies, not first-party code — Log4Shell made that permanent institutional memory — so security testing starts with the dependency tree and works inward: SCA for known CVEs, SAST for insecure code patterns, DAST against the running app, fuzzing for the parsers, and secrets scanning over the repo. None of it matters unless it runs in the build; the tooling below all has Maven/Gradle or CI-native integration.

Key points

  • SCA (software composition analysis): OWASP dependency-check matches the full transitive tree against the NVD and other CVE sources (free; needs an NVD API key for sane update speed); Snyk adds a commercial curated DB, fix PRs and reachability hints. Transitive dependencies are the point — your directs are the minority of the tree.
  • SAST: SpotBugs + the FindSecBugs plugin covers the classic JVM sinks (injection, XXE, weak crypto, deserialisation); Semgrep runs fast, writable pattern rules well-suited to PR diffs; SonarQube folds security rules and hotspot review into the quality gate.
  • DAST: OWASP ZAP probes the running application (baseline passive scan vs full active scan) — catches misconfiguration and behaviour static tools cannot see; automate via the ZAP GitHub Actions / container against an ephemeral deployment.
  • Fuzzing on the JVM: Jazzer (libFuzzer-based) does coverage-guided fuzzing of Java targets and powers Java in OSS-Fuzz; point it at anything parsing untrusted bytes.
  • Secrets scanning: gitleaks/trufflehog in pre-commit and CI, GitHub secret scanning with push protection on the platform side; scan history, not just HEAD.
  • SBOM: generate CycloneDX from Maven/Gradle so consumers and later audits can answer "are we affected?" without re-resolving the tree.
  • Framework note: exercise DAST against real framework config — Spring Security filter chains, Quarkus and Micronaut security annotations are exactly the layer ZAP validates and SAST mostly cannot; unit-test authorisation rules too (e.g. Spring Security's @WithMockUser/MockMvc support).

Details

Layer by layer

Layer Question answered Tools When it runs
SCA Known-vulnerable dependencies? dependency-check, Snyk, gradle dependencies-based audit Every build + scheduled (new CVEs hit old code)
SAST Insecure first-party patterns? SpotBugs+FindSecBugs, Semgrep, SonarQube PR / merge
DAST Is the running app exploitable? OWASP ZAP (baseline in PR, full scan nightly) Against ephemeral/staging deploys
Fuzzing Do parsers crash on hostile input? Jazzer, OSS-Fuzz for OSS projects Continuous or nightly jobs
Secrets Credentials in the repo? gitleaks, trufflehog, GitHub push protection Pre-commit + CI + history audits

Build & CI wiring

  • Maven: org.owasp:dependency-check-maven bound to verify with failBuildOnCVSS (e.g. 7.0); com.github.spotbugs:spotbugs-maven-plugin with the findsecbugs-plugin dependency; cyclonedx-maven-plugin for the SBOM.
  • Gradle: org.owasp.dependencycheck, com.github.spotbugs, org.cyclonedx.bom plugins — same knobs.
  • Use a suppression file (dependency-check-suppressions.xml, reviewed in PRs) for false positives; an unsuppressed red build that everyone ignores trains the team to ignore red builds.
  • Split fast checks (SCA cache-hit, Semgrep, gitleaks) into the PR gate; slow ones (full ZAP active scan, long fuzz runs, full NVD refresh) into scheduled pipelines.
  • Rescan on a schedule, not only on change: yesterday's clean build is vulnerable the day a new CVE lands on an unchanged dependency.

Examples

<plugin>
  <groupId>org.owasp</groupId>
  <artifactId>dependency-check-maven</artifactId>
  <version>${depcheck.version}</version>
  <configuration>
    <failBuildOnCVSS>7.0</failBuildOnCVSS>
    <suppressionFiles>
      <suppressionFile>dependency-check-suppressions.xml</suppressionFile>
    </suppressionFiles>
    <nvdApiKeyEnvironmentVariable>NVD_API_KEY</nvdApiKeyEnvironmentVariable>
  </configuration>
  <executions>
    <execution><goals><goal>check</goal></goals></execution>
  </executions>
</plugin>
// Jazzer fuzz target — feed hostile bytes to a parser
public class InvoiceParserFuzzTest {
    @FuzzTest
    void parseNeverThrowsUnexpected(FuzzedDataProvider data) {
        try {
            InvoiceParser.parse(data.consumeRemainingAsBytes());
        } catch (InvalidInvoiceException expected) {
            // declared failure mode is fine; anything else is a finding
        }
    }
}

Related