Skip to content
Merged
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
48 changes: 40 additions & 8 deletions .github/workflows/security-scan.yml
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,8 @@ jobs:

- name: Check for vulnerabilities (with exclusions)
run: |
set -euo pipefail

# Ignored vulnerabilities with justification:
# Go stdlib advisories published 2026-06-02, all fixed in go1.26.4 /
# go1.25.11 (DoS / log-injection class, no RCE):
Expand All @@ -123,21 +125,51 @@ jobs:
#
# GO-2026-5932: golang.org/x/crypto/openpgp is deprecated-by-design
# ("unsafe, not maintained, should not be used"). No fixed version
# exists and none is planned. ToolHive does not import openpgp
# directly; it is pulled transitively via sigstore (rekor/sigstore-go)
# for backward-compatible OpenPGP verification. Remove when sigstore
# drops the openpgp dependency.
# exists and none is planned.
#
# ToolHive does not import openpgp. It arrives four levels up:
# pkg/skills/signer -> sigstore-go/pkg/sign -> rekor/pkg/pki
# -> rekor/pkg/pki/pgp -> golang.org/x/crypto/openpgp
# rekor/pkg/pki is a pluggable signature-format registry, so
# importing it links every format, PGP included. Every govulncheck
# trace is package-init reachability, not a call: the code is
# linked but never parses PGP data on any ToolHive path.
#
# REMOVAL TRIGGER: rekor migrated to ProtonMail/go-crypto/openpgp
# in sigstore/rekor#2883 (merged 2026-07-15), which landed after
# v1.5.3 (2026-07-02) and so is not in any release yet. Bump rekor
# past v1.5.3 once a release contains it, then drop this entry.
IGNORED_VULNS="GO-2026-5037 GO-2026-5038 GO-2026-5039 GO-2026-5932"

# Show the raw output for debugging
# A scan that produced nothing is a failed scan, not a clean one.
# Without this, a crashed or truncated run yields an empty finding
# list indistinguishable from "no vulnerabilities" and the gate
# goes green.
if [ ! -s govulncheck-output.json ]; then
echo "::error::govulncheck produced no output; refusing to report success"
exit 1
fi

echo "::group::govulncheck raw output"
cat govulncheck-output.json
echo "::endgroup::"

# Extract vulnerability IDs that have actual findings (called symbols)
# The JSON has "finding" objects with "osv" field only for vulnerabilities
# where vulnerable code paths are actually called
FOUND_VULNS=$(jq -r 'select(.finding != null) | .finding.osv' govulncheck-output.json | sort -u | grep -E '^GO-' || true)
# Extract vulnerability IDs that have actual findings (called symbols).
# The JSON has "finding" objects with an "osv" field only for
# vulnerabilities whose vulnerable code paths are actually called.
#
# jq failures must be fatal. A trailing `|| true` on the pipeline
# would swallow a parse error and hand us an empty list, which is
# how malformed output could pass the gate.
if ! jq -r 'select(.finding != null) | .finding.osv' govulncheck-output.json > found-osv.txt; then
echo "::error::could not parse govulncheck output; treating as a failed scan"
exit 1
fi

# grep exiting 1 means "no matches", which is legitimately clean —
# that is the only non-zero status tolerated here.
FOUND_VULNS=$(sort -u found-osv.txt | { grep -E '^GO-' || true; })

if [ -z "$FOUND_VULNS" ]; then
echo "✅ No vulnerabilities found"
Expand Down
Loading