Skip to content

feat(govulncheck): add call-dagger-govulncheck.yaml, fix module-pin blind spot - #161

Merged
patrick-hermann-sva merged 1 commit into
mainfrom
feat/call-dagger-govulncheck
Sep 5, 2026
Merged

feat(govulncheck): add call-dagger-govulncheck.yaml, fix module-pin blind spot#161
patrick-hermann-sva merged 1 commit into
mainfrom
feat/call-dagger-govulncheck

Conversation

@patrick-hermann-sva

Copy link
Copy Markdown
Contributor

Closes #157. First consumer: stuttgart-things/schmetterpause#86 part 2.

What is here

1. call-dagger-govulncheck.yaml — new, modelled on call-dagger-security-scan.yaml, same conventions (actions pinned by SHA with the tag as a trailing comment, dagger module pinned to a released tag).

2. A Renovate blind spot, fixed — and it is the more consequential half.

3. The two scan templates bumped to @v0.128.0.

The call form, and the thing #157 could not know yet

#157 left one question open: "the export --path step runs after the scan, and a failing scan would skip it and lose the report." go/vuln.go@v0.128.0 answers it in its own doc comment — the module does not solve this, the template has to:

with failOnFinding set, this function returns an error, so a dagger call govulncheck ... export --path report.txt never reaches the export. To both keep the report and gate the build, call twice — once to export, once to gate. The second call hits Dagger's cache and costs nothing.

So: report pass → artifact upload → gate pass. The upload sits between them deliberately; behind the gate it would be skipped exactly when the report matters.

Second finding from the implementation: exit 3 is a text-mode-only signal. With json, sarif and openvex govulncheck exits 0 even on a reachable finding, on the assumption that the caller parses. The module compensates with an internal second text pass; asking for --format text directly on the gate step is cheaper and does the same thing. So the report honours format, the gate is always text.

fail-on-finding defaults to true, unlike the gosec template's report-only posture — the argument in #157, and a default is the only place it gets expressed.

The Renovate blind spot

The dagger customManager matched dagger-module-version: but not the full-path form (github.com/stuttgart-things/dagger/<mod>@vX.Y.Z inline in a default:). Eleven pins across six workflows were therefore unwatched:

Workflow Pin
call-dagger-security-scan.yaml go@v0.103.0bumped here
call-dagger-trivy-image-scan.yaml trivy@v0.103.0bumped here
call-dagger-ko-build.yaml go@v0.103.0
call-push-kustomize.yaml kcl@v0.114.0
call-go-release.yaml release@v0.82.1, kcl@v0.82.1
call-go-microservice-release.yaml release/go/crane/kcl@v0.82.1

Every watched pin sits at v0.128.0. This is the same drift the manager's own description was written against ("HAD DRIFTED TO FIVE DIFFERENT VERSIONS ACROSS FIVE WORKFLOWS BECAUSE NOTHING WAS WATCHING IT"), one notation further along. One matchString covers all of them independently of the input name, and matches only literal pins — never the @${{ inputs.… }} form.

Expect a Renovate PR straight after this merges that lifts the remaining pins. The v0.82.1 ones are a 46-release jump and want looking at on their own, which is why they are not folded in here.

Verified, and not

  • pre-commit run over all four changed files: passes, including check-github-workflows schema validation.
  • The new matchString was run against every workflow file — 11 matches, all real pins, no false positives.
  • dagger-version: 0.21.9engineVersion: v0.21.8 from go/dagger.json.
  • Not executed. No Dagger run happened here, so the template is verified against the module's source and signature, not against a green job. First real run will be schmetterpause's.

One knock-on for #85

no-cache is exposed because the vulnerability database changes independently of the source, so a cached hit can report a clean tree against a weeks-old database. It sets a timestamp cache-buster in the module, which means with no-cache: true the gate pass misses the report pass's cache and scans twice over. Suited to a schedule: run, not to every pull request — hence the default false.

And a note for the pinning decision #85 leaves open: #86 argued @main was acceptable for the Trivy call because it is report-only and holds no credentials. That argument does not carry over here — with fail-on-finding: true an upstream change to this workflow can turn a consumer's build red. This is the first call site where a tag instead of @main actually earns its keep.

🤖 Generated with Claude Code

https://claude.ai/code/session_01DGshyN1b9amKkrRRUubhbM

…lind spot

Closes #157.

The go module gained Govulncheck in stuttgart-things/dagger v0.128.0
(#342). This is the other half: a Dagger function nobody can call from a
workflow gets used once.

Two calls rather than one. The module's own doc comment explains why:
with --fail-on-finding it returns an error, so a `govulncheck ... export
--path` never reaches the export and the report goes missing exactly when
somebody wants it. The report pass writes the file, the artifact upload
sits between them, and the gate pass decides the build. The second call
hits the cache from the first and costs nothing.

The gate pass is pinned to --format text regardless of the report format,
because govulncheck sets exit 3 on a finding only in text mode -- json,
sarif and openvex exit 0 and expect the caller to parse. The module
handles that internally with a second text pass; asking for it directly
is cheaper.

fail-on-finding defaults to true, unlike the gosec template's report-only
posture. Reachability analysis is what earns that: govulncheck stays quiet
about a CVE whose affected function is never called, so a finding means
something is genuinely exposed.

Also fixes a Renovate blind spot the new template would have inherited.
The dagger customManager matched `dagger-module-version:` but not the
full-path form (github.com/stuttgart-things/dagger/<mod>@vX.Y.Z inline in
a default), so eleven pins across six workflows were unwatched and had
drifted to v0.103.0, v0.114.0 and v0.82.1 while every watched pin sat at
v0.128.0 -- the same drift the manager was written against, one notation
further. One matchString covers them independently of the input name.

The two scan templates are bumped to v0.128.0 here so the family is
consistent. The rest (call-dagger-ko-build, call-push-kustomize,
call-go-release, call-go-microservice-release) are left to Renovate now
that it can see them; the v0.82.1 pins are a 46-release jump that wants
looking at on its own, not folded into this change.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DGshyN1b9amKkrRRUubhbM
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add call-dagger-govulncheck.yaml, alongside the gosec one

1 participant