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
82 changes: 82 additions & 0 deletions .github/workflows/apps/govulncheck-contribs-sarif.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
#!/bin/bash
set -euo pipefail

# Scans each contrib module with govulncheck in SARIF format and merges the
# results into a single SARIF file for upload to GitHub Code Scanning.
#
# Usage: govulncheck-contribs-sarif.sh [output-file]
# output-file Path for the merged SARIF output (default: govulncheck-contribs.sarif)
#
# Requires: govulncheck, jq

OUTPUT="${1:-govulncheck-contribs.sarif}"
SARIF_DIR=$(mktemp -d)
trap 'rm -rf "$SARIF_DIR"' EXIT

count=0
# Use go.work as the authoritative module list — avoids picking up nested
# test-only go.mod files (e.g. contrib/aws/datadog-lambda-go/test/...) that
# are not workspace members and cause govulncheck package-load errors.
grep -E '^\s+\./contrib/' go.work | awk '{print $1}' | while read -r dir; do
echo "Scanning $dir"
# Capture the module path (repo-root-relative) before any fallback rewrites
# $dir — used as the URI prefix when rewriting SARIF paths below.
module_dir="${dir#./}" # strip leading "./" → "contrib/aws/aws-sdk-go"

# govulncheck requires at least one .go file in the target directory;
# fall back to the first subdirectory when the module root has none.
go_files=$(find "$dir" -maxdepth 1 -type f -name '*.go' | wc -l)
[[ $go_files -eq 0 ]] && dir=$(realpath "$(ls -d "$dir"/*/ | head -1)")

safe_name=$(printf '%s' "$module_dir" | tr '/' '_')
# -format sarif exits 0 even when vulnerabilities are found.
govulncheck -format sarif -C "$dir" . >"$SARIF_DIR/${safe_name}.sarif"
Comment thread
kakkoyun marked this conversation as resolved.

# Rewrite URIs to be repo-root-relative so Code Scanning annotations resolve
# correctly. govulncheck emits paths relative to the module dir with
# uriBaseId="%SRCROOT%". Prefix each with the module dir and drop uriBaseId
# so the merged single-run file has unambiguous repo-root-relative paths.
jq --arg prefix "${module_dir}/" '
walk(
if type == "object" and (.uriBaseId? == "%SRCROOT%") and has("uri")
then .uri = ($prefix + .uri) | del(.uriBaseId)
else .
end
)
' "$SARIF_DIR/${safe_name}.sarif" >"$SARIF_DIR/${safe_name}.tmp"
mv "$SARIF_DIR/${safe_name}.tmp" "$SARIF_DIR/${safe_name}.sarif"

count=$((count + 1))
done

sarif_files=("$SARIF_DIR"/*.sarif)
if [[ ! -e "${sarif_files[0]}" ]]; then
echo "No contrib modules found; skipping SARIF merge."
exit 0
fi

# Merge all per-module SARIF files into one file with a single run.
# CodeQL upload-sarif rejects files with multiple runs under the same category
# (https://github.blog/changelog/2025-07-21-code-scanning-will-stop-combining-multiple-sarif-runs-uploaded-in-the-same-sarif-file/).
# govulncheck uses URI-based artifact locations (not index-based), so merging
# results across runs is safe — no artifact re-indexing required.
#
# tool.driver.rules is merged and deduplicated across all runs: each run only
# carries the rules referenced by its own results, so a naïve first-wins merge
# would lose rule descriptors (title, help) for vulns found in later modules.
jq -s '
. as $all |
{
"version": .[0].version,
"$schema": (.[0]."$schema" // ""),
"runs": [{
"tool": (
.[0].runs[0].tool |
.driver.rules = ([$all[].runs[].tool.driver.rules[]?] | unique_by(.id))
),
"results": [$all[].runs[].results[]?]
}]
}
' "$SARIF_DIR"/*.sarif >"$OUTPUT"

echo "Merged $(echo "$SARIF_DIR"/*.sarif | wc -w) SARIF files into $OUTPUT"
3 changes: 2 additions & 1 deletion .github/workflows/apps/govulncheck-contribs-v2.sh
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#!/bin/bash

find ./contrib -mindepth 2 -type f -name go.mod -exec dirname {} \; | while read dir ; do
# Use go.work as the authoritative module list — avoids nested test go.mod files.
grep -E '^\s+\./contrib/' go.work | awk '{print $1}' | while read -r dir; do
echo "Checking $dir"
# govulncheck doesn't support modules with only a go.mod in the root
go_files=$(find $dir -maxdepth 1 -type f -name '*.go' | wc -l)
Expand Down
89 changes: 72 additions & 17 deletions .github/workflows/govulncheck.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ on:
- "go.work.sum"
- ".github/workflows/govulncheck.yml"
- ".github/workflows/apps/govulncheck-contribs-v2.sh"
- ".github/workflows/apps/govulncheck-contribs-sarif.sh"
workflow_call: # allows to reuse this workflow
inputs:
ref:
Expand Down Expand Up @@ -40,38 +41,92 @@ permissions:
jobs:
# Non-blocking: generates SARIF and uploads to GitHub Code Scanning.
# Replaces Dependabot Security Alerts with reachability-aware findings.
# Uses govulncheck@latest installed directly (golang/govulncheck-action is
# not in the DataDog enterprise action allowlist).
# NOTE: Only core packages are scanned here. Contrib modules (each with
# their own go.mod) are scanned in govulncheck-tests, which blocks merges,
# but contrib vulnerabilities do not appear in GitHub Code Scanning alerts.
# Uses the official golang/govulncheck-action — see PR description for
# the enterprise allowlist request context.
# NOTE: Only core packages are scanned here. Contrib modules are scanned
# separately in govulncheck-contribs-analysis (SARIF, non-blocking) and
# in govulncheck-tests (blocking, sandboxed).
govulncheck-analysis:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ref: ${{ inputs.ref || github.event.pull_request.head.sha || github.ref }}
persist-credentials: false
- name: Setup Go
uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5.6.0
with:
go-version: stable
cache-dependency-path: '**/go.sum'
- name: Install govulncheck
run: go install golang.org/x/vuln/cmd/govulncheck@latest
- name: Run govulncheck (SARIF)
- name: Run govulncheck
# golang/govulncheck-action is the official Go Security Team action.
# It handles Go setup internally and always uses the latest govulncheck.
# -format sarif exits 0 even when vulnerabilities are found, so the
# upload step always runs. The blocking check is in govulncheck-tests.
run: |-
govulncheck -format sarif \
./ddtrace/... ./appsec/... ./profiler/... ./internal/... ./instrumentation/... \
> govulncheck.sarif || true
uses: golang/govulncheck-action@31f7c5463448f83528bd771c2d978d940080c9fd # master @ 2026-02-26
with:
output-format: sarif
output-file: govulncheck.sarif
go-version-input: stable
Comment thread
kakkoyun marked this conversation as resolved.
# Disable the action's implicit checkout — we already checked out the
# correct ref above with persist-credentials: false. Without this, the
# action re-runs actions/checkout with default credentials and the
# event ref, which can diverge from inputs.ref on workflow_call.
repo-checkout: false
- name: Upload SARIF to GitHub Code Scanning
if: always()
uses: github/codeql-action/upload-sarif@38697555549f1db7851b81482ff19f1fa5c4fedc # v4.34.1
with:
sarif_file: govulncheck.sarif
category: govulncheck
- name: Run govulncheck (OpenVEX)
if: always()
run: |-
govulncheck -format openvex \
./ddtrace/... ./appsec/... ./profiler/... ./internal/... ./instrumentation/... \
> govulncheck-raw.vex || true
- name: Patch OpenVEX author and product
if: always() && hashFiles('govulncheck-raw.vex') != ''
run: |-
MODULE_PATH=$(go list -m -f '{{ .Path }}' github.com/DataDog/dd-trace-go/v2)
COMMIT=$(git rev-parse HEAD)
VERSION=$(GOPROXY=direct GONOSUMDB="${MODULE_PATH}" go list -m -json "${MODULE_PATH}@${COMMIT}" | jq -r .Version)
PRODUCT="pkg:golang/${MODULE_PATH}@${VERSION}"
jq --arg author "security@datadoghq.com" \
--arg product "${PRODUCT}" \
'.author = $author | (.statements[]?.products[]?["@id"]) = $product' \
govulncheck-raw.vex > govulncheck.vex
- name: Upload OpenVEX artifact
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: govulncheck-openvex
path: govulncheck.vex
if-no-files-found: warn

Comment thread
kakkoyun marked this conversation as resolved.
# Non-blocking: scans all contrib modules for vulnerabilities and uploads
# the merged SARIF to GitHub Code Scanning for Security tab visibility.
# Contrib vulns are also caught by govulncheck-tests (blocking, sandboxed).
govulncheck-contribs-analysis:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ref: ${{ inputs.ref || github.event.pull_request.head.sha || github.ref }}
persist-credentials: false
- name: Setup Go
uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5.6.0
with:
go-version: stable
cache-dependency-path: '**/go.sum'
- name: Run govulncheck on contrib modules (SARIF)
# Installs govulncheck and runs govulncheck-contribs-sarif.sh, which
# scans each contrib module and merges results into one SARIF file.
# -format sarif exits 0 even when vulnerabilities are found.
run: |
go install golang.org/x/vuln/cmd/govulncheck@latest
./.github/workflows/apps/govulncheck-contribs-sarif.sh govulncheck-contribs.sarif
- name: Upload contrib SARIF to GitHub Code Scanning
if: always()
uses: github/codeql-action/upload-sarif@38697555549f1db7851b81482ff19f1fa5c4fedc # v4.34.1
with:
sarif_file: govulncheck-contribs.sarif
category: govulncheck-contribs

# Blocking: fails the build if any reachable vulnerability is found.
# Scans both core packages and all contrib modules (each with its own go.mod).
Expand Down
Loading