Skip to main content
GitHub DevSecOps Part 5: Static Application Security Testing (SAST)
  1. Blogs/

GitHub DevSecOps Part 5: Static Application Security Testing (SAST)

Author
Romano Roth
I believe the next competitive edge isn’t AI itself, it’s the organisation around it. As Chief AI Officer at Zühlke, I work with C-level leaders to build enterprises that sense, decide, and adapt continuously. 20+ years turning this conviction into practice.
Ask AI about this article

SCA covered our dependencies. License compliance covered what we are allowed to ship. SAST is where we point the scanners at the code we wrote ourselves. In Part 5 of our GitHub DevSecOps series, Patrick Steger and I add Static Application Security Testing to the pipeline — and find out the hard way that on GitHub it takes three Actions, not one.

Why Three Tools
#

SAST is about finding security flaws in your own source code, not in your dependencies. On GitHub we ended up wiring in three scanners:

  • CodeQL — GitHub’s own static analysis engine, provided as a standard Action.
  • SpotBugs — a long-established Java scanner, strong on traditional code-quality and security rules.
  • Semgrep — a newer pattern-based scanner; the Action is provided directly by the Semgrep team.

Three scanners on a sample project sounds excessive. For a real company codebase it is a reasonable starting point. Each has different strengths and blind spots, and even with all three running we still missed findings that other platforms caught easily. There is no single GitHub Action today that covers SAST end-to-end at a sensible price, so layering tools is the pragmatic path.

A second wrinkle: many SAST scanners on the Marketplace require extra accounts and do not write findings into the GitHub Security tab by default. Getting SpotBugs results into the Security tab needed a custom Action — Juan Manuel from Microsoft helped us convert SpotBugs output into SARIF, the standardized format the Security tab understands.

Wiring SAST into the Pipeline
#

We use the same pattern as before: a sast.yml workflow called from the main pipeline. It declares three jobs — Sast-CodeQL, Sast-SpotBugs, and Sast-Semgrep — and it can be triggered manually via workflow_dispatch or invoked from the main pipeline.

CodeQL. Standard runs-on: ubuntu, the permissions the Action needs (which we figured out by reading the Marketplace docs and a bit of trial and error), fail-fast: false so the whole job runs through, and a matrix that pins the language to Java for speed. The steps check out the code, initialize CodeQL, set up JDK 11, build the project, and run the analyzer. We rebuild here, but a real pipeline could reuse the artifact from the build job.

SpotBugs. Same skeleton — name, runner, permissions, steps — plus a needs: Sast-CodeQL dependency so it can use the cache populated by the CodeQL job. After running SpotBugs, we upload the artifacts and then run the special SARIF upload step so findings appear in the Security tab.

Semgrep. Runs inside Semgrep’s own Docker container, takes a needs dependency for the cache, checks out the code, and runs the scanner with an explicit ruleset. For Java that ruleset is roughly 110 rules — enough to find real issues, but a fraction of what dedicated commercial tooling ships with.

What the Scanners Found
#

Once the pipeline ran, the new jobs showed up in Actions, and the SARIF artifacts (Semgrep.sarif, SpotBugs.sarif) were attached to the run. Filtering the Security tab by tool:

  • CodeQL: two findings. Use of broken or risky cryptography — older MD5 hashing. And cross-site scripting — we return the caller’s message back to the caller, a textbook reflected XSS.
  • Semgrep: three findings. The MD5 issue twice (it matches two rules), plus a Docker recommendation to run the container as a dedicated user instead of root.
  • SpotBugs: zero findings on this project.

What the Scanners Missed
#

The controller we deliberately broke also contains hardcoded passwords and secrets — those we expect secret scanning to catch in a later session. More worrying: an obvious use of the standard Random to generate a key, and a static-material-derived keybytes constant. Both are easy-to-spot weaknesses, both went unflagged. With combined commercial tooling on another platform, we found these without effort. Here we did not.

The honest summary: GitHub SAST works, the SARIF integration into the Security tab is genuinely useful, but expect to invest in extra rules or custom scanners if you need parity with what other platforms surface out of the box.

Key Takeaways
#

  1. One Action is not enough. No single GitHub SAST Action covers everything affordably. We layered CodeQL, SpotBugs, and Semgrep — and even then we missed findings other platforms catch.

  2. SARIF is the price of admission. If a scanner does not emit SARIF, its findings will not show up in the GitHub Security tab. For SpotBugs we needed a custom Action to convert output. Check this before you adopt a tool.

  3. Permissions are trial-and-error. Each Action declares the GitHub permissions it needs. Documentation is uneven; expect a few iterations before the workflow runs cleanly.

  4. Cache between scanner jobs. Use needs: and the actions/cache action to share the compiled Java and dependencies between CodeQL, SpotBugs, and Semgrep. Otherwise you pay the build cost three times.

  5. Pin the language matrix. CodeQL can scan many languages. Restricting the matrix to the languages you actually ship — Java in our case — keeps pipeline time down. Add languages explicitly when you need them.

  6. Expect gaps. MD5 and reflected XSS were caught. A Random-generated key and static key material were not. Treat the default GitHub SAST setup as a baseline, not as completeness — and budget for custom rules or commercial scanners where the risk justifies it.