A production-grade automated security pipeline integrating three SAST/SCA tools into a Jenkins CI/CD workflow. Built and deployed on a real banking application environment.
Every git push automatically triggers a security scan and posts findings as GitLab commit comments within 3 minutes.
The demo shows the actual HTML report format generated by the pipeline on every build — executive summary, Semgrep findings, CVE list, and SonarQube results.
Developer pushes code
│
▼
GitLab webhook
│
▼
Jenkins picks up
│
┌────┴──────────────────────────────────────────┐
│ │
▼ │
Semgrep SAST ──── findings posted ────▶ │
│ as GitLab commit │
▼ comments │
Dependency-Check SCA ──── per tool ──▶ │
│ │
▼ │
SonarQube analysis ──── quality gate ──▶ │
│ │
└───────────────────────────────────────────────┘
│
Generate 3 HTML reports
├── security-report.html (executive summary)
├── semgrep-detail.html (full Semgrep findings)
└── dc-report-full.html (full CVE list)
│
Security Gate
├── PASSED → ✅ green commit status in GitLab
└── FAILED → ❌ red commit status + combined reason
The security team maintains one Jenkinsfile in this repository. Every application repository points to it. When the security team updates a rule or adds a tool, it takes effect on every project on their next push — no coordination with development teams required.
Application repositories contain only a 3-line comment Jenkinsfile with no logic.
All stages run regardless of earlier findings. The Security Gate at the end fails with a combined reason covering all tools. Developers see all issues in one push, not one per push.
Generating one large report that tries to show everything ends up truncating at thousands of CVEs. Instead three separate reports are generated — each focused, complete, and fast to load:
| Report | Audience | Content |
|---|---|---|
security-report.html |
Management · Auditors · Security team | Executive summary, counts, links |
semgrep-detail.html |
Security team · Developers | Full Semgrep findings, no truncation |
dc-report-full.html |
Security team | All CVEs with filter controls (Dependency-Check native) |
Dependency-Check scans use --noupdate — a dedicated daily Jenkins job updates the NVD database at 2am. This keeps pipeline scans at ~30 seconds instead of the 20+ minutes a live database download would take.
| Tool | Version | Role |
|---|---|---|
| Semgrep | 1.x | SAST — code pattern analysis |
| OWASP Dependency-Check | 12.x | SCA — library CVE scanning |
| SonarQube | 10.x CE | SAST + quality gate |
| Jenkins | LTS | Pipeline orchestration |
| GitLab | CE | Source control + webhook trigger |
| Language | Semgrep | Dependency-Check | SonarQube |
|---|---|---|---|
| PHP | ✅ p/php + p/owasp-top-ten |
✅ composer.lock |
✅ |
| .NET C# | ✅ p/csharp |
✅ packages.lock.json |
✅ |
| JavaScript | ✅ p/owasp-top-ten |
✅ package-lock.json |
✅ |
.
├── Jenkinsfile Main pipeline — all projects point here
├── Jenkinsfile.nvd-update Daily NVD database update job
├── Jenkinsfile.app-repo-stub Paste this into application repos
├── generate-report.py HTML security report generator
├── sonar-project.properties SonarQube project config template
├── dc-suppressions.xml Dependency-Check false positive suppressions
│
├── rules/
│ └── custom-security-rules.yml Custom Semgrep rules (PHP + C#)
│
├── scripts/
│ └── install-tools.sh Tool installation script (Ubuntu 24.04)
│
├── docs/
│ ├── architecture.md Pipeline design decisions
│ ├── onboarding.md How to onboard a new project (3 steps)
│ ├── jenkins-setup.md Jenkins credentials reference
│ └── tools.md Tool configuration and tuning
│
└── demo/
├── generate-demo.py Script that generated the demo report
└── security-report-demo.html Demo report with fictional data
- Ubuntu 24.04 LTS server for Jenkins
- Jenkins LTS installed with plugins: Git, GitLab, Pipeline, SonarQube Scanner
- SonarQube CE running and accessible
- GitLab CE running and accessible
- Java 17+ on the Jenkins server
sudo bash scripts/install-tools.shThis installs Semgrep, Dependency-Check, and SonarQube Scanner.
Get a free API key: https://nvd.nist.gov/developers/request-an-api-key
/opt/dependency-check/bin/dependency-check.sh \
--updateonly \
--nvdApiKey YOUR_API_KEY \
--nvdApiDelay 6000This takes 15–20 minutes on first run. Subsequent daily updates take ~2 minutes.
Create these credentials in Jenkins → Manage Jenkins → Credentials:
| ID | Kind | Purpose |
|---|---|---|
jenkins-ci-checkout |
Username/password | Checkout application repos |
security-group-token |
Username/password | Checkout this repo |
gitlab-api-token |
Secret text | Post GitLab comments |
sonarqube-token |
Secret text | SonarQube analysis |
sonarqube-read-token |
Secret text | Read SonarQube results |
nvd-api-key |
Secret text | NVD database updates |
gitlab-webhook-secret |
Secret text | Webhook verification |
See docs/jenkins-setup.md for detailed instructions.
Edit Jenkinsfile and set your server addresses:
environment {
SONAR_HOST = 'http://<sonarqube-ip>:9000'
GITLAB_HOST = 'http://<gitlab-ip>'
DC_HOME = '/opt/dependency-check/bin/dependency-check.sh'
}In Jenkins: New Item → Freestyle or Pipeline → Name: dependency-check-db-update
Use Jenkinsfile.nvd-update as the pipeline definition. Set cron trigger: H 2 * * *.
Follow the 3-step process in docs/onboarding.md:
- Create SonarQube project (2 min)
- Create Jenkins pipeline job with 3 parameters (5 min)
- Add GitLab webhook (2 min)
rules/custom-security-rules.yml contains rules targeting patterns that community rulesets miss. Examples:
direct-sql-bypass-orm— detects raw SQL bypassing the ORM layersensitive-data-in-logs— detects password/PIN/card fields being loggedweak-hash-md5— flags MD5 used for password or data hashinghardcoded-secret— detects API keys and passwords in source codeshell-exec-user-input— detects command injection patterns
These rules work on top of the community rulesets — not instead of them.
After every push, findings are posted directly to the GitLab commit as comments:
### Semgrep SAST
**Status: FAILED**
#### Blocking findings (ERROR) — 2
- **ERROR** | PaymentController.php line 142 | SQL query built by string concatenation...
- **ERROR** | Dockerfile line 31 | Container runs as root — specify USER directive...
---
_Fix blocking findings before pipeline can pass._
The commit also gets a green ✅ or red ❌ status that appears in merge requests and the repository.
This repository is sanitised for public sharing. All internal IP addresses, project names, CVE findings, and credentials have been removed. Replace the <placeholder> values in Jenkinsfile with your own environment's addresses before deployment.
Never commit credentials, scan results, or internal project data to this repository.