chapter 01
Architecture & Threat Model
Three tools, three different layers of the same problem. Before configuring any of them, it's worth understanding why none of them alone would be enough.
The three layers
A secure code review pipeline isn't one tool, it's a composition. Each gate in this lab looks at a different moment in the code's lifecycle:
- Gitleaks looks at Git history β every commit in a PR β searching for credential patterns (API keys, tokens, passwords in a recognizable format).
- SonarCloud looks at the source code itself β static analysis (SAST) of the new code introduced in the PR, without running anything.
- OWASP ZAP looks at the running application β it attacks from the outside, the way a real visitor would, with no access at all to the source code.
In this lab's own history, a test PR contained a variable DB_PASSWORD = "supersecret123". Gitleaks passed clean β the string doesn't have enough entropy and doesn't match any known credential pattern. SonarCloud blocked the same PR, because it understands that a hardcoded credential in source code is an insecure practice, regardless of whether the string "looks like" a real key. That's exactly the kind of gap that justifies having both gates, not just one.
What each gate solves β and what it doesn't
| Gate | Solves | Doesn't solve | Runs when |
|---|---|---|---|
| Gitleaks | A secret leaking into Git history (keys, tokens, passwords in a recognizable format) | A weak credential with no recognizable format, insecure business logic, runtime vulnerabilities | local pre-commit + every Pull Request |
| SonarCloud | Code smells, bugs, known vulnerabilities in new code (SQL injection via concatenation, eval(), hardcoded credentials, test coverage) |
Vulnerabilities that only show up at runtime (server configuration, HTTP headers, actual application behavior) | Every Pull Request, evaluating only new code |
| OWASP ZAP | Insecure behavior of the already-running application: missing security headers, CORS configuration, misconfigured cookies | Doesn't read the source code, so it can't point to where to fix it β only what is exposed | On demand, against an environment with the app up |
Why GitHub Actions + SonarCloud (not self-hosted)
The most important infrastructure decision in this lab was keeping nothing running 24/7. A self-hosted SonarQube would require an always-on VM just to store analysis history; a self-hosted CI runner has the same problem. By using GitHub Actions (hosted, free for public repos) and SonarCloud (same idea, managed version of SonarQube), everything runs on demand β the pipeline only "exists" while it's executing.
This is different from a lab that needs to receive real internet traffic (like a honeypot) β there, keeping something always on is part of the requirement. Here, the requirement is just proving the gate works, which doesn't require constant presence.
Why OWASP Juice Shop as the target
Juice Shop is the de facto standard for AppSec demos: Node.js, Docker-ready, with vulnerabilities documented and mapped to the OWASP Top 10. That means the DAST gate (ZAP) has guaranteed real findings against it without needing to introduce an artificial vulnerability β the app already ships that way on purpose, for educational use.
The two moments of the pipeline
This repository's workflows are split into two files, because they represent two different moments in the lifecycle:
pre-merge-gates.yml- Gitleaks + SonarCloud, on every Pull Request
pre-deploy-dast.yml- OWASP ZAP, manually triggered against the deployed app
The second workflow doesn't run on every push β only on demand. Why that choice was made (and what happened when it did run automatically) is covered in chapter 05.