---
name: cve-source-check
promoted_to: deploy
description: "Audit CVE/vulnerability source coverage for a technology stack. Maps each component (container, library, base image, runtime) to authoritative CVE feeds, flags gaps, and produces audit-ready reports. Generic: works for any service or stack."
user-invocable: false
argument-hint: "[--inventory <file>] [--inline <tech-list>] [--current-sources <file>] [--service <name>] [--check-urls]"
allowed-tools:
  - Bash
  - Read
  - Write
  - Edit
  - Glob
  - Grep
routing:
  triggers:
    - "check cve sources"
    - "cve source coverage"
    - "audit cve feeds"
    - "vulnerability source audit"
    - "verify cve sources"
    - "security feed audit"
  category: infrastructure
  complexity: Simple
  pairs_with:
    - assessment
---

# CVE Source Check

Audit CVE/vulnerability source coverage for a technology stack. Maps components
to authoritative CVE feeds via a versioned registry, flags gaps, and produces
audit-ready reports (JSON + Markdown).

**In scope**: component-to-feed mapping, coverage/gap reporting, optional URL
reachability checks. **Out of scope**: running scanners (Trivy/Snyk), fetching
CVE content, private vuln databases.

## Quick Start

```bash
# Inline, offline
python3 scripts/check-cve-sources.py \
  --inline "go@1.22,alpine@3.19,postgres@16,redis@7,nginx@1.25" \
  --service my-service

# Inventory + monitored feeds + link verification
python3 scripts/check-cve-sources.py \
  --inventory examples/inventory.example.json \
  --current-sources examples/current-sources.example.txt \
  --service my-service --check-urls
```

## Inputs

| Flag | Purpose |
|---|---|
| `--inventory <file>` | JSON: `[{name, version?, type?}, ...]` or `{components: [...]}`. |
| `--inline "name@ver,..."` | Comma-separated list. Mutually exclusive with `--inventory`. |
| `--current-sources <file>` | One URL per line. `#` comments and blank lines skipped. |
| `--service <name>` | Name for report header and filenames. |
| `--check-urls` | HEAD-check every source URL (5s timeout, graceful degradation). |
| `--registry <path>` | Override default `tech-source-registry.json`. |
| `--out-dir <path>` | Output directory (default: cwd). |

JSON only. YAML not supported (no stdlib parser).

## Outputs

Files: `cve-source-report-{service}-{YYYYMMDD}.{md,json}` in `--out-dir`.

| Exit | Meaning |
|---|---|
| 0 | Full coverage. |
| 1 | Gaps (unmapped components or unmonitored sources). |
| 2 | Unreachable source URL (only with `--check-urls`). |
| 3 | Input error (missing/malformed registry or inventory). |

## Workflow

### Phase 1: LOAD

1. Locate `tech-source-registry.json` (next to SKILL.md by default, or `--registry`).
2. Build inventory from `--inventory` (JSON list or `{components: [...]}`) or `--inline` (comma-split `name@version`).
3. If `--current-sources` provided, read URLs and normalize for case-insensitive comparison.

**Gate**: at least one component present. Empty inventory -> exit 3.

### Phase 2: MAP & VERIFY

1. Look up each component `name` (and aliases) in the registry.
   - Found -> `mapped`, attach source list. Missing -> `unmapped`, sources `[]`.
2. If current sources loaded, mark each source `monitored: true` when its normalized URL appears.
3. If `--check-urls`: HEAD-check each unique URL. Treat 200/301/302/403/405 as reachable. 4xx (except 403/405) and 5xx -> `reachable: false`. Timeout/DNS/TLS failure -> `reachable: null` (WARN, does not affect exit code). 5s timeout per URL, cached per run.

**Gate**: every component has status; every source has `monitored` and `reachable` fields.

### Phase 3: REPORT

1. Compute summary: components, mapped/unmapped, monitored, coverage %, gaps, unreachable.
2. Write JSON report with per-component status and per-source `monitored`/`reachable` fields.
3. Write Markdown report: summary table, components table (markers), gaps section (when gaps exist), unmapped section (when unmapped exist).
4. Print one-screen summary to stdout. Set exit code per table above.

**Gate**: both files written, summary printed.

## Registry Schema

`tech-source-registry.json` shape:

```json
{
  "$schema_version": "1.0",
  "kinds": ["advisory-list", "github-security", "mailing-list", "distro-tracker", "vendor-page", "mitre"],
  "priorities": ["primary", "secondary"],
  "technologies": [
    {"name": "postgres", "aliases": ["postgresql","pg"], "type": "container",
     "sources": [{"url": "https://...", "kind": "advisory-list", "priority": "primary"}]}
  ]
}
```

Each technology: `name` (lowercase, unique), `aliases` (list), `type` (`runtime`/`base-image`/`container`/`library`), `sources` (1-3, at least one `primary`).

To add a technology: pick canonical name, list aliases, add 1-3 sources (lead with vendor advisory page), re-run against a sample inventory.

## Error Handling

| Error | Cause | Fix |
|---|---|---|
| Failed to load registry | Missing or malformed JSON | Validate with `python3 -m json.tool` |
| Failed to load inventory | Missing, malformed, or wrong shape | Validate JSON; must be list or `{components: [...]}` |
| Inventory empty | No usable components | Each entry needs `name`. Inline needs non-empty tokens. |
| Coverage stuck at 0% | `--current-sources` URLs don't match registry | Copy URLs from registry. Scheme/host case and trailing slash are normalized; rest must match. |
| Many `[--]` entries with `--check-urls` | Network issues | Re-run without `--check-urls`. Network errors don't affect gap exit code. |
