Security scanning (Gitleaks + Trivy + DefectDojo)¶
Automated checks on every push and pull request in Woodpecker CI. Failed scans block merges.
What runs in CI¶
| Step | Tool | Detects |
|---|---|---|
gitleaks |
Gitleaks | API keys, passwords, tokens in code and git history |
trivy-fs |
Trivy | HIGH/CRITICAL CVEs in dependencies, Dockerfile / compose misconfig |
trivy-image |
Trivy | Container image CVEs (full-stack apps on main only) |
upload-defectdojo |
DefectDojo API | Sends JSON reports to the dashboard |
security-gate |
— | Fails pipeline if any scan found issues |
Reading a red security-gate¶
The gate fails closed on two different problems, and its output says which:
| Marker | Message says | Meaning | What to do |
|---|---|---|---|
.gitleaks_fail / .trivy_fail |
"found …" | A real finding | Fix it, or allowlist a false positive in scripts/gitleaks.toml (needs PR review) |
.gitleaks_error / .trivy_error |
"SCANNER FAILURE, not a finding" | The scanner never ran | Restart the pipeline. Usually Trivy's vulnerability DB failing to download; it already retries 3×. |
A scanner that could not run proves nothing, so it still blocks the deploy —
but do not go looking for a CVE that was never reported. The two are told apart
by whether the step produced a report at all, not by its exit code, since both
cases exit non-zero. On a scanner error the DefectDojo upload is skipped as
well: reimporting a placeholder report with close_old_findings=true would
close every real finding from the last good scan.
Dashboard¶
| Tool | URL |
|---|---|
| DefectDojo | https://security.rafaelgonzalezalbes.com |
Centralized on infra VPS — pipelines on the apps Woodpecker agent (location: apps-vps) upload to the same security.* instance. Do not deploy DefectDojo on the apps VPS.
One Product per Forgejo repo, one CI engagement per product. Findings dedupe on reimport.
Bootstrap all repos:
bash scripts/bootstrap-all-defectdojo-engagements.sh
# or on VPS when API token fails:
bash scripts/wire-defectdojo-woodpecker-sql.sh
scripts/upload-defectdojo.sh resolves product_name + engagement_name from defectdojo_engagement_id before calling /api/v2/reimport-scan/ (DefectDojo 2.40 rejects engagement id alone with product_name parameter missing). It also sets close_old_findings=true so fixed issues (e.g. Trivy DS002) are mitigated on the next successful upload.
Setup (platform admin)¶
1. Deploy DefectDojo¶
# Generate passwords and encrypt (or: sops edit secrets/defectdojo.enc.env manually)
bash scripts/setup-defectdojo-secrets.sh
# Or encrypt template only: bash scripts/secrets-init.sh --encrypt-only
git add secrets/defectdojo.enc.env
git push # Woodpecker deploys stack including defectdojo/
DNS: add A record security → VPS (85.215.32.166).
2. Bootstrap repo engagements¶
# local/.env: DEFECTDOJO_API_KEY=...
bash scripts/rollout-security-scanning.sh
Or per repo:
bash scripts/bootstrap-defectdojo-engagement.sh --owner alex --repo wearlab-main
bash scripts/bootstrap-defectdojo-engagement.sh --owner alex --repo wearlab-main
onboard-project.sh does this automatically for new projects.
3. Woodpecker secrets (per repo)¶
| Secret | Value |
|---|---|
defectdojo_url |
https://security.rafaelgonzalezalbes.com |
defectdojo_api_key |
DefectDojo API token |
defectdojo_engagement_id |
From bootstrap script |
security_webhook_url |
http://kuma-discord-bot.internal:8787/webhook/ci-security |
security_webhook_secret |
Same as SECURITY_WEBHOOK_SECRET on the Discord bot |
Wire all repos:
bash scripts/wire-defectdojo-woodpecker.sh
4. App repo files¶
Each repo needs:
- Security steps in
.woodpecker.yml(seetemplates/woodpecker-security.yml) scripts/gitleaks.toml— allowlist for false positivesscripts/upload-defectdojo.sh— copied byonboard-project.shscripts/notify-discord-security.sh— posts to Discord#securitywhensecurity-gatefails
Discord #security alerts¶
The Kuma Discord bot (uptime-kuma/discord-bot/) routes three alert types to #security:
| Source | Trigger | Delivery |
|---|---|---|
| DefectDojo | scan_added with new High/Critical findings |
HTTP POST /webhook/defectdojo |
| Woodpecker | security-gate step fails |
notify-discord-security step → POST /webhook/ci-security |
| Uptime Kuma | DefectDojo monitor down/up | Socket.IO (monitor name matches DefectDojo\|Security) |
Bot env (secrets/plain/kuma-discord-bot.env)¶
| Variable | Purpose |
|---|---|
DISCORD_CHANNEL_SECURITY |
Discord channel ID for #security |
SECURITY_WEBHOOK_SECRET |
Bearer token for inbound webhooks |
WEBHOOK_PORT |
Default 8787 (internal devops network) |
Generate webhook secret: openssl rand -hex 32
DefectDojo webhook (after bot deploy)¶
bash scripts/setup-defectdojo-discord-webhook.sh
Or manually: Administration → Notification Webhooks → URL http://kuma-discord-bot.internal:8787/webhook/defectdojo, events scan_added, header Authorization: Bearer <SECRET>.
Developer workflow¶
- Push branch → Woodpecker runs security steps before build/test
- If Gitleaks fails: remove the secret, rotate the credential, rewrite history if needed — see
scripts/rotate-exposed-secrets.mdin the infra-devops repo - If Trivy fails: upgrade the vulnerable dependency or pin a patched version
- Open DefectDojo engagement for the repo to track remediation
False positives (allowlist)¶
Edit scripts/gitleaks.toml in the repo:
[allowlist]
paths = ['''(?i)fixtures/''']
regexes = ['''REPLACE_ME''']
Require PR review for allowlist changes — do not blanket-ignore *.env.
infra/devops specifics¶
secrets/*.enc.env(SOPS ciphertext) is allowlisted inscripts/gitleaks.toml- Never commit
secrets/plain/,local/.env, orsecrets/.age/
Verify¶
- Create a test branch with a fake secret → Gitleaks must fail CI
- Check DefectDojo → Product → CI engagement for the finding
- Remove secret → pipeline green
Related¶
- Team workflow — PR must pass CI
- Onboard project — auto-wires DefectDojo secrets
- Architecture → Secrets — SOPS and Woodpecker secrets