Compare commits

...
Author SHA1 Message Date
KaifAhmad1 7b8147d6eb fix(ci): apply the accepted-CVE exclusion list to the PR comment too
Qodo caught a real gap on this PR: the jq-based exclusion I added only
covers the CI gate (the VULNS count and the failure-path detail print).
The "Comment PR with Security Results" step reads safety-report.json
independently in its own JS, with no filtering at all, so a PR touching
only the accepted cuda-toolkit CVE would still get a comment saying
"Found 1" even though the gate itself correctly treats it as
non-actionable and passes.

Export IGNORED_VULN_IDS via $GITHUB_ENV from the shell step so the JS
step can read the same list, and filter data.vulnerabilities there
before rendering - with a footnote naming what was excluded and why,
so the comment stays transparent about the accepted finding rather than
just silently hiding it.

Verified the JS logic standalone against two synthetic reports: one with
the accepted CVE plus an unrelated real one (shows only the real one,
plus the footnote), and one with only the accepted CVE (shows "No
findings" plus the footnote, rather than misleadingly looking identical
to a clean scan with no explanation).
2026-09-01 19:35:20 +05:30
KaifAhmad1 7dbc775637 fix(ci): drop --ignore from Safety check, filter accepted CVEs in jq instead
The follow-up to #1370: adding `--ignore SFTY-20260120-40557` to the
`safety check` invocation reintroduced the exact crash #1131/#1157 had
just fixed - "Unhandled exception happened: 'cuda-toolkit'" - but only
once Safety actually has a live vulnerability match to apply the ignore
against (the plain, un-ignored scan against the same requirements-ci.txt
had already succeeded and correctly reported that same match on main,
per the run right before this one).

I couldn't reproduce this locally: my local Safety installation doesn't
surface the live cuda-toolkit CVE match at all (its open-source
vulnerability DB appears to lag CI's), so --ignore never had a real
match to crash on in my testing. That's on me - I should have caught
that my "0 vulnerabilities" local result meant the DB hadn't even seen
the finding yet, not that the fix worked.

Since I can't safely iterate against Safety's own --ignore path without
live-DB access, this moves the "should we still fail on ID X" decision
out of Safety entirely: run the plain scan (the one path an actual CI
run has now proven doesn't crash), then filter the accepted vulnerability
ID out of the report ourselves in jq before counting/printing. Verified
the jq expression directly against a synthetic report shaped like a real
one (id present + one other unrelated id): filters exactly the intended
entry, and - as a bonus - iterating over a null/missing "vulnerabilities"
key with jq now raises inside jq the way the existing guard comment always
assumed it did, rather than silently coming back as 0.
2026-09-01 19:25:34 +05:30
+64 -27
View File
@@ -65,22 +65,7 @@ jobs:
# to avoid crashes from packages like cuda-toolkit that Safety cannot
# parse. This also ensures we're auditing the declared dependency tree
# rather than transitive dependencies of the security tooling itself.
#
# --ignore SFTY-20260120-40557 (CVE-2025-33228): cuda-toolkit<13.1.0 on
# PyPI. torch 2.13.0 (the latest release; there is no newer torch to
# upgrade to) hard-pins `cuda-toolkit[cublas,cudart,cufft,cufile,cupti,
# curand,cusolver,cusparse,nvjitlink,nvrtc,nvtx]==13.0.3` on Linux, so
# this can't be resolved with a version bump on our end - it's not a
# loose transitive pin we control. The actual flaw is OS command
# injection in NVIDIA Nsight Systems' gfx_hotspot recipe
# (process_nsys_rep_cli.py), which requires a human to manually run
# that script with an attacker-supplied string; it isn't reachable from
# any Semantica code path, and isn't even installed here - none of the
# extras torch requests (cublas/cudart/cufft/cufile/cupti/curand/
# cusolver/cusparse/nvjitlink/nvrtc/nvtx, all listed above) include
# Nsight Systems. Re-evaluate once torch ships a release pinning a
# patched cuda-toolkit.
safety check --file requirements-ci.txt --ignore SFTY-20260120-40557 --save-json safety-report.json || true
safety check --file requirements-ci.txt --save-json safety-report.json || true
# Guard 1: fail loudly if Safety exited before writing a report at all
# (network error, API auth failure, tool crash). Without this check a
@@ -93,10 +78,45 @@ jobs:
echo "Checking for package vulnerabilities..."
# No || echo "0" fallback: if jq fails (malformed JSON, missing key,
# vulnerabilities:null) VULNS will be empty or "null" so guard 2 below
# catches it rather than silently treating the broken report as zero.
VULNS=$(jq '.vulnerabilities | length' safety-report.json 2>/dev/null)
# Vulnerability IDs reviewed and accepted as non-actionable for this
# project. Filtered out here with jq rather than passed to Safety's
# own --ignore flag: --ignore crashes ("Unhandled exception happened:
# 'cuda-toolkit'") when it has to apply itself against a live-matched
# vulnerability for cuda-toolkit, apparently the same class of
# unguarded dependency-graph lookup that broke the plain environment
# scan (see git history on this file). The un-ignored scan above is
# the one path confirmed - by an actual CI run - not to crash even
# with a live cuda-toolkit match, so all filtering happens after the
# fact in jq instead of inside Safety.
#
# - SFTY-20260120-40557 (CVE-2025-33228): cuda-toolkit<13.1.0. torch
# 2.13.0 (latest available; no newer release exists) hard-pins
# cuda-toolkit[cublas,cudart,cufft,cufile,cupti,curand,cusolver,
# cusparse,nvjitlink,nvrtc,nvtx]==13.0.3 on Linux - not a version we
# control. The CVE is OS command injection in NVIDIA Nsight
# Systems' gfx_hotspot recipe (process_nsys_rep_cli.py), requiring
# manual invocation with an attacker-supplied string; unreachable
# from Semantica, and Nsight Systems isn't among the extras torch
# requests above. Re-evaluate once torch pins a patched
# cuda-toolkit.
IGNORED_VULN_IDS="SFTY-20260120-40557"
# Exported so the "Comment PR with Security Results" step below can
# apply the same exclusion list to the raw report - it reads
# safety-report.json independently in JS, so without this the PR
# comment would show the accepted CVE as a live finding even though
# this gate correctly treats it as non-actionable.
echo "IGNORED_VULN_IDS=$IGNORED_VULN_IDS" >> "$GITHUB_ENV"
# No []? / || echo "0" fallback on a missing/null "vulnerabilities"
# key: iterating over null raises inside jq, leaving VULNS empty, so
# guard 2 below catches it rather than silently treating a broken
# report as zero.
VULNS=$(jq --arg ignored "$IGNORED_VULN_IDS" '
($ignored | split(",")) as $ignore_list
| [.vulnerabilities[] | select(.vulnerability_id as $id | ($ignore_list | index($id)) | not)]
| length
' safety-report.json 2>/dev/null)
# Guard 2: ensure VULNS is a non-negative integer before the -gt
# comparison. "null" (missing/null key) or "" (jq parse failure) would
@@ -112,10 +132,14 @@ jobs:
echo "CI will fail to prevent merging of vulnerable dependencies"
echo ""
echo "Vulnerability details:"
jq -r '.vulnerabilities[] | "- \(.package_name)==\(.analyzed_version): \(.vulnerability_id) (\(.CVE // "no CVE assigned"))"' safety-report.json || true
jq --arg ignored "$IGNORED_VULN_IDS" -r '
($ignored | split(",")) as $ignore_list
| .vulnerabilities[] | select(.vulnerability_id as $id | ($ignore_list | index($id)) | not)
| "- \(.package_name)==\(.analyzed_version): \(.vulnerability_id) (\(.CVE // "no CVE assigned"))"
' safety-report.json || true
exit 1
else
echo "✅ No security vulnerabilities found"
echo "✅ No actionable security vulnerabilities found (ignored: $IGNORED_VULN_IDS)"
fi
- name: Run Bandit (Code Security Linter)
@@ -204,14 +228,27 @@ jobs:
return lines.join('\n');
}
// Mirrors the shell step's own IGNORED_VULN_IDS (passed through
// $GITHUB_ENV) so an accepted, non-actionable CVE that the CI
// gate already excluded doesn't reappear here as a live finding -
// this reads the same raw, unfiltered safety-report.json.
const ignoredVulnIds = (process.env.IGNORED_VULN_IDS || '')
.split(',')
.map((id) => id.trim())
.filter(Boolean);
const safetySection = renderSection(
'Safety — dependency vulnerabilities',
'safety-report.json',
(data) => (data.vulnerabilities || []).map(
(v) => `- \`${v.package_name}==${v.analyzed_version}\`: ${v.vulnerability_id}` +
(v.CVE ? ` (${v.CVE})` : '') + ` — ${v.advisory || 'no advisory text'}`
)
);
(data) => (data.vulnerabilities || [])
.filter((v) => !ignoredVulnIds.includes(v.vulnerability_id))
.map(
(v) => `- \`${v.package_name}==${v.analyzed_version}\`: ${v.vulnerability_id}` +
(v.CVE ? ` (${v.CVE})` : '') + ` — ${v.advisory || 'no advisory text'}`
)
) + (ignoredVulnIds.length
? `\n\n_Excluded as accepted, non-actionable findings: ${ignoredVulnIds.join(', ')} — see the workflow file's inline comments for why._`
: '');
const banditSection = renderSection(
'Bandit — HIGH-severity code issues',