ci-security init source
Superseded. These scanners now live in
aioz-ci-templateand arrive throughinclude:, pinned to a tag - seeconventional/ci-cd.md. New repositories should include thelibraryprofile rather than paste these jobs.Two differences in the shared version: the stage is named
validate, notsecurity, and the jobs warn rather than block while the older libraries clear their backlog of findings. The triage guidance below still applies unchanged - it is the reason this README is worth keeping.
GitLab CI security jobs for a Go service, built from the OWASP CI/CD Security, Vulnerable Dependency Management and Docker Security (rule #9) cheat sheets.
Why: on 2026-09-12 none of the team's eight CI configurations ran a dependency, secret or container scanner; two repositories had no CI at all.
Contents
| File | What it is |
|---|---|
security.gitlab-ci.yml | A security stage with four blocking jobs, every tool pinned |
| Job | Catches | Tool |
|---|---|---|
govulncheck | Known vulnerabilities in dependencies, only where the code calls them | govulncheck v1.7.0 (the newest on Go 1.25) |
gitleaks | Secrets committed to git: the merge request's commits, or the whole history on the default branch | gitleaks v8.30.1 |
trivy-fs | HIGH/CRITICAL vulnerable dependencies with a fix available, secrets in files, Dockerfile and compose misconfiguration | trivy 0.74.0 |
hadolint | Dockerfile mistakes: unpinned tags, root, secrets in ENV, and more | hadolint v2.15.1 |
What is still current here
The jobs are superseded; the triage guidance below is not. It is step 1 of adopting the shared template: run the four scanners locally and deal with what they find before the pipeline does, because turning on a job over a backlog of findings only teaches people to ignore it.
| File | Status |
|---|---|
security.gitlab-ci.yml | Superseded by the shared validate:* jobs. Reference only |
| The commands and triage notes below | Current. How to clear a repository before it adopts the template |
Running the scanners locally
- Run the four commands below and triage what they find.
- Write what is not worth fixing into
.gitleaksignoreand.trivyignore.yaml, one reason per entry, with an expiry on the trivy entries. - Commit the reviewed ignore files, then add the shared template - see
conventional/ci-cd.md.
go run golang.org/x/vuln/cmd/govulncheck@v1.7.0 ./...
docker run --rm -v "$PWD:/repo" -w /repo zricethezav/gitleaks:v8.30.1 git --redact --verbose .
docker run --rm -v "$PWD:/repo" -w /repo aquasec/trivy:0.74.0 fs --scanners vuln,secret,misconfig --severity HIGH,CRITICAL --ignore-unfixed --exit-code 1 .
docker run --rm -v "$PWD:/repo" -w /repo hadolint/hadolint:v2.15.1-alpine sh -c 'hadolint --failure-threshold warning Dockerfile*'Before turning it on: patch the Go image
govulncheck reports vulnerabilities in the Go standard library too, and those are fixed by building with a newer Go, not by changing go.mod. On 2026-09-13 the team's registry:5000/go:1.25 was go1.25.8 (built 2026-03-13). Against it, govulncheck reports ten reachable standard-library vulnerabilities, fixed between go1.25.10 and go1.25.13:
| ID | Package | Fixed in |
|---|---|---|
| GO-2026-6218 | net/url | go1.25.13 |
| GO-2026-6091, GO-2026-4982 | html/template | go1.25.13, go1.25.10 |
| GO-2026-6090, GO-2026-5856 | crypto/tls | go1.25.13, go1.25.12 |
| GO-2026-6089, GO-2026-5026 | net/http | go1.25.13 |
| GO-2026-5972 | encoding/asn1 | go1.25.13 |
| GO-2026-5039 | net/textproto | go1.25.11 |
| GO-2026-5037 | crypto/x509 | go1.25.11 |
Every service binary built on that image carries them. On 2026-09-13 registry:5000/go:1.25 was rebuilt on go1.25.14 (digest 6d60a95c…), and the same code comes back clean; the old image stays available as go:1.25.8 for rollback. Keep the image on the newest patch - a Go image that falls behind makes this job fail in every repository, which is the job doing its work.
Why govulncheck passes when trivy fails
They answer different questions. govulncheck asks "does this code call a vulnerable function?" and follows the call graph. trivy asks "is a vulnerable version in the module graph at all?". Run against aioz-common on 2026-09-13, govulncheck found nothing, while trivy failed on ten HIGH CVEs in golang.org/x/crypto v0.43.0 (all in x/crypto/ssh, which nothing calls) and one in moby/go-archive, pulled in only by the test-only testcontainers.
Both results are right, and both matter. The govulncheck result means nothing is exploitable today. The trivy result means the first import of x/crypto/ssh would be, so the fix is still the upgrade (to v0.55.0 here), not an ignore entry. Accept a trivy finding only when the vulnerable package cannot enter the binary - a test-only dependency - and say so in the ignore file.
When a job fails
Fix the finding. If it genuinely does not apply, record that where the tool will read it and a reviewer will see it:
| Tool | Accept a finding with | Write down |
|---|---|---|
| govulncheck | Nothing: upgrade the module. Its findings are reachable by definition | n/a |
| trivy | .trivyignore: CVE-2026-12345 exp:2026-12-31 | a # comment above: why, and who |
| gitleaks | .gitleaksignore: the finding's fingerprint | a comment: false positive, or rotated on which date |
| hadolint | # hadolint ignore=DL3008 alone on the line above the instruction (text after the rule ID breaks the pragma) | the reason on a comment line above it |
A secret gitleaks finds is compromised the moment it was pushed, whatever happens to the commit: rotate it first, then ignore the fingerprint. Removing it from history does not un-leak it.