Skip to content

Commit a685d01

Browse files
fix(ci): dedupe vulnerability findings by id within each scanner
Trivy can report the same CVE multiple times (across lockfiles/targets or when a CVE affects several packages). The previous build mapped over every Trivy entry, so duplicate ids produced duplicate findings and therefore duplicate Linear issues. Group Trivy and Dependabot by id so each unique id yields exactly one finding, collecting all affected packages into it. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 70c3952 commit a685d01

1 file changed

Lines changed: 24 additions & 16 deletions

File tree

.github/workflows/vulnerability-triage.yml

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -433,9 +433,11 @@ jobs:
433433
run: |
434434
set -euo pipefail
435435
# Deterministically build the findings list from the three normalized scan
436-
# files. Dedup by the pre-computed `id` field: a CVE/GHSA id present in both
437-
# Trivy and Dependabot is merged into one entry; CodeQL alerts are grouped by
438-
# rule id. Titles and descriptions are templated from the scan fields — no LLM.
436+
# files. Dedup by the pre-computed `id` field so each unique id yields exactly
437+
# one finding: duplicate ids within a single scanner are collapsed, a CVE/GHSA
438+
# id present in both Trivy and Dependabot is merged into one entry, and CodeQL
439+
# alerts are grouped by rule id. Titles and descriptions are templated from the
440+
# scan fields — no LLM.
439441
for f in trivy-alerts.json dependabot-alerts.json codeql-alerts.json; do
440442
[ -f "$f" ] || echo "[]" > "$f"
441443
done
@@ -451,20 +453,24 @@ jobs:
451453
($codeql[0] // []) as $codeql_raw |
452454
($trivy_raw | map(.id)) as $trivy_ids |
453455
454-
# --- Trivy findings (merged with Dependabot when ids collide) ---
455-
($trivy_raw | map(
456-
. as $t |
456+
# --- Trivy findings (deduped by id, merged with Dependabot when ids collide) ---
457+
# Trivy can report the same CVE multiple times (across lockfiles/targets or
458+
# affecting several packages), so group by id and collect every package.
459+
($trivy_raw | group_by(.id) | map(
460+
. as $group |
461+
($group[0]) as $t |
462+
($group | map(.pkg_name) | unique) as $pkgs |
457463
($dep_raw | map(select(.id == $t.id)) | .[0]) as $match |
458464
{
459465
cveId: $t.id,
460466
severity: ($t.severity | sev),
461467
source: (if $match then "trivy+dependabot" else "trivy" end),
462-
title: (if (($t.title // "") != "") then $t.title else (($t.severity | sev) + " vulnerability in " + $t.pkg_name) end),
463-
affectedPackage: $t.pkg_name,
468+
title: (if (($t.title // "") != "") then $t.title else (($t.severity | sev) + " vulnerability in " + ($pkgs | join(", "))) end),
469+
affectedPackage: ($pkgs | join(", ")),
464470
description: (
465471
"**Source:** Trivy container scan" + (if $match then " + Dependabot" else "" end) + "\n\n"
466-
+ "**Package:** `" + $t.pkg_name + "` (installed `" + $t.installed_version + "`"
467-
+ (if (($t.fixed_version // "") != "") then ", fixed in `" + $t.fixed_version + "`" else ", no fix available" end) + ")\n\n"
472+
+ "**Affected package(s):**\n"
473+
+ ($group | map("- `" + .pkg_name + "` (installed `" + .installed_version + "`" + (if ((.fixed_version // "") != "") then ", fixed in `" + .fixed_version + "`" else ", no fix available" end) + ")") | unique | join("\n")) + "\n\n"
468474
+ "**Severity:** " + ($t.severity | sev) + "\n\n"
469475
+ (if (($t.title // "") != "") then "**" + $t.title + "**\n\n" else "" end)
470476
+ (if (($t.description // "") != "") then $t.description + "\n\n" else "" end)
@@ -475,18 +481,20 @@ jobs:
475481
}
476482
)) as $trivy_findings |
477483
478-
# --- Dependabot-only findings (ids not already covered by Trivy) ---
479-
($dep_raw | map(select(.id as $id | ($trivy_ids | index($id)) | not)) | map(
480-
. as $d |
484+
# --- Dependabot-only findings (deduped by id, ids not already covered by Trivy) ---
485+
($dep_raw | group_by(.id) | map(select((.[0].id) as $id | ($trivy_ids | index($id)) | not)) | map(
486+
. as $group |
487+
($group[0]) as $d |
488+
($group | map(.package_name) | unique) as $pkgs |
481489
{
482490
cveId: $d.id,
483491
severity: ($d.severity | sev),
484492
source: "dependabot",
485-
title: (if (($d.summary // "") != "") then $d.summary else (($d.severity | sev) + " vulnerability in " + $d.package_name) end),
486-
affectedPackage: $d.package_name,
493+
title: (if (($d.summary // "") != "") then $d.summary else (($d.severity | sev) + " vulnerability in " + ($pkgs | join(", "))) end),
494+
affectedPackage: ($pkgs | join(", ")),
487495
description: (
488496
"**Source:** Dependabot\n\n"
489-
+ "**Package:** `" + $d.package_name + "` (" + $d.package_ecosystem + ")"
497+
+ "**Package:** `" + ($pkgs | join("`, `")) + "` (" + $d.package_ecosystem + ")"
490498
+ (if (($d.first_patched_version // "") != "") then " — patched in `" + $d.first_patched_version + "`" else " — no patched version available" end) + "\n\n"
491499
+ (if (($d.manifest_path // "") != "") then "**Manifest:** `" + $d.manifest_path + "`\n\n" else "" end)
492500
+ "**Severity:** " + ($d.severity | sev) + "\n\n"

0 commit comments

Comments
 (0)