{"uuid": "27445fd5-8c30-44f2-ad08-0059ff5c8164", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2026-42506", "type": "seen", "source": "https://gist.github.com/illoyd/2131d93f5933897a469738feaf75a3e9", "content": "#!/usr/bin/env bash\n#\n# Bulk-triage GitHub code-scanning alerts by rule/CVE id.\n#\n# Dismiss (or reopen) EVERY open alert matching a CVE in one shot, recording a\n# reason + comment. Dismissing keeps the alert tracked \u2014 it leaves the active\n# list but is retained under `is:dismissed`, and when a later scan no longer\n# finds the CVE (e.g. upstream ships the fix and we bump the package) GitHub\n# auto-closes it as \"fixed\". This is preferable to a scan-time `.trivyignore`\n# for upstream / won't-fix CVEs, which would hide the finding entirely and never\n# signal resolution.\n#\n# Matching is by `rule.id`, which for Trivy/Dependabot is the CVE (e.g.\n# CVE-2026-42506) \u2014 so a single run also covers a CVE that fans out across\n# several sibling packages.\n#\n# Usage:\n#   bin/code-scanning-triage   [comment]\n#\n#   action:  wontfix | falsepositive | usedintest | reopen\n#   comment: required when dismissing; recorded on each alert (audited).\n#            Quote it \u2014 it is a single argument.\n#\n# Examples:\n#   bin/code-scanning-triage CVE-2026-42506 wontfix \\\n#     \"Upstream: thruster must bump golang.org/x/net; x/net/html not linked.\"\n#   bin/code-scanning-triage CVE-2026-42506 reopen\n#\n# Env:\n#   DRY_RUN=1   print the alerts that would change, without modifying anything.\n#\n# Requires: gh (authenticated, with security-events/repo write scope) + jq.\nset -euo pipefail\n\ndie() { echo \"error: $*\" &gt;&amp;2; exit 1; }\n\ncve=\"${1:-}\"\naction_raw=\"${2:-}\"\ncomment=\"${3:-}\"\n\n[ -n \"$cve\" ]        || die \"missing CVE/rule id (arg 1), e.g. CVE-2026-42506\"\n[ -n \"$action_raw\" ] || die \"missing action (arg 2): wontfix | falsepositive | usedintest | reopen\"\n[[ \"$cve\" =~ ^[A-Za-z0-9._-]+$ ]] || die \"invalid rule/CVE id: '$cve'\"\n\ncommand -v gh &gt;/dev/null || die \"gh CLI not found\"\ncommand -v jq &gt;/dev/null || die \"jq not found\"\n\n# Normalise the action (lower-case, strip punctuation/spaces) -&gt; GitHub state.\nnorm=\"$(printf '%s' \"$action_raw\" | tr '[:upper:]' '[:lower:]' | tr -cd 'a-z')\"\ncase \"$norm\" in\n  wontfix|wont)          state=dismissed; reason=\"won't fix\";      list_state=open ;;\n  falsepositive|fp)      state=dismissed; reason=\"false positive\"; list_state=open ;;\n  usedintest|test|tests) state=dismissed; reason=\"used in test\";   list_state=open ;;\n  reopen|open)           state=open;      reason=\"\";               list_state=dismissed ;;\n  *) die \"unknown action '$action_raw' (use: wontfix | falsepositive | usedintest | reopen)\" ;;\nesac\n\nif [ \"$state\" = dismissed ] &amp;&amp; [ -z \"$comment\" ]; then\n  die \"a comment is required when dismissing (arg 3) \u2014 quote it\"\nfi\n\nrepo=\"$(gh repo view --json nameWithOwner -q .nameWithOwner)\" || die \"not in a GitHub repo (or gh not authenticated)\"\n\necho \"repo:    $repo\"\necho \"rule:    $cve\"\necho \"action:  $action_raw  -&gt;  state=$state${reason:+, reason=\\\"$reason\\\"}\"\n[ -n \"$comment\" ] &amp;&amp; echo \"comment: $comment\"\necho\n\n# Collect matching alert numbers (filter by rule id client-side).\nraw=\"$(gh api --paginate -X GET \"repos/$repo/code-scanning/alerts\" \\\n        -f state=\"$list_state\" -f per_page=100 \\\n        --jq \".[] | select(.rule.id == \\\"$cve\\\") | .number\")\" \\\n  || die \"failed to query code-scanning alerts (token scope? security-events:write)\"\n\n# shellcheck disable=SC2206  # alert numbers are integers \u2014 word-splitting is safe\nnumbers=( $raw )\ncount=${#numbers[@]}\n\nif [ \"$count\" -eq 0 ]; then\n  echo \"no $list_state alert(s) found for $cve \u2014 nothing to do.\"\n  exit 0\nfi\necho \"matched $count $list_state alert(s): ${numbers[*]}\"\n\nif [ \"${DRY_RUN:-}\" = \"1\" ]; then\n  echo \"DRY_RUN=1 \u2014 no changes made.\"\n  exit 0\nfi\n\nfor n in \"${numbers[@]}\"; do\n  if [ \"$state\" = dismissed ]; then\n    gh api -X PATCH \"repos/$repo/code-scanning/alerts/$n\" \\\n      -f state=dismissed -f dismissed_reason=\"$reason\" -f dismissed_comment=\"$comment\" \\\n      --jq '\"  #\\(.number) -&gt; \\(.state) (\\(.dismissed_reason))\"'\n  else\n    gh api -X PATCH \"repos/$repo/code-scanning/alerts/$n\" \\\n      -f state=open \\\n      --jq '\"  #\\(.number) -&gt; \\(.state)\"'\n  fi\ndone\n\necho \"done: $count alert(s) updated.\"\n\n# Bulk-triage code-scanning alerts by CVE. Dismissing keeps them tracked\n# (filter `is:dismissed`) and auto-resolves to \"fixed\" once a later scan no\n# longer finds the CVE. action: wontfix | falsepositive | usedintest | reopen.\n#   mise run cve-triage CVE-2026-42506 wontfix \"Depends on upstream thruster bump\"\n#   DRY_RUN=1 mise run cve-triage CVE-2026-42506 wontfix \"preview only\"\n[tasks.cve-triage]\ndescription = \"Dismiss/reopen all open code-scanning alerts for a CVE, with a comment\"\nrun = \"bin/code-scanning-triage {{arg(name='cve')}} {{arg(name='action')}} {{arg(name='comment', required=false)}}\"\n", "creation_timestamp": "2026-06-30T01:29:32.450597Z"}