# CVE Collector

ATR collects every AI / agent / LLM / MCP CVE from authoritative public
sources and emits it as a draft ATR rule proposal. The collector is the
"baseline coverage" half of the standard — it ensures that when an
investor / auditor / customer asks "do you cover CVE-X?", the answer is
"yes, here is the proposal file" rather than "we haven't heard of it."

## What "track" vs "detect" means

Not every CVE can be detected by a content-layer rule. Honest accounting:

| Triage | Meaning | Example |
|---|---|---|
| `detection_ready: true` | Advisory body contains a payload, PoC, or extractable attack pattern. A regex can be authored and tested. | Prompt injection variants, SQL injection in LiteLLM, path traversal in MCP file tools, RCE with reproducible HTTP body. |
| `detection_ready: false` | Auth bypass, deserialization in a library function, supply-chain compromise, misconfig, or any CVE without a runtime payload that ATR can pattern-match at the content layer. | OAuth flow bypass, pickle RCE in a transformers helper, dependency confusion in pypi mirror, default-credential issues. |

The collector treats both as first-class — both go into
`proposals/<source>/`. Only `detection_ready: true` proposals are expected
to advance to active rules in `rules/`. Tracking-only proposals stay in
proposals/ forever so the catalog stays comprehensive.

This honest split is what makes ATR look like a standard rather than a
demo. Sigma doesn't claim coverage of patches; we don't claim coverage of
RCEs that can't be regex'd.

## Sources

| Source | Script | Output dir | Frequency | Notes |
|---|---|---|---|---|
| CISA KEV | `scripts/sync-cisa-kev.ts` | `proposals/cisa-kev/` | Daily | Filtered to AI-relevant vendors / products / descriptions. Federal-mandate remediation deadlines preserved. |
| AVID-DB | `scripts/sync-avid-db.ts` | `proposals/avid/` | Daily | AI-scoped by construction. Filters out fairness-only (E0100) entries; keeps Security SEPs. |
| GHSA | `scripts/sync-ghsa.ts` | `proposals/ghsa/` | Daily | GitHub Security Advisories for an AI-package allowlist (pip / npm / maven / nuget / go / rubygems). Token-authenticated via `GITHUB_TOKEN`. |
| NVD AI-filter | _planned_ | `proposals/nvd/` | Daily | NVD recent-CVE feed with AI keyword filter. Highest noise — adopt last. |
| Huntr.dev | _planned_ | `proposals/huntr/` | Daily | Bug-bounty platform with AI/ML scope. Smaller volume but high signal. |

Adding a new source = drop a `scripts/sync-<src>.ts` that follows the
shared contract:

- Reads from authoritative source over HTTPS.
- Writes draft proposals to `proposals/<src>/<id>.proposal.yaml`.
- Each proposal has `_triage.detection_ready: true|false` based on whether
  the source provides a payload or PoC.
- Final line on stdout is `::<src>-summary::{json}` so the orchestrator
  can aggregate.
- Exits 2 on rate-limit (recoverable), 1 on fatal error.

Then register it in `SOURCES` inside `scripts/cve-collect.ts`.

## Daily run

`.github/workflows/cve-daily-sync.yml` runs at 06:00 UTC every day. It:

1. Runs `scripts/cve-collect.ts --write` against every registered source.
2. Counts new proposals per source.
3. If anything new landed, commits to a branch
   `cve-collect/<YYYY-MM-DD>` and opens a **draft PR** with a summary
   table + coverage block.
4. The PR can stay open as a working queue. Maintainers cherry-pick
   proposals into focused PRs as they get detection regex.

The collector is idempotent — running it daily is safe. Each source
script de-dupes against:

- `rules/**/*.yaml` (active rules)
- `proposals/**/*.yaml` (existing draft proposals, all subdirs)

So a re-run on the same day produces zero new files, and a re-run after
a new CVE drops produces exactly the new ones.

## Coverage report

`data/cve-collector/last-run.json` is written on every collector run with:

```json
{
  "run_date": "2026-05-12T06:00:00.000Z",
  "mode": "write",
  "sources": [
    { "id": "cisa-kev", "exit_code": 0, "proposals_on_disk": 3, ... },
    { "id": "ghsa",     "exit_code": 0, "proposals_on_disk": 47, ... }
  ],
  "coverage": {
    "total_cves_tracked": 142,
    "cves_with_active_rule": 23,
    "proposals_total": 138,
    "proposals_detection_ready": 47,
    "proposals_tracking_only": 91
  }
}
```

The website pulls this file to render the "ATR tracks N AI CVEs" stat on
the coverage page.

## Promotion: proposal → rule

When a maintainer (or contributor) wants to promote a tracked CVE into
an active rule:

1. Open the proposal file in `proposals/<source>/<id>.proposal.yaml`.
2. Read the source advisory referenced under `_*_sync.html_url` (or
   `references.external`). Pull the PoC / payload.
3. Author `detection.conditions:` with the regex / compound condition.
4. Populate `test_cases.true_positives` with the actual payload strings,
   and `test_cases.true_negatives` with ≥3 benign lookalikes.
5. Run `npx tsx scripts/check-rules-safety.ts <proposal-path>`. Must
   produce 0 FP on the 432-skill benign corpus.
6. Move the file from `proposals/<source>/` to
   `rules/<category>/ATR-2026-NNNNN-<slug>.yaml` with the next free
   `ATR-2026-NNNNN` id.
7. The provenance block (`_cisa_kev_sync` / `_ghsa_sync` / etc.) stays
   in the rule file so we can trace the rule back to its origin advisory.

## Why this is in `proposals/` not `rules/`

ATR's engine in `src/engine.ts` only loads YAML files under `rules/`.
Anything in `proposals/` is invisible to consumers — exactly what you
want for half-finished detection logic. The validate.yml CI run does
schema-validate proposals, so a malformed proposal still fails fast, but
nobody is shipping a draft rule by accident.
