@@ -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