nvidia/aicr-managing-openvex
| Use when adding, updating, or removing CVE/GHSA suppressions in `.openvex.json` — the OpenVEX document consumed by the daily image vulnerability scan workflow. Triggers on "VEX", "OpenVEX", ".openvex.json", "suppress CVE", "ignore CVE", "vulnerability suppression", "aiperf-bench CVE", or any request to act on findings reported by `Daily Image Vulnerability Scan` for the aiperf-bench for new HIGH+ findings, drops statements that no longer apply (dependency upgraded past the fix, advisory recalled, package removed), and verifies suppressions actually land in the JSON output.
npx skills add https://github.com/NVIDIA/aicr --skill aicr-managing-openvex
.openvex.json.openvex.json carries per-CVE reachability evidence used to suppress
vulnerability findings in the aiperf-bench container image. The file is
consumed by the Daily Image Vulnerability Scan workflow
(.github/workflows/vuln-scan-images.yaml) via the vex: input on
anchore/[email protected], which passes it to grype as `--vex
.openvex.json`.
This skill exists because the file has *non-obvious* invariants — most
notably the product-PURL matching rule — and getting them wrong silently
no-ops every statement in the document.
Daily Image Vulnerability Scan run reports HIGH+ CVE(s) on theaiperf-bench image and a maintainer needs to add a suppression after
verifying reachability.
AIPERF_VERSION invalidators/performance/aiperf-bench.Dockerfile) or its dependency
pins, fixing a CVE that was previously suppressed → the entry must be
removed.
cover them" — typically a PURL or vulnerability-ID mismatch.
These are the rules that, when violated, cause silent suppression
failures. Verify each one before claiming a statement is correctly
applied.
products[].purl must equal the grype image PURLGrype derives the OCI image PURL from the **registry repository
basename**, not from org.opencontainers.image.title. For the aiperf-bench
image:
ghcr.io/nvidia/aicr-validators/aiperf-bench:<tag> →grype PURL pkg:oci/aiperf-bench.
aicr-aiperf-bench:test (matching the titlelabel) → grype PURL pkg:oci/aicr-aiperf-bench.
Every statement in this repo therefore carries both product entries:
"products": [
{ "@id": "pkg:oci/aicr-aiperf-bench", "identifiers": { "purl": "pkg:oci/aicr-aiperf-bench" } },
{ "@id": "pkg:oci/aiperf-bench", "identifiers": { "purl": "pkg:oci/aiperf-bench" } }
]
If you add a statement, include both. If you rename the image or add a
new image to the VEX scope, derive the new PURL by repeating the local
reproduction below and checking .source.target.userInput against the
generated PURL — do not guess from labels.
vulnerability.name must equal grype's primary IDGrype emits a single primary ID per match (the .vulnerability.id field
of .matches[]). For ecosystem advisories with both a GHSA and a CVE,
the primary ID is usually the GHSA; the CVE shows up only as a
relatedVulnerabilities[].id alias. OpenVEX matching is by exact name —
a CVE in the VEX file will not match a GHSA primary ID even though they
describe the same advisory.
Use the ID that appears in the HIGH+: line of the scan artifact /
Slack notification (which prints <pkg> <primary-id> (<aliases>)), or
extract it directly from the JSON:
jq -r '.matches[] | select(.vulnerability.severity == "High" or .vulnerability.severity == "Critical")
| "\(.artifact.name) \(.vulnerability.id) (\(.relatedVulnerabilities|map(.id)|join(",")))"' \
<(grype <image> --only-fixed -c .grype.yaml --vex .openvex.json -o json)
Allowed values for not_affected status:
component_not_present — package isn't in the image at all.vulnerable_code_not_present — package is in the image but thespecific vulnerable symbol/file/build is absent (e.g., conditionally
compiled out, removed in the shipped version).
vulnerable_code_not_in_execute_path — code exists but the workloadnever invokes it.
vulnerable_code_cannot_be_controlled_by_adversary — code isreachable but inputs are not attacker-influenced.
inline_mitigations_already_exist — runtime hardening (seccomp,caps drop, etc.) blocks the trigger.
vulnerable_code_not_in_execute_path is the most common choice for
this image; vulnerable_code_not_present is used when the symbol is
conditionally compiled out (e.g., Windows-only APIs in a Linux glibc).
impact_statement must cite concrete evidenceEvery statement requires a substantive impact_statement — not a
hand-wave. Reviewers and downstream consumers (auditors, customers
reading SBOMs) read this. Cite at least one of:
pattern shown (e.g., grep -rn -E '^(import|from) (gzip|lzma|bz2)').
feature is gated off (e.g., aiperf/plot/dashboard/server.py is only
reached via the aiperf plot subcommand).
(USER, capabilities, base-image choice).
use.
See existing statements for the expected density; CI does not enforce
this but reviewers will.
The only way to be certain a statement applies is to run the same
grype invocation CI runs and confirm the finding moves from .matches[]
to .ignoredMatches[]. The recipe:
# 1. Build the image locally with the title label the workflow sets
docker buildx build \
--load \
--platform linux/amd64 \
-f validators/performance/aiperf-bench.Dockerfile \
-t aicr-aiperf-bench:test \
--label "org.opencontainers.image.title=aicr-aiperf-bench" \
.
# 2. Install the exact grype version the workflow pins
# (lives in GrypeVersion.js of anchore/[email protected])
GRYPE_VERSION=v0.110.0 # cross-check with .github/workflows/vuln-scan-images.yaml
gh release download "${GRYPE_VERSION}" --repo anchore/grype \
--pattern "grype_*_darwin_arm64.tar.gz" -O /tmp/grype.tgz
tar -xzf /tmp/grype.tgz -C /tmp grype && mv /tmp/grype /tmp/grype-vex
# 3. Reproduce the CI scan flags exactly
/tmp/grype-vex aicr-aiperf-bench:test \
--fail-on high --only-fixed --vex .openvex.json -c .grype.yaml \
-o json --file /tmp/scan.json
# 4. Inspect what survived (these MUST be empty for a passing scan)
jq '[.matches[] | select(.vulnerability.severity == "High" or .vulnerability.severity == "Critical")
| {id: .vulnerability.id, pkg: .artifact.name}]' /tmp/scan.json
# 5. Confirm the suppression landed. NOTE: ignoredMatches entries carry
# `vulnerability` at the top level (NOT under `.match`) in grype 0.110.
jq '[.ignoredMatches[]? | select(.vulnerability.severity == "High" or .vulnerability.severity == "Critical")
| {id: .vulnerability.id, rules: .appliedIgnoreRules}]' /tmp/scan.json
A new statement is correct only when step 4 returns [] for the
vulnerability it targets and step 5 lists it under appliedIgnoreRules
with namespace = "vex".
The scan workflow builds and pushes every image with tag
scan-<full-head-sha> before scanning. Those tags stay on GHCR, so you
can scan the exact bytes CI scanned — all seven matrix images, not
just aiperf-bench — without a local docker build:
SHA=$(gh run list -R NVIDIA/aicr --workflow vuln-scan-images.yaml \
--limit 1 --json headSha --jq '.[0].headSha')
/tmp/grype-vex "ghcr.io/nvidia/aicr-validators/aiperf-bench:scan-${SHA}" \
--only-fixed --vex .openvex.json -c .grype.yaml -o json --file /tmp/scan.json
Use this for triage and the stale audit (it covers aicr-gate and
aicr, which have no local Dockerfile build path). Use the docker-build
recipe above only when validating a Dockerfile change before it is
pushed. Caveat: a local grype DB newer than this morning's CI run can
surface advisories CI hasn't seen yet — treat those as *incoming*
findings, not discrepancies.
The daily scan emits HIGH+ identifiers in the per-image artifact and
Slack notification:
aiperf-bench: 0 critical, 2 high, 6 medium, 0 low, 0 negligible (10 VEX-suppressed)
HIGH+: pillow GHSA-pwv6-vv43-88gr (CVE-2026-42311), pillow GHSA-whj4-6x5x-4v2j (CVE-2026-40192)
For each ID:
shipped in a version reachable from aiperf's pins, the right action
is usually *not* a VEX entry — it's bumping the aiperf pin so the
fix lands and the finding disappears. Bump
AIPERF_VERSION in validators/performance/aiperf-bench.Dockerfile,
verify with the local repro above, and skip the rest of this section.
must be visible in impact_statement:
grep -rn patterns).aiperf profile <text-llm> invokedby validators/performance/inference_perf_constraint.go) reaches
the code path even transitively.
python:3.13-slim isDebian trixie / glibc / Linux only, so Windows-only and
glibc-only-on-certain-locales conditions are inert).
correct primary ID (see invariant 2), a v0.2.0 justification (see
invariant 3), and concrete evidence (see invariant 4).
[] for the new ID.include it, so dead statements never accumulate alongside new ones.
Run `gh workflow run "Daily Image Vulnerability Scan" --repo
NVIDIA/aicr --ref main, watch with gh run watch <id> --exit-status`,
inspect the aiperf-bench scan-result artifact.
Statements rot: dependencies get upgraded past fixes, advisories get
withdrawn, components leave the image. A stale statement is invisible —
it applies to nothing, silently — so the audit runs on every change
to the file, not just before releases. (The audit that introduced this
rule found 12 dead statements out of 70.)
Scan every image the document has product entries for (currently
aiperf-bench, aicr-gate, aicr) using the scan-<sha> shortcut
above, then diff declared statements against applied rules:
# Applied: unique vuln IDs suppressed via the vex namespace
jq -r '[.ignoredMatches[]? | select((.appliedIgnoreRules//[]) | any(.namespace=="vex"))
| .vulnerability.id] | unique[]' /tmp/scan-<image>.json | sort > /tmp/applied.txt
# Declared: statement names scoped to that image's product PURL
jq -r '.statements[] | select([.products[]["@id"]] | any(test("<image>")))
| .vulnerability.name' .openvex.json | sort > /tmp/declared.txt
comm -23 /tmp/declared.txt /tmp/applied.txt # stale candidates
For each candidate, classify before deleting — three distinct cases:
grep <id> /tmp/scan-<image>.json → 0 hits,including aliases): the finding no longer exists (package upgraded
past the fix, advisory withdrawn). Delete.
fix-state: wont-fix (appears in.ignoredMatches[] with appliedIgnoreRules[].namespace == ""):
--only-fixed already hides it, so the VEX statement never applies.
Delete — do not keep it "just in case": if the distro ships a
fix, the daily image rebuild absorbs it automatically, and until it
does the finding must surface rather than be pre-suppressed (a fix
that becomes reachable means bump, not VEX).
.matches[] under a different primary ID (a CVEstatement while grype emits the GHSA, or vice versa): NOT stale — a
name mismatch. Fix vulnerability.name per invariant 2.
After deleting, re-run the scans for all covered images and confirm
the vex-suppressed counts still match the latest CI run for the
statements that remain (deleting must be count-neutral).
Bump the document version and refresh timestamp in the same edit.
pkg:oci/<image-title> when CI scans pkg:oci/<repo-basename>.The label has no effect on grype's image PURL. Always include the
registry-basename form.
vulnerability.name when grype emits a GHSA primary.The two names are NOT interchangeable for OpenVEX matching.
for findings that *cannot* be remediated by upgrading; if the fixed
version is reachable, bump the pin instead.
impact_statement ("not exploitable", "low risk").Cite the specific code path, file, or upstream language that supports
the claim. Reviewers will reject thin justifications.
timestamp and version at the documentlevel when materially changing statements.** Bump
version on each substantive edit and update timestamp (or
Reviewed: notes) so downstream consumers can detect drift.
fails to apply is invisible — there is no warning, no failure, no log
line. The only signal is that the CVE keeps appearing in scans. Always
run the local repro before committing.
.github/workflows/vuln-scan-images.yaml.openvex.json.grype.yamlvalidators/performance/aiperf-bench.DockerfileAIPERF_VERSION ARG in that DockerfileGrypeVersion.js at thepinned scan-action SHA in the workflow
<short-name>: N critical, N high, N medium, N low, N negligible (N VEX-suppressed)
HIGH+: <pkg> <primary-id> (<aliases>), ...
Take nvidia/aicr-managing-openvex from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.