Validate a CVE against a Red Hat container image using official SBOM attestations, Red Hat VEX data, and CVE metadata from MITRE/OSV.dev.
65
79%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Low
Low-risk findings worth noting
Fix and improve this skill with Tessl
tessl review fix ./ocp-admin/skills/container-cve-validator/SKILL.mdUse this skill when the user asks you to check, validate, or analyze one or more CVEs against Red Hat container images. Input may be a single CVE ID + image reference, or a CSV file path containing multiple pairs.
The user may specify these optional flags. Parse them before extracting the required inputs.
--format markdown — (default) Markdown report; verbosity controlled by --output--format json — machine-readable JSON with 6 structured fields--format csv — single-row CSV with a header row (one row per scan in batch mode)--output full — (default) complete Markdown report with all sections; only applies to --format markdown--output summary — condensed Markdown: executive summary + recommended actions only; only applies to --format markdown--file PATH — path to a CSV input file for batch scanning (mutually exclusive with positional CVE ID and image reference)Record the resolved values as OUTPUT_FORMAT (default: markdown), OUTPUT_MODE (default: full), and INPUT_FILE (default: empty). Strip all flag tokens before extracting remaining positional arguments.
Use the validate_input.py script for all input validation — both single scan and batch mode.
Single scan:
python $SCRIPTS_DIR/validate_input.py --cve [CVE-ID] --image [IMAGE_REFERENCE]Batch mode (when --file was provided):
python $SCRIPTS_DIR/validate_input.py --file [PATH]The script returns JSON with valid (boolean), mode ("single" or "batch"), entries (array of validated CVE/image pairs with registry classification), and errors (array of error messages with line numbers for batch mode).
valid is false: print the errors and stop. Do not proceed to any execution step.valid is true: use the entries array to drive the pipeline. Each entry contains image_ref, ref_format ("tag" or "digest"), registry info, and input_type.input_type is "rhsa": the user provided an RHSA advisory ID. Resolve it to individual CVEs using (do NOT use WebFetch or curl to access access.redhat.com):
python $SCRIPTS_DIR/fetch_rhsa_advisory.py [RHSA-ID]cve_ids[] list against the same image.input_type is "cve": proceed directly with the single CVE ID.[1/N] Scanning CVE-YYYY-NNNNN — registry.../image:tagBatch output aggregation:
--format markdown: print each report separated by a ==== divider line--format json: output a single JSON array [{...}, {...}] — one object per scan--format csv: one header row followed by one data row per scan (no repeated headers)Use the inspect_image.py script to fetch image metadata:
python $SCRIPTS_DIR/inspect_image.py [IMAGE_REFERENCE]The script returns JSON with labels (cpe, name, com.redhat.component, vendor, maintainer, org.opencontainers.image.created), digest, architecture, and errors.
If the script fails with an authentication error, log in first:
regctl registry login registry.redhat.ioThen re-run. If login fails, stop and inform the user.
From the output, record:
labels.cpe → product_cpe — primary anchor for VEX product_tree matchinglabels.name → public component name for VEX matchinglabels["com.redhat.component"] → internal build name (secondary fallback only)labels.vendor and labels.maintainer — for registry ownership validationlabels["org.opencontainers.image.created"] → image build timestampRegistry ownership check: The validate_input.py output already classifies the registry (is_redhat, type). For non-Red Hat registries:
vendor or maintainer from inspect output is "Red Hat, Inc.": the image is a Red Hat image mirrored to an alternate registry. Record the canonical registry.redhat.io form for VEX matching.Input format classification: The validate_input.py output provides ref_format ("tag" or "digest"). For digest-based references, the human-readable tag will be extracted from the SBOM in Step 2.
Do NOT use version or release labels to reconstruct the image tag.
Step 0 — Resolve scripts directory. Before anything else, locate the helper scripts. They are inside the skill's scripts/ subfolder. Run:
SCRIPTS_DIR="scripts"
test -f "$SCRIPTS_DIR/validate_input.py" || { echo "Error: Scripts directory not found at $SCRIPTS_DIR"; exit 1; }Use $SCRIPTS_DIR in all subsequent script calls. The scripts handle tool checks internally (regctl, cosign, syft) and return clear errors if tools are missing.
MANDATORY EXECUTION CONTRACT — read before starting:
This skill requires completing ALL of the following steps in order. No step may be skipped, condensed, or replaced with an alternative approach — regardless of which model is executing.
| # | Step | Required | May skip only when |
|---|---|---|---|
| 0 | Prerequisites check | Always | Never |
| 0 | Input validation + image inspection | Always | Never |
| 1 | CVE Reconnaissance (MITRE → OSV.dev → Go vuln DB) | Always | Never |
| 2 | SBOM Extraction + package verification | Always | Never |
| 3 | Red Hat VEX Validation | Always | Package NOT found in SBOM (Step 2) |
| 4 | Newer Image Availability Scan | Conditional | Trigger conditions not met (see Step 4 header) |
| 5 | Final Report | Always | Never |
Strict rules:
✓ Step N complete — [key finding]grep — do not grep through JSON data filesjq — do not use jq filters on JSON datacurl — do not fetch URLs directly, use the helper scriptscosign — do not call cosign directly, use download_sbom.pyfor ... do ... done loops that process tool-result filespython $SCRIPTS_DIR/...), SCRIPTS_DIR=... resolution, test -f, and cat to read a file.N/A — [reason] for undetermined fields. Never fabricate values.fetch_redhat_vex.py script takes ONLY a CVE-ID argument — no flags. It returns the full raw VEX document.Run the fetch_cve_metadata.py script to query all CVE data sources in a single call:
python $SCRIPTS_DIR/fetch_cve_metadata.py [CVE-ID]The script automatically queries MITRE CVE API, OSV.dev, and (if a GO-* alias is found) the Go vulnerability database. It returns merged JSON with:
description — CVE descriptionaffected[] — all affected packages with ecosystem, package, versions (introduced/fixed), and source (mitre/osv/go_vuln_db)aliases[] — cross-references (GO-, GHSA-, etc.)errors[] — any API failures (non-fatal; the script continues with available data)From the output, identify:
affected is empty and errors indicate the CVE was not found: stop and report "CVE not found"Print: ✓ Step 1 complete — [package name], ecosystem: [ecosystem], sources: [list from affected[].source]
Run the download_sbom.py script to handle all SBOM extraction logic in a single call:
python $SCRIPTS_DIR/download_sbom.py [IMAGE_REFERENCE]The script handles attestation/build-time fallback and image index detection automatically. It returns JSON with:
sbom_source — "attestation" or "build_time"spdx — the full raw SPDX JSON document with all packages, relationships, PURLs, and version infoerrors[] — any issues encounteredRead and interpret the full raw SPDX document from the spdx field. Analyze the SPDX data directly — packages (with externalRefs PURLs and versionInfo), relationships (for parent RPM linkage), checksums. Do NOT use grep or jq to search the SBOM.
If spdx is null (no SBOM found), fall back to syft:
python $SCRIPTS_DIR/generate_sbom_syft.py [IMAGE_REFERENCE]This generates an analyzed SBOM using syft. The output has the same JSON schema but sbom_source will be "syft_analyzed" — note this in the report as a generated SBOM, not an official one.
If neither method produces an SBOM: stop and report "Unable to extract SBOM. Cannot determine package presence."
Package presence check and version confirmation:
Red Hat container image SBOMs contain both RPM packages and non-RPM content (Go modules, Python packages, Node.js packages, etc.), each identified by ecosystem-specific PURLs. A non-RPM package may exist as a standalone SBOM entry AND be linked via an SPDX relationship to the RPM package that delivers it. Use the following matching logic:
Identify the target package name and ecosystem from Step 1. For Go, use the module path from the Go vuln DB as the authoritative name.
Search SBOM packages by PURL and name:
externalRefs[] for entries with referenceType: purl. Parse the PURL to extract ecosystem, name, and version.pkg:rpm, pkg:golang, pkg:pypi, pkg:npm, etc.).golang.org/x/net).DYNAMIC_LINK, STATIC_LINK, or CONTAINED_BY relationship to a parent RPM package entry. Record both the non-RPM package entry and its parent RPM if present.name and sourceName fields (case-insensitive).If the package is NOT found by any method: skip Step 3 and go to Step 4 (Early Exit — package not in image).
If the package IS found: record the installed version from the SPDX versionInfo field and proceed to version range confirmation.
Confirm the installed version falls within the vulnerable range:
Epoch:Version-Release). A higher EVR is a newer, potentially fixed version. If the installed version is within the introduced–fixed range from MITRE, it is vulnerable.introduced and fixed boundaries from the Go vuln DB ranges[].events[].versions[] array.Print: ✓ Step 2 complete — package [found|not found], version: [version], verdict: [vulnerable|patched|inconclusive], SBOM method: [attestation|build-time]
Run the fetch_redhat_vex.py script to fetch the full raw VEX document:
python $SCRIPTS_DIR/fetch_redhat_vex.py [CVE-ID]The script returns JSON with:
http_status — 200, 404, or error codevex — the full raw CSAF VEX document with product_tree, vulnerabilities, remediations, flags, threatserrors[] — any issuesRead and interpret the full VEX document from the vex field. The LLM should analyze the product_tree (branches, relationships, CPEs, PURLs), vulnerabilities (product_status, remediations, flags, threats) directly to perform matching against the image CPE and component name from Step 0. This gives the LLM full context to:
Interpret the results:
http_status is 404: record "No Red Hat VEX data available for this CVE" and proceed to Step 4 (check VEX data gap Condition B).vex.product_tree contains only a generic blanket statement (all products under cpe:/a:redhat): treat as no analysis performed (check VEX data gap Condition C).Read references/01-vex-validation-procedure.md for the complete VEX matching procedure (sub-steps 3–9: generic blanket detection, product_tree matching, status determination, remediation/flags/severity extraction, and parent RPM patch check).
Print: ✓ Step 3 complete — VEX status: [status], severity: [severity], gap condition: [A|B|C|none]
Trigger — perform this step only when ALL of the following are true:
product_status[].fixed for the container (Step 3, sub-step 5), ORfixed in RHELvendor_fix remediation URL or advisory metadata)If trigger conditions are NOT met, print ✓ Step 4 skipped — trigger conditions not met ([reason]) and proceed to Step 5.
Procedure:
Extract the image repository from the scanned image reference — strip tag or digest, keeping only registry + namespace + image name (e.g., registry.redhat.io/multicluster-engine/cluster-proxy-addon-rhel9).
Run the scan_newer_images.py script:
python $SCRIPTS_DIR/scan_newer_images.py [IMAGE_REPOSITORY] --since [SCANNED_IMAGE_CREATED_LABEL]The script returns JSON with newer_images[] — each entry has tag, created, digest, and cpe. Images are sorted by date (newest first), deduplicated by digest, capped at 10 results.
If newer_images is empty: no newer images exist yet. Record this and proceed to Step 5.
For each newer image candidate, download its SBOM and search for the vulnerable RPM:
python $SCRIPTS_DIR/download_sbom.py [IMAGE_REPOSITORY]:[TAG]versionInfo against the known fixed version using RPM EVR orderingFor each image found to contain the fix, compare its CPE against the scanned image's CPE:
[IMAGE_REPOSITORY]:[TAG] ([DIGEST], built [CREATED])"[NEWER_CPE]): [IMAGE_REPOSITORY]:[TAG] ([DIGEST], built [CREATED]). Upgrading would change product stream from [IMAGE_CPE] to [NEWER_CPE]."If no newer image contains the fixed RPM: record "No patched container image released yet. The RPM fix exists but the container has not yet been rebuilt with the updated RPM."
Print: ✓ Step 4 complete — [patched image found: REPO:TAG | no patched image found | skipped]
Read references/02-report-template.md for VEX data gap condition evaluation (Conditions A/B/C), executive summary rules, upstream patch note logic, RHSA advisory assessment, the full report template (markdown/JSON/CSV), and field semantics.
Print: ✓ Step 5 complete — report generated
validate_input — validates CVE ID format and image referenceinspect_image — extracts container image metadata via regctlfetch_cve_metadata — queries MITRE, OSV.dev, and Go vuln DBdownload_sbom — fetches SBOM attestations and build-time SBOMsgenerate_sbom_syft — fallback SBOM generation via syft (optional, used when attestations unavailable)fetch_redhat_vex — retrieves Red Hat VEX security advisoriesfetch_rhsa_advisory — resolves RHSA advisory IDs to CVE listsscan_newer_images — lists and checks newer image tags for patched RPMscve-recon — standalone CVE reconnaissance (Step 1 only)image-inspect — standalone image inspection (Input Validation only)coreos-cve-validator — CVE validation for CoreOS/RHCOS imagese46c4fa
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.