Skip to content

Commit b847cb7

Browse files
authored
Add govulncheck composite action (#18)
Differential govulncheck analysis that scans the current branch and compares against the base branch, failing only on newly introduced vulnerabilities. Pre-existing vulns are reported but don't block. - Auto-detects base SHA on pull_request and merge_group events - Installs govulncheck once, scans both branches, diffs the results - Writes a GitHub job summary with markdown tables linking to pkg.go.dev - Exposes new-count and has-new-vulns outputs for downstream steps - Best-effort base scan with EXIT trap to guarantee HEAD restoration
1 parent a36114c commit b847cb7

3 files changed

Lines changed: 430 additions & 0 deletions

File tree

golang/govulncheck/README.md

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# govulncheck
2+
3+
Differential [govulncheck](https://pkg.go.dev/golang.org/x/vuln/cmd/govulncheck) analysis for Go repositories. Scans the current branch and compares against the base branch, **failing only on newly introduced vulnerabilities**. Pre-existing vulnerabilities are reported but don't block the PR.
4+
5+
## Usage
6+
7+
```yaml
8+
steps:
9+
- uses: actions/checkout@v4
10+
11+
- uses: actions/setup-go@v5
12+
with:
13+
go-version-file: go.mod
14+
15+
- uses: temporalio/public-actions/golang/govulncheck@main
16+
```
17+
18+
That's it. On `pull_request` and `merge_group` events, `base-sha` is automatically detected and the scan is differential. On other events (e.g., `push`), all findings are treated as new.
19+
20+
## Inputs
21+
22+
| Input | Default | Description |
23+
|---|---|---|
24+
| `govulncheck-version` | `v1.1.4` | Version of govulncheck to install |
25+
| `base-sha` | Auto-detected | Base branch SHA for differential comparison. Override to compare against a specific commit. |
26+
27+
## Outputs
28+
29+
| Output | Description |
30+
|---|---|
31+
| `new-count` | Number of newly introduced vulnerabilities |
32+
| `has-new-vulns` | `true` if new vulnerabilities were found, `false` otherwise |
33+
34+
### Using outputs
35+
36+
```yaml
37+
- uses: temporalio/public-actions/golang/govulncheck@main
38+
id: vulncheck
39+
40+
- if: steps.vulncheck.outputs.has-new-vulns == 'true'
41+
run: echo "${{ steps.vulncheck.outputs.new-count }} new vulnerabilities found"
42+
```
43+
44+
## Job summary
45+
46+
The action writes a GitHub job summary with:
47+
- **New vulnerabilities** — not present on the base branch (blocks the PR)
48+
- **Resolved vulnerabilities** — were on the base branch but are now fixed
49+
- **Pre-existing vulnerabilities** — present on both branches (does not block)
50+
51+
Each vulnerability links to [pkg.go.dev/vuln](https://pkg.go.dev/vuln/) with the affected module, current version, and fix version.
52+
53+
## Prerequisites
54+
55+
- Go must be set up before calling this action (e.g., via [actions/setup-go](https://github.com/actions/setup-go))
56+
- `jq` must be available on the runner (pre-installed on GitHub-hosted runners)

golang/govulncheck/action.yml

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
name: 'govulncheck'
2+
description: 'Differential govulncheck analysis — scans the current branch and optionally compares against a base branch, failing only on newly introduced vulnerabilities'
3+
inputs:
4+
govulncheck-version:
5+
description: 'Version of govulncheck to use'
6+
required: false
7+
default: 'v1.1.4'
8+
base-sha:
9+
description: >-
10+
Base branch SHA for differential comparison. Automatically detected on
11+
pull_request and merge_group events. When empty (e.g., on push), all
12+
findings are treated as new. Override to compare against a specific commit.
13+
required: false
14+
default: ${{ github.event.pull_request.base.sha || github.event.merge_group.base_sha }}
15+
outputs:
16+
new-count:
17+
description: 'Number of newly introduced vulnerabilities'
18+
value: ${{ steps.report.outputs.new-count }}
19+
has-new-vulns:
20+
description: 'Whether new vulnerabilities were found (true/false)'
21+
value: ${{ steps.report.outputs.has-new-vulns }}
22+
runs:
23+
using: "composite"
24+
steps:
25+
- name: Install govulncheck
26+
shell: bash
27+
env:
28+
GOVULNCHECK_VERSION: ${{ inputs.govulncheck-version }}
29+
# Override GOTOOLCHAIN so Go can fetch the toolchain govulncheck needs,
30+
# even if the project's go.mod pins an older Go version.
31+
GOTOOLCHAIN: auto
32+
run: go install "golang.org/x/vuln/cmd/govulncheck@${GOVULNCHECK_VERSION}"
33+
34+
- name: Scan current branch
35+
shell: bash
36+
run: |
37+
set +e
38+
govulncheck -json ./... > "${RUNNER_TEMP}/pr-vulns.json" 2>"${RUNNER_TEMP}/pr-vulns.stderr"
39+
exit_code=$?
40+
set -e
41+
# Exit code 3 = vulnerabilities found (expected). Anything else is a real error.
42+
if [ "$exit_code" -ne 0 ] && [ "$exit_code" -ne 3 ]; then
43+
if [ -s "${RUNNER_TEMP}/pr-vulns.stderr" ]; then
44+
echo "::group::govulncheck stderr (current branch)"
45+
cat "${RUNNER_TEMP}/pr-vulns.stderr"
46+
echo "::endgroup::"
47+
fi
48+
exit "$exit_code"
49+
fi
50+
51+
- name: Scan base branch
52+
if: ${{ inputs.base-sha != '' }}
53+
shell: bash
54+
env:
55+
BASE_SHA: ${{ inputs.base-sha }}
56+
run: |
57+
# Best-effort: failure here should not block the report.
58+
# Composite actions don't support continue-on-error, so we handle it inline.
59+
HEAD_SHA=$(git rev-parse HEAD)
60+
# --force: the base branch checkout may leave go.mod/go.sum in a modified state.
61+
restore_head() { git checkout --force --quiet "$HEAD_SHA" 2>/dev/null || true; }
62+
trap restore_head EXIT
63+
64+
if git fetch --depth=1 origin "$BASE_SHA" && git checkout --quiet "$BASE_SHA"; then
65+
set +e
66+
govulncheck -json ./... > "${RUNNER_TEMP}/base-vulns.json" 2>"${RUNNER_TEMP}/base-vulns.stderr"
67+
scan_exit=$?
68+
set -e
69+
if [ "$scan_exit" -ne 0 ] && [ "$scan_exit" -ne 3 ] && [ -s "${RUNNER_TEMP}/base-vulns.stderr" ]; then
70+
echo "::group::govulncheck stderr (base branch)"
71+
cat "${RUNNER_TEMP}/base-vulns.stderr"
72+
echo "::endgroup::"
73+
fi
74+
else
75+
echo "::warning::Failed to checkout base branch — differential comparison will treat all findings as new"
76+
fi
77+
78+
# restore_head runs via the EXIT trap. Always exit 0 — this step is best-effort.
79+
exit 0
80+
81+
- name: Compare and report
82+
id: report
83+
shell: bash
84+
run: |
85+
# Ensure base scan file exists even if the base scan step was skipped or failed.
86+
touch "${RUNNER_TEMP}/base-vulns.json"
87+
"$GITHUB_ACTION_PATH/scripts/govulncheck-report.sh" "${RUNNER_TEMP}/pr-vulns.json" "${RUNNER_TEMP}/base-vulns.json"

0 commit comments

Comments
 (0)