Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/actions/go-cache/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ runs:
id: go
with:
go-version: ${{ inputs.go-version }}
# Resolve '1.26' to the newest 1.26.x patch instead of the runner's
# preinstalled one, so stdlib fixes (spec 013 G1, govulncheck) land
# without a manual pin bump.
check-latest: true
cache: false
- name: Go dependency cache
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
Expand Down
30 changes: 29 additions & 1 deletion .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,9 @@ jobs:
- '**/go.mod'
- '**/go.sum'
- '**/Dockerfile'
- 'hack/govulncheck-all.sh'
- 'hack/check-govulncheck_test.sh'
- 'hack/testdata/govulncheck/**'
web:
- 'web/**'
charts:
Expand Down Expand Up @@ -593,6 +596,29 @@ jobs:
fail-on-severity: high
comment-summary-in-pr: on-failure

# Spec 013 G1: fail on any reachable Go vulnerability in any go.work module.
# The proof step first shows the gate goes red on a synthetic reachable
# advisory (hack/testdata/govulncheck). Fix findings by bumping, never excluding.
vuln-go:
name: vuln (go)
needs: [changes]
if: needs.changes.outputs.go == 'true' || github.event_name == 'push'
runs-on: ubuntu-latest
timeout-minutes: 30
permissions:
contents: read
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/go-cache
with:
key-suffix: vuln-go
- name: install govulncheck
run: go install golang.org/x/vuln/cmd/govulncheck@v1.9.0
- name: prove the gate fails on a reachable vulnerable symbol
run: ./hack/check-govulncheck_test.sh
- name: govulncheck every go.work module
run: ./hack/govulncheck-all.sh

lint:
name: lint (${{ matrix.module }})
needs: [changes]
Expand Down Expand Up @@ -2054,6 +2080,7 @@ jobs:
codegen-drift,
submodule-freshness,
dependency-review,
vuln-go,
lint,
go,
web,
Expand Down Expand Up @@ -2165,7 +2192,7 @@ jobs:

const NEEDS_ORDER = [
'changes', 'build-images', 'build-images-arm64',
'capture-sidecar-setcap-proof', 'docs', 'codegen-drift', 'submodule-freshness', 'dependency-review', 'lint', 'go',
'capture-sidecar-setcap-proof', 'docs', 'codegen-drift', 'submodule-freshness', 'dependency-review', 'vuln-go', 'lint', 'go',
'web', 'web-e2e-mock', 'helm', 'chart-template', 'workflow-lint', 'go-e2e-unit',
'api-postgres',
'e2e-buckets', 'e2e-go', 'e2e-go-arm64', 'e2e-multicluster', 'e2e-multicluster-arm64', 'e2e-upgrade', 'e2e-upgrade-arm64',
Expand Down Expand Up @@ -2205,6 +2232,7 @@ jobs:
'e2e-kind-ingress-smoke': (n) => n === 'ingress-nginx smoke (kind)',
'submodule-freshness': (n) => n === 'submodule freshness',
'dependency-review': (n) => n === 'dependency review',
'vuln-go': (n) => n === 'vuln (go)',
};

let passed = 0, failed = 0, skipped = 0, cancelled = 0;
Expand Down
33 changes: 33 additions & 0 deletions .github/workflows/vuln-weekly.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Spec 013 G1: catches advisories published after a merge; scans master weekly.
# Runs govulncheck over every go.work module on the master branch (same gate as the vuln (go) CI job).
name: vuln weekly

on:
schedule:
- cron: "0 4 * * 1"
workflow_dispatch:

permissions:
contents: read

concurrency:
group: vuln-weekly
cancel-in-progress: false

jobs:
vuln-go:
name: vuln (go) weekly
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: master
persist-credentials: false
- uses: ./.github/actions/go-cache
with:
key-suffix: vuln-weekly
- name: install govulncheck
run: go install golang.org/x/vuln/cmd/govulncheck@v1.9.0
- name: govulncheck every go.work module
run: ./hack/govulncheck-all.sh
2 changes: 1 addition & 1 deletion go.work.sum
Original file line number Diff line number Diff line change
Expand Up @@ -332,7 +332,6 @@ github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2
github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1/go.mod h1:lXGCsh6c22WGtjr+qGHj1otzZpV/1kwTMAqkwZsnWRU=
github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.1.0/go.mod h1:hM2alZsMUni80N33RBe6J0e423LB+odMj7d3EMP9l20=
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.0/go.mod h1:qOchhhIlmRcqk/O9uCo/puJlyo07YINaIqdZfZG3Jkc=
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.3/go.mod h1:NbCUVmiS4foBGBHOYlCT25+YmGpJ32dZPi75pGEUpj4=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI=
Expand Down Expand Up @@ -492,6 +491,7 @@ github.com/santhosh-tekuri/jsonschema/v5 v5.3.1/go.mod h1:uToXkOrWAZ6/Oc07xWQrPO
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU=
github.com/sashamelentyev/interfacebloat v1.1.0/go.mod h1:+Y9yU5YdTkrNvoX0xHc84dxiN1iBi9+G8zZIhPVoNjQ=
github.com/sashamelentyev/usestdlibvars v1.29.0/go.mod h1:8PpnjHMk5VdeWlVb4wCdrB8PNbLqZ3wBZTZWkrpZZL8=
github.com/sassoftware/relic v7.2.1+incompatible h1:Pwyh1F3I0r4clFJXkSI8bOyJINGqpgjJU3DYAZeI05A=
github.com/securego/gosec/v2 v2.26.1/go.mod h1:57UW4p0uoP3kxoTkhoo3axLdVAi+OWrLg/Ax/kdqtPE=
github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys=
github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
Expand Down
55 changes: 55 additions & 0 deletions hack/check-govulncheck_test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#!/usr/bin/env bash
# Proves the "vuln (go)" gate fails on a reachable vulnerable symbol (spec 013, G1).
#
# Fixtures under hack/testdata/govulncheck/ have no replace directive: govulncheck
# ignores modules whose version is "", so the proof vendors the fixture module
# into a temp copy of each case and gives it a real v1.0.0 version. Nothing is
# fetched (GOPROXY=off) and vendor/ is never committed (it is gitignored).
set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$SCRIPT_DIR/.."

FIXTURES="$PWD/hack/testdata/govulncheck"
DB="file://$FIXTURES/db"
tmp="$(mktemp -d)"
trap 'rm -rf "$tmp"' EXIT

export GOWORK=off GOFLAGS=-mod=vendor GOPROXY=off

stage() {
local case="$1"
cp -R "$FIXTURES/$case" "$tmp/$case"
mkdir -p "$tmp/$case/vendor/example.invalid/vulnfixture"
cp "$FIXTURES/vulnfixture/"*.go "$tmp/$case/vendor/example.invalid/vulnfixture/"
cat > "$tmp/$case/vendor/modules.txt" <<'EOF'
# example.invalid/vulnfixture v1.0.0
## explicit; go 1.22
example.invalid/vulnfixture
EOF
}

fail=0

run() {
local case="$1" want="$2" rc
set +e
(cd "$tmp/$case" && govulncheck -db "$DB" ./...)
rc=$?
set -e
if [ "$rc" -eq "$want" ]; then
echo "✓ $case: govulncheck exit $rc (want $want)"
else
echo "✗ $case: govulncheck exit $rc (want $want)"
fail=1
fi
}

for case in fail pass; do
stage "$case"
done

run fail 3
run pass 0

exit "$fail"
94 changes: 94 additions & 0 deletions hack/govulncheck-all.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
#!/usr/bin/env bash
# govulncheck-all.sh - runs govulncheck over every Go module listed in go.work.
#
# Spec 013 G1. Used by the "vuln (go)" CI job and .github/workflows/vuln-weekly.yaml.
#
# Module tags:
# operator, api -> -tags=envtest (api is also scanned once more with -tags=postgres)
# test/e2e -> -tags=e2e
# others -> no tags
#
# Exit codes from govulncheck: 3 = reachable vulnerabilities found, other
# non-zero = tool/build error. Every scan runs; failures are summarised at the end.
set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$SCRIPT_DIR/.."

if ! command -v govulncheck >/dev/null 2>&1; then
echo "error: govulncheck not found in PATH" >&2
exit 1
fi

if [[ ! -f go.work ]]; then
echo "error: go.work not found at repo root" >&2
exit 1
fi

# Module directories: the lines inside the `use ( ... )` block, e.g. "\t./api".
mapfile -t MODULES < <(awk '
/^use[[:space:]]*\(/ { inuse = 1; next }
inuse && /^\)/ { inuse = 0; next }
inuse {
line = $0
gsub(/^[[:space:]]+|[[:space:]]+$/, "", line)
if (line == "" || line ~ /^\/\//) next
sub(/^\.\//, "", line)
print line
}
' go.work)

if [[ ${#MODULES[@]} -eq 0 ]]; then
echo "error: no modules found in go.work use block" >&2
exit 1
fi

# Build the scan list as "dir|tagflag" entries (tagflag may be empty).
SCANS=()
for dir in "${MODULES[@]}"; do
case "$dir" in
operator|api) tagflag="-tags=envtest" ;;
test/e2e) tagflag="-tags=e2e" ;;
*) tagflag="" ;;
esac
SCANS+=("$dir|$tagflag")
if [[ "$dir" == "api" ]]; then
SCANS+=("api|-tags=postgres")
fi
done

FAILED=()
total=0
for entry in "${SCANS[@]}"; do
dir="${entry%%|*}"
tagflag="${entry#*|}"
total=$((total + 1))

echo "== govulncheck $dir ${tagflag:-no tags}"

set +e
(cd "$dir" && govulncheck ${tagflag:+"$tagflag"} ./...)
rc=$?
set -e

if [[ $rc -ne 0 ]]; then
if [[ $rc -eq 3 ]]; then
reason="reachable vulnerabilities"
else
reason="govulncheck error"
fi
FAILED+=("$dir ${tagflag:-no tags} (rc=$rc: $reason)")
fi
done

if [[ ${#FAILED[@]} -gt 0 ]]; then
echo ""
echo "govulncheck non-zero in ${#FAILED[@]} of $total scans:"
for f in "${FAILED[@]}"; do
echo " - $f"
done
exit 1
fi

echo "✓ no reachable vulnerabilities in $total scans"
exit 0
42 changes: 42 additions & 0 deletions hack/testdata/govulncheck/db/ID/GO-0000-0001.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
{
"schema_version": "1.3.1",
"id": "GO-0000-0001",
"modified": "2026-01-01T00:00:00Z",
"published": "2026-01-01T00:00:00Z",
"summary": "Synthetic advisory for the spec 013 govulncheck gate proof",
"details": "Fixture only. vulnfixture.Bad is affected below v1.0.1; vulnfixture.Good is not.",
"affected": [
{
"package": {
"name": "example.invalid/vulnfixture",
"ecosystem": "Go"
},
"ranges": [
{
"type": "SEMVER",
"events": [
{
"introduced": "0"
},
{
"fixed": "1.0.1"
}
]
}
],
"ecosystem_specific": {
"imports": [
{
"path": "example.invalid/vulnfixture",
"symbols": [
"Bad"
]
}
]
}
}
],
"database_specific": {
"url": "https://example.invalid/GO-0000-0001"
}
}
3 changes: 3 additions & 0 deletions hack/testdata/govulncheck/db/index/db.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"modified": "2026-01-01T00:00:00Z"
}
12 changes: 12 additions & 0 deletions hack/testdata/govulncheck/db/index/modules.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[
{
"path": "example.invalid/vulnfixture",
"vulns": [
{
"id": "GO-0000-0001",
"modified": "2026-01-01T00:00:00Z",
"fixed": "1.0.1"
}
]
}
]
7 changes: 7 additions & 0 deletions hack/testdata/govulncheck/db/index/vulns.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[
{
"id": "GO-0000-0001",
"modified": "2026-01-01T00:00:00Z",
"aliases": []
}
]
5 changes: 5 additions & 0 deletions hack/testdata/govulncheck/fail/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module example.invalid/govulncheckfail

go 1.22

require example.invalid/vulnfixture v1.0.0
13 changes: 13 additions & 0 deletions hack/testdata/govulncheck/fail/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Command govulncheckfail calls the vulnerable symbol of vulnfixture, so
// govulncheck must report a reachable vulnerability (exit 3).
package main

import (
"fmt"

"example.invalid/vulnfixture"
)

func main() {
fmt.Println(vulnfixture.Bad())
}
5 changes: 5 additions & 0 deletions hack/testdata/govulncheck/pass/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module example.invalid/govulncheckpass

go 1.22

require example.invalid/vulnfixture v1.0.0
13 changes: 13 additions & 0 deletions hack/testdata/govulncheck/pass/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Command govulncheckpass calls only the unaffected symbol of vulnfixture, so
// govulncheck must report no reachable vulnerability (exit 0).
package main

import (
"fmt"

"example.invalid/vulnfixture"
)

func main() {
fmt.Println(vulnfixture.Good())
}
3 changes: 3 additions & 0 deletions hack/testdata/govulncheck/vulnfixture/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module example.invalid/vulnfixture

go 1.22
10 changes: 10 additions & 0 deletions hack/testdata/govulncheck/vulnfixture/vulnfixture.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// Package vulnfixture is a synthetic module used to prove the govulncheck gate
// (spec 013, G1). It is not published and is never fetched: the proof script
// vendors it at runtime.
package vulnfixture

// Bad returns a value from the vulnerable code path.
func Bad() string { return "bad" }

// Good returns a value from code that is not affected by the advisory.
func Good() string { return "good" }
Loading
Loading