Skip to content

feat(cli): honor CycloneDX VEX suppressions in the --fail-on gate#51

Open
dmchaledev wants to merge 1 commit into
mainfrom
claude/magical-ptolemy-ivwsm7
Open

feat(cli): honor CycloneDX VEX suppressions in the --fail-on gate#51
dmchaledev wants to merge 1 commit into
mainfrom
claude/magical-ptolemy-ivwsm7

Conversation

@dmchaledev

Copy link
Copy Markdown
Contributor

Summary

CycloneDX vulnerabilities can carry an analysis.state (VEX) declaring that the product is not_affected by a CVE, or that a finding is a false_positive. Today the parser ignores this field entirely, so such a vulnerability is reported as a plain "new CVE" and — since #49 landed the --fail-on gate — it fails the CI build.

That is a false-positive gate break: the SBOM's own author has already assessed and documented that the product is not impacted, yet the gate stops the pipeline anyway. Suppressing exactly this case is the entire purpose of VEX, so a gate that ignores it is a real blind spot for a tool marketed as "perfect for CI/CD gates."

No open issue or PR touches VEX / analysis.state — this is orthogonal to all in-flight work (which is about component/purl keying, CVSS extraction, ordering, license/hash diffs, etc.).

Reproduction (before this change)

A CVE the SBOM marks not_affected still exits 3 under --fail-on critical:

"vulnerabilities": [{
  "id": "CVE-2021-44228",
  "affects": [{ "ref": "pkg:maven/org.apache.logging.log4j/log4j-core@2.14.1" }],
  "ratings": [{ "severity": "critical" }],
  "analysis": { "state": "not_affected", "justification": "code_not_reachable" }
}]
$ sbom-diff old.json new.json --fail-on critical
Gate failed: 1 new CVE(s) at or above "critical" severity: CVE-2021-44228   # exit 3 — false positive

What changed

  • parser (src/parser.ts): capture vulnerabilities[].analysis.state, lowercased, onto CVEEntry.analysisState.
  • cli (src/cli.ts): gateFailures now excludes not_affected / false_positive CVEs before applying the policy, via a new exported isSuppressed(v) helper. Actionable states (exploitable, in_triage, resolved, none) and CVEs with no VEX still gate exactly as before.
  • reporter (src/reporter.ts): new CVEs are tagged (VEX: <state>) in text and markdown, so a suppressed-but-listed CVE explains why the gate passed — the audit trail stays complete.
  • types (src/types.ts): documents the new optional analysisState field.
  • docs: README notes the VEX-aware gate behavior.

Behavior after this change

$ sbom-diff old.json new.json --fail-on critical
...
⚠ New CVEs:
  ! CVE-2021-44228 [critical] (VEX: not_affected) — pkg:maven/...log4j-core@2.14.1
                                                                              # exit 0 — correctly suppressed

The same CVE without the analysis block still exits 3, as before.

Why it's safe / self-contained

  • Backward compatible: only vulnerabilities that explicitly declare not_affected / false_positive change behavior; everything else is untouched. SBOMs without VEX (the current fixtures) behave identically.
  • No new dependencies. Purely additive to the type model.
  • Low conflict surface: the reporter change only appends to existing cells (no table restructuring).

Tests

npm run typecheck, npm run lint, and npx vitest run all pass — 45 tests (was 38). Added coverage:

  • parser extracts and lowercases analysis.state; absent block → undefined
  • gateFailures ignores suppressed CVEs under any and severity thresholds, while still failing on real + in_triage findings
  • isSuppressed true only for not_affected / false_positive
  • reporter renders the (VEX: …) tag in text and markdown

🤖 Generated with Claude Code

https://claude.ai/code/session_01GxwABuKnNRN53T7QmVqe4d


Generated by Claude Code

A CycloneDX vulnerability may carry an `analysis.state` (VEX) declaring the
product is `not_affected` or the finding a `false_positive`. Previously the
parser ignored this field, so such a vulnerability was reported as a new CVE
and could fail the `--fail-on` CI gate — a false-positive build break that
undermines the gate's trustworthiness, which is exactly what VEX exists to
prevent.

- parser: capture `vulnerabilities[].analysis.state` (lowercased) on CVEEntry
- cli: exclude `not_affected`/`false_positive` CVEs from gateFailures via a new
  exported `isSuppressed` helper; actionable states (exploitable, in_triage,
  resolved, none) and CVEs without VEX still gate as before
- reporter: annotate new CVEs with `(VEX: <state>)` in text and markdown so a
  suppressed-but-listed CVE explains why the gate passed
- types: document the new optional `analysisState` field
- tests: parser extraction/lowercasing, gate suppression, isSuppressed, reporter
- docs: note VEX-aware gating in the README

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GxwABuKnNRN53T7QmVqe4d
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants