$npx -y skills add opendatahub-io/ai-helpers --skill cve-vex-assessUse this skill to determine VEX (Vulnerability Exploitability eXchange) justification when a CVE is not present in scan results. Auto-detects three justification types. Cases requiring human judgment are flagged for manual review. Writes result to autofix-output/cve-vex-result.js
| 1 | # Skill: CVE VEX Assess |
| 2 | |
| 3 | When a CVE scan returns "absent" or "informational", determine the appropriate |
| 4 | VEX justification. Three of the five CSAF justification types can be |
| 5 | auto-detected; the remaining two require human judgment. |
| 6 | |
| 7 | ## Step 1: Check — Component not Present |
| 8 | |
| 9 | The package is not declared in any dependency manifest. |
| 10 | |
| 11 | ```bash |
| 12 | cd "${REPO_DIR}/${BUILD_LOCATION:-.}" |
| 13 | |
| 14 | FOUND_IN_MANIFEST="" |
| 15 | case "$LANG" in |
| 16 | go) FOUND_IN_MANIFEST=$(grep -Fi -- "${PACKAGE}" go.mod 2>/dev/null) ;; |
| 17 | node) FOUND_IN_MANIFEST=$(grep -Fi -- "${PACKAGE}" package.json package-lock.json 2>/dev/null) ;; |
| 18 | python) FOUND_IN_MANIFEST=$(grep -rFi -- "${PACKAGE}" requirements*.txt setup.py pyproject.toml 2>/dev/null) ;; |
| 19 | esac |
| 20 | |
| 21 | if [ -z "$FOUND_IN_MANIFEST" ]; then |
| 22 | JUSTIFICATION="component_not_present" |
| 23 | EVIDENCE="Package '${PACKAGE}' not found in any dependency manifest" |
| 24 | fi |
| 25 | ``` |
| 26 | |
| 27 | ## Step 2: Check — Vulnerable Code not Present |
| 28 | |
| 29 | Package is in the manifest but at a version that is already patched. |
| 30 | |
| 31 | Determine the fixed version by checking the CVE advisory (NVD, GitHub Advisory |
| 32 | Database, or language-specific advisory). For Go, check the govulncheck output |
| 33 | which lists the fixed version. For Node.js, check `npm audit` output. For |
| 34 | Python, check `pip-audit` output or the PyPI advisory. |
| 35 | |
| 36 | ```bash |
| 37 | # Note: sort -V requires GNU coreutils (standard in CI containers; macOS needs `brew install coreutils`) |
| 38 | if [ -z "$JUSTIFICATION" ] && [ -n "$FOUND_IN_MANIFEST" ] && [ -n "$CVE_FIXED_VERSION" ]; then |
| 39 | HIGHER=$(printf '%s\n' "$INSTALLED_VERSION" "$CVE_FIXED_VERSION" | sort -V | tail -1) |
| 40 | if [ "$HIGHER" = "$INSTALLED_VERSION" ]; then |
| 41 | JUSTIFICATION="vulnerable_code_not_present" |
| 42 | EVIDENCE="Package '${PACKAGE}' at version ${INSTALLED_VERSION} >= fixed version ${CVE_FIXED_VERSION}" |
| 43 | fi |
| 44 | fi |
| 45 | ``` |
| 46 | |
| 47 | ## Step 3: Check — Vulnerable Code not in Execute Path (Go only) |
| 48 | |
| 49 | govulncheck reports a module as "Informational" when it is in the dependency |
| 50 | tree but the vulnerable symbol is not called. |
| 51 | |
| 52 | ```bash |
| 53 | if [ -z "$JUSTIFICATION" ] && [ "$LANG" = "go" ]; then |
| 54 | if printf '%s' "$SCAN_OUTPUT" | grep -q "Informational" && \ |
| 55 | printf '%s' "$SCAN_OUTPUT" | grep -A5 "Informational" | grep -Fqi -- "${PACKAGE}"; then |
| 56 | JUSTIFICATION="vulnerable_code_not_in_execute_path" |
| 57 | EVIDENCE="govulncheck found module in dep tree but vulnerable symbol is not called" |
| 58 | fi |
| 59 | fi |
| 60 | ``` |
| 61 | |
| 62 | ## Step 4: Determine final assessment |
| 63 | |
| 64 | | # | Justification | Auto-detectable? | |
| 65 | |---|---|---| |
| 66 | | 1 | Component not Present | Yes | |
| 67 | | 2 | Vulnerable Code not Present | Yes | |
| 68 | | 3 | Vulnerable Code not in Execute Path | Yes (Go only) | |
| 69 | | 4 | Vulnerable Code cannot be Controlled by Adversary | No — human judgment | |
| 70 | | 5 | Inline Mitigations already Exist | No — human judgment | |
| 71 | |
| 72 | If none of checks 1-3 matched: |
| 73 | |
| 74 | ```bash |
| 75 | if [ -z "$JUSTIFICATION" ]; then |
| 76 | JUSTIFICATION="needs_human_review" |
| 77 | EVIDENCE="Auto-detection inconclusive — requires human judgment (types 4 or 5)" |
| 78 | fi |
| 79 | ``` |
| 80 | |
| 81 | ## Step 5: Write output |
| 82 | |
| 83 | Create `autofix-output/` if it doesn't exist. Write `autofix-output/cve-vex-result.json`: |
| 84 | |
| 85 | ```json |
| 86 | { |
| 87 | "cve_id": "CVE-2025-68121", |
| 88 | "repo": "opendatahub-io/models-as-a-service", |
| 89 | "branch": "main", |
| 90 | "justification": "component_not_present", |
| 91 | "justification_label": "Component not Present", |
| 92 | "evidence": "Package 'urllib3' not found in any dependency manifest", |
| 93 | "auto_detected": true, |
| 94 | "package": "urllib3", |
| 95 | "installed_version": null, |
| 96 | "fixed_version": "2.2.3", |
| 97 | "timestamp": "2026-04-27T12:00:00Z" |
| 98 | } |
| 99 | ``` |
| 100 | |
| 101 | ## Caller contract |
| 102 | |
| 103 | The orchestrator reads `auto_detected`: |
| 104 | - `true` → post Jira comment with VEX justification (never auto-close the issue) |
| 105 | - `false` → document in artifacts, flag for manual review |
| 106 | |
| 107 | ## Gotchas |
| 108 | |
| 109 | - Only 3 of 5 VEX justification types can be auto-detected; types 4 (Vulnerable Code cannot be Controlled by Adversary) and 5 (Inline Mitigations already Exist) require human judgment |
| 110 | - `sort -V` requires GNU coreutils (standard in CI containers, not available on macOS by default — install via `brew install coreutils`) |
| 111 | - Never auto-close Jira issues — leave closing to the human reviewer, even when a VEX justification is clear-cut |