enrich-nse-fetch Design
Searches GitHub for Nmap NSE scripts that reference CVE/GHSA identifiers, stores them as first-class Exploit records, and creates ExploitCVE junctions linking to all matching CVEMetadata rows including aliases.
Data Source
GitHub Code Search API. The API has a hard 1000-result cap per query (REST docs; the 2023 Blackbird engine did not raise it and no pagination or cursor trick bypasses it), so every configured prefix is uniformly year-sharded. Default prefix set: CVE-,GHSA-,CNVD-,BDU-.
- For each prefix
P, emitextension:nse <P>YYYY-for YYYY in1999 .. current+1, ordered newest-first. - 4 prefixes × 29 years = 116 shards on the default set. Prefixes whose IDs don’t contain a year (e.g. GHSA-) return 0 results per shard — that’s acceptable overhead; the only scaling constraint is the GitHub PAT search rate limit (30 req/min), which a 40-min soft deadline comfortably absorbs.
- No per-prefix special-casing — adding a new prefix is a one-token config change.
Excludes files that mention “vulners” verbatim (noise source with bulk CVE references, not targeted exploits).
Processing Flow
- Acquire
BulkDataDumpTrackerlock (source = "enrich_nse", 8-min TTL) and start a heartbeat goroutine that re-stamps it every 5 minutes, so a 40-minute run cannot expire its own lock - Build sharded query list from
--prefixvalues (see Data Source) - For each shard (ordered newest-year-first for CVE): issue GitHub Code Search with
sort=indexed&order=descso freshly indexed files come first. Paginate up to 10 pages × 100 results. Break out of the shard on the first already-processed file (SHA match) since everything indexed earlier was seen in a prior run. Also stops early on soft deadline or--limit. - Per file:
- Compare git blob SHA to stored
BulkDataDumpTrackerentry → skip if unchanged - Fetch full file content via GitHub REST API
- Extract vuln IDs via the configurable
--prefixflag (defaultCVE-,GHSA-,CNVD-,BDU-).CVE-andGHSA-use strict format regexes; any other prefix falls back toPREFIX + 2+ chars + at least one more hyphenated segment, which rejects bare words likeCVE-SEARCH. - Strip Lua comments → SHA256 hash for content-based dedup (
Exploit.bodyContentHash) - Get first commit date (≤ 2 API calls: most-recent + last-page). Always fetched, even when the content hash matches an existing
Exploit, because first-commit date is per-file — every fork or copy has its own git history. - Upload raw (un-stripped) content to S3 at
nmap-nse/{owner}/{repo}/{path}(the.nsesuffix is trimmed) - Upsert
Exploitrecord (source=nmap-nse, platform=network, category=exploit) - Resolve CVE IDs + aliases → insert
ExploitCVEjunctions - Mark the file processed in
BulkDataDumpTrackerunderenrich_nse_file:{owner}/{repo}/{path}with the blob SHA
- Compare git blob SHA to stored
- Release lock
Files that mention vulners verbatim, and files from which no vuln ID could be
extracted, are still marked processed — so they are not re-fetched — but produce
no Exploit row.
Deduplication
- Blob SHA (via
BulkDataDumpTracker.sha256): skips unchanged files entirely, zero API calls - Content hash (
Exploit.bodyContentHash): SHA256 of comment-stripped content; a match means the file is not counted as a new exploit, though theExploitrow is still upserted with the current path’s metadata
Rate Limiting
- Code Search: 30 req/min authenticated → soft cap at 10 pages, sleep-to-reset if remaining < 5
- REST API (content + commits): 5000 req/hour → max 3 calls per new file (content + up to 2 commit-list pages); existing files skipped
RateLimitErrorandAbuseRateLimitErrorboth sleep to the reset instant and retry the same page
Tables Written
Exploit— one row per distinct NSE script (source=nmap-nse), withbodyContentHash,fileSize,r2Bucket/r2Key,originalUrl(GitHub HTML URL) andsourceArchiveUrl(raw URL)ExploitCVE— one junction per(cveId, source)pair resolved fromCVEMetadataand itsCVEAliasneighbours, so an NSE script naming a GHSA also links the aliased CVEBulkDataDumpTracker— theenrich_nsemaster lock plus oneenrich_nse_file:{owner}/{repo}/{path}row per seen file
No CVEMetadata, CVEMetadataReferences or CVEAlias rows are written — this
task only reads the alias graph.
Flags
| Flag | Default | Description |
|---|---|---|
--prefix | CVE-,GHSA-,CNVD-,BDU- | Comma-separated vuln ID prefixes to extract from NSE content — also drives the search shards |
--limit | 0 | Max NSE files to process per run; 0 means unlimited (for ECS runs where the soft deadline bounds work). Local smoke tests can pass e.g. --limit=50. |
Environment Variables
| Variable | Required | Description |
|---|---|---|
GITHUB_PAT | Yes | Personal Access Token for GitHub API authentication — the binary exits 1 without it |
DATABASE_URL | Yes | PostgreSQL write connection string |
DATABASE_URL_READ | No | Read replica connection string |
S3_BUCKET_NAME | No | S3 bucket for raw NSE file storage |
EXPECTED_DURATION_MINUTES | No | Task budget; the soft deadline is budget − 10 minutes. Unset (local runs) falls back to a fixed 50-minute soft deadline. ECS sets 50, so the effective soft deadline is 40 minutes. |
S3 Persistence
- Archive: ⚠ Not yet implemented — requires record reconstruction (DB row → canonical JSON).
- Quarantine: ⚠ Not yet implemented — same reason.
- Likely reasons when implemented:
fetch-error,decode-error
This is an enrichment processor; it reads from CVEMetadata rather than ingesting raw feeds, so there is no original payload to archive verbatim. See S3 Persistence Contract § Processors whose unit-of-work is not a file.