feat(security): govulncheck standing lane + pin every CI scanner (age-govulncheck-standing-lane-eg2n, age-pin-ci-scanners-7u00)

Sweep 2026-07-09-claude M-2 + M-3, the arc that turns the GO-2026-4970
catch into a standing gate:

eg2n: run_govulncheck in scripts/toolchain-validate.sh (mirrors run_gosec:
gate-scope skip, per-module run, exit 0/3 = ran, anything else = loud tool
error). Every reachable finding counts SECURITY HIGH so the gate BLOCKs -
govulncheck's callgraph mode means a finding is a known CVE on a live path.
Pinned govulncheck@v1.6.0 installed in validate.yml security job + nightly
scanner suite; nightly is the standing lane that catches CVEs published
AFTER code lands. Real-data measurement: go1.26.3 scanned 2 hits (sweep);
landed go1.26.5 scans clean (No vulnerabilities found, rc 0).

7u00: every scanner the gate trusts is now pinned - gosec@v2.27.1,
gitleaks@v8.30.1, semgrep==1.169.0, ruff==0.15.21, radon==6.0.1,
pytest==9.1.1, hadolint v2.14.0, yq v4.53.3 (2 sites), and validate.yml's
trivy no longer pipes the MUTABLE main-branch install script (pinned
v0.72.0 download-then-execute, mirroring nightly). release.yml gosec/
gitleaks/semgrep pinned too. Zero @latest / releases/latest remain in
.github/workflows/.

Claude-Session: https://claude.ai/code/session_01BkgdP88SiccCSyYdi8GHNa
This commit is contained in:
boshu
2026-07-10 14:58:29 -04:00
parent 8f85897b3c
commit 297dd022b5
4 changed files with 110 additions and 15 deletions
+10 -4
View File
@@ -124,14 +124,20 @@ jobs:
- name: Install scanner tools
run: |
python -m pip install --upgrade pip
python -m pip install semgrep ruff radon pytest
python -m pip install semgrep==1.169.0 ruff==0.15.21 radon==6.0.1 pytest==9.1.1
GOBIN=/usr/local/bin go install github.com/securego/gosec/v2/cmd/gosec@latest
GOBIN=/usr/local/bin go install github.com/zricethezav/gitleaks/v8@latest
GOBIN=/usr/local/bin go install github.com/securego/gosec/v2/cmd/gosec@v2.27.1
GOBIN=/usr/local/bin go install github.com/zricethezav/gitleaks/v8@v8.30.1
# golangci-lint
GOBIN=/usr/local/bin go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.11.4
# govulncheck — known-CVE reachability over the module graph + stdlib
# (sweep 2026-07-09 M-2: the blind spot that let GO-2026-4970 sit a week).
# The nightly run is the STANDING lane: it catches CVEs published after
# the code landed, which no diff-triggered gate can.
GOBIN=/usr/local/bin go install golang.org/x/vuln/cmd/govulncheck@v1.6.0
# trivy — pinned tag, download-then-execute. Piping a mutable-branch
# install script into sh was the semgrep gha-curl-pipe-shell CRITICAL
# that blocked the v3.2.0 publisher (supply-chain: a hijacked script
@@ -140,7 +146,7 @@ jobs:
sh /tmp/trivy-install.sh -b /usr/local/bin v0.72.0
# hadolint
curl -sSfL -o /usr/local/bin/hadolint "https://github.com/hadolint/hadolint/releases/latest/download/hadolint-Linux-x86_64"
curl -sSfL -o /usr/local/bin/hadolint "https://github.com/hadolint/hadolint/releases/download/v2.14.0/hadolint-Linux-x86_64"
chmod +x /usr/local/bin/hadolint
- name: Run security gate (full)
+3 -3
View File
@@ -55,9 +55,9 @@ jobs:
- name: Install scanner tools
run: |
python -m pip install --upgrade pip
python -m pip install semgrep
GOBIN=/usr/local/bin go install github.com/securego/gosec/v2/cmd/gosec@latest
GOBIN=/usr/local/bin go install github.com/zricethezav/gitleaks/v8@latest
python -m pip install semgrep==1.169.0
GOBIN=/usr/local/bin go install github.com/securego/gosec/v2/cmd/gosec@v2.27.1
GOBIN=/usr/local/bin go install github.com/zricethezav/gitleaks/v8@v8.30.1
- name: Generate pre-publish release evidence
run: |
+10 -7
View File
@@ -206,7 +206,7 @@ jobs:
run: |
sudo apt-get update
sudo apt-get install -y jq shellcheck
sudo wget -qO /usr/local/bin/yq https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64
sudo wget -qO /usr/local/bin/yq https://github.com/mikefarah/yq/releases/download/v4.53.3/yq_linux_amd64
sudo chmod +x /usr/local/bin/yq
pip install jsonschema pyyaml
@@ -297,7 +297,7 @@ jobs:
- name: Install yq (skill-schema)
if: needs.changes.outputs.skills == 'true' || needs.changes.outputs.ci == 'true'
run: |
sudo wget -qO /usr/local/bin/yq https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64
sudo wget -qO /usr/local/bin/yq https://github.com/mikefarah/yq/releases/download/v4.53.3/yq_linux_amd64
sudo chmod +x /usr/local/bin/yq
- name: Install jq
@@ -810,12 +810,15 @@ jobs:
done
}
retry python -m pip install --upgrade pip
retry python -m pip install semgrep ruff radon
retry env GOBIN=/usr/local/bin go install github.com/securego/gosec/v2/cmd/gosec@latest
retry env GOBIN=/usr/local/bin go install github.com/zricethezav/gitleaks/v8@latest
retry python -m pip install semgrep==1.169.0 ruff==0.15.21 radon==6.0.1
retry env GOBIN=/usr/local/bin go install github.com/securego/gosec/v2/cmd/gosec@v2.27.1
retry env GOBIN=/usr/local/bin go install github.com/zricethezav/gitleaks/v8@v8.30.1
retry env GOBIN=/usr/local/bin go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.11.4
retry bash -c 'curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin'
retry bash -c 'curl -sL https://github.com/hadolint/hadolint/releases/latest/download/hadolint-Linux-x86_64 -o /tmp/hadolint && chmod +x /tmp/hadolint && sudo mv /tmp/hadolint /usr/local/bin/hadolint'
retry env GOBIN=/usr/local/bin go install golang.org/x/vuln/cmd/govulncheck@v1.6.0
# trivy — pinned tag, download-then-execute (mirrors nightly.yml; piping a
# mutable-branch script into sh is the gha-curl-pipe-shell supply-chain class).
retry bash -c 'curl -sSfL -o /tmp/trivy-install.sh https://raw.githubusercontent.com/aquasecurity/trivy/v0.72.0/contrib/install.sh && sh /tmp/trivy-install.sh -b /usr/local/bin v0.72.0'
retry bash -c 'curl -sL https://github.com/hadolint/hadolint/releases/download/v2.14.0/hadolint-Linux-x86_64 -o /tmp/hadolint && chmod +x /tmp/hadolint && sudo mv /tmp/hadolint /usr/local/bin/hadolint'
- name: Run security toolchain gate
if: needs.changes.outputs.go == 'true' || needs.changes.outputs.shell == 'true' || needs.changes.outputs.ci == 'true'
+87 -1
View File
@@ -830,6 +830,91 @@ run_gosec() {
fi
}
# ============================================================================
# TOOL: govulncheck (known-CVE reachability: module graph + Go stdlib)
# ============================================================================
# Sweep 2026-07-09-claude M-2 (age-govulncheck-standing-lane-eg2n): gosec/
# semgrep/gitleaks cover own-code classes; NOTHING checked the module graph or
# the pinned toolchain's stdlib against the vulnerability database — exactly
# why GO-2026-4970 (an os.Root escape, this repo's containment primitive) sat
# undetected for a week after a clean scan. Every reachable finding counts as
# SECURITY HIGH: govulncheck's default callgraph mode only reports vulns whose
# code paths are actually reachable, so a finding here is a known CVE on a live
# path — the gate must BLOCK, not warn.
run_govulncheck() {
local output_file="$OUTPUT_DIR/govulncheck.txt"
local stderr_file="$OUTPUT_DIR/govulncheck.stderr.txt"
if [[ "$GATE" == "true" ]] && ! target_has_any_ext go mod sum; then
echo "NO_GO_CHANGES_IN_TARGET" > "$output_file"
TOOL_STATUS["govulncheck"]="skipped"
TOOLS_SKIPPED=$((TOOLS_SKIPPED + 1))
return 0
fi
if ! run_tool "govulncheck" govulncheck; then return 0; fi
local modules
modules="$(discover_go_modules)"
if [[ -z "$modules" ]]; then
echo "NO_GO_MODULES" > "$output_file"
TOOL_STATUS["govulncheck"]="skipped"
TOOLS_SKIPPED=$((TOOLS_SKIPPED + 1))
return 0
fi
: > "$stderr_file"
: > "$output_file"
local vuln_count=0
while IFS= read -r module_dir; do
[[ -z "$module_dir" ]] && continue
local module_out module_stderr module_rc
module_out="$(mktemp)"
module_stderr="$(mktemp)"
echo "== govulncheck: $module_dir ==" >> "$output_file"
module_rc=0
(cd "$module_dir" && govulncheck ./... > "$module_out" 2> "$module_stderr") || module_rc=$?
cat "$module_out" >> "$output_file"
echo "" >> "$output_file"
# Exit 0 = clean, 3 = vulns found (both are successful RUNS); anything
# else (network/vulndb/toolchain failure) is a tool error — fail loud,
# never silently green.
if [[ "$module_rc" -ne 0 && "$module_rc" -ne 3 ]]; then
{
echo "ERROR: govulncheck exited $module_rc in $module_dir"
[[ -s "$module_stderr" ]] && cat "$module_stderr"
} >> "$stderr_file"
else
local module_vulns
module_vulns=$(grep -c '^Vulnerability #' "$module_out" 2>/dev/null || true)
module_vulns=${module_vulns:-0}
module_vulns=$(echo "$module_vulns" | tr -d '[:space:]')
vuln_count=$((vuln_count + module_vulns))
fi
rm -f "$module_out" "$module_stderr"
done <<< "$modules"
if [[ -s "$stderr_file" ]]; then
TOOL_STATUS["govulncheck"]="error"
cat "$stderr_file" >> "$output_file"
return 0
fi
if [[ "$vuln_count" -gt 0 ]]; then
HIGH_COUNT=$((HIGH_COUNT + vuln_count))
SECURITY_HIGH_COUNT=$((SECURITY_HIGH_COUNT + vuln_count))
TOOL_STATUS["govulncheck"]="findings"
else
TOOL_STATUS["govulncheck"]="pass"
fi
}
# ============================================================================
# TOOL: hadolint (Dockerfile)
# ============================================================================
@@ -889,6 +974,7 @@ run_radon
run_semgrep
run_trivy
run_gosec
run_govulncheck
run_hadolint
run_pytest
run_gotest
@@ -909,7 +995,7 @@ fi
# Build tools JSON object
TOOLS_JSON="{"
first=true
for tool in ruff golangci-lint gitleaks shellcheck radon semgrep trivy gosec hadolint pytest go-test; do
for tool in ruff golangci-lint gitleaks shellcheck radon semgrep trivy gosec govulncheck hadolint pytest go-test; do
status="${TOOL_STATUS[$tool]:-not_run}"
if [[ "$first" == "true" ]]; then
first=false