enrich-coalition-cess Design
Enriches CVE records with Coalition ESS (Exploit Scoring System) data.
Overview
- Batch size: 50 CVEs per run
- Schedule: Every 30 minutes (
cron(*/30 * * * ? *)),expected_duration_minutes = 15 - Concurrency: Lock via
BulkDataDumpTracker(source=enrich_coalition_cess); exits if < 8 min since last run - State: Per-CVE rows in
BulkDataDumpTracker(source=enrich_coalition_cess:{cveId}) - Allowed prefixes:
CVE-(configured viaallowedPrefixesvar inmain.go; uses sharedenrichment.FindNextBatch) - Minted source slug:
coalition_cess(underscore —internal/coalitioncess/mapper.go:14), record idsCESS-{YYYY}-{N} - Soft deadline:
now + (EXPECTED_DURATION_MINUTES − 10), i.e. 5 minutes on the ECS budget, defaulting tonow + 60 minwhen the env var is unset. The loop breaks at that instant, so a run can stop part-way through its 50-CVE batch; unreached CVEs keep their claim row and are retried on a later run.
Processing Flow
- Check lock (
enrich_coalition_cess) — exit 0 if recent - Acquire lock
- Find next batch (50 CVEs,
CVE-prefix only viaenrichment.FindNextBatch): unprocessed first, then oldest reprocess - Claim batch (upsert per-CVE tracker rows)
- For each CVE:
FetchCVE: GETess-api.coalitioninc.com/cve/{cveId}- If 404: mark enriched, skip
- Upload raw JSON to S3 at
coalition-cess/{cveId}.json— a barePutObject, not the contracted content-addressed{source}/files/{sha256}/{filename}archive path, and there is no quarantine path - Create
Artifact+Linkrecords for the S3 object (Link.urlis the S3 key, not a resolvable URL) - Upsert
CessScore(probability_exploit_usage, score, variation, model version) — one row per(cve, dateString), so the table is a daily time series, not one row per CVE - Build
cvelistv5.Recordfrom Coalition data (CVSS → metrics, CESS → OtherMetric, exploits → references) - Store as CESS record via
StoreRecordWithSource()(source=coalition_cess, id=CESS-{YYYY}-{N}) - Insert
CVEAliaslinking CESS-* ↔ original CVE-* viadb.InsertAliases - Update
CVEMetadata.lastEnriched— noteMarkCVEEnriched(internal/enrichment/tracker.go:215) filters oncveIdonly, so it stamps the row of every source carrying that CVE, not just the CESS row
- Release lock
A CVE the Coalition API does not know (HTTP 404 → info == nil) is marked
enriched and skipped, so it is not retried on the next run.
Tables Written
CessScore— exploit probability scores (one row per CVE per day)CVEMetadata— CESS records (source=coalition_cess), plus alastEnrichedstamp on all sources’ rows for the CVECVEAlias— CESS ↔ CVE alias mappingsArtifact— S3 artifact metadataLink— artifact download linksCVEDescription— enriched descriptionsCVEMetadataReferences— exploit referencesCVEMetric— CVSS + CESS metricsCVEProblemType,CVEAffected— whatevercvelistv5.storeContainerderives from the synthesised recordBulkDataDumpTracker— run lock (enrich_coalition_cess) + per-CVE claim rows (enrich_coalition_cess:{cveId})
API
- Endpoint:
GET https://ess-api.coalitioninc.com/cve/{cveId} - Auth: None (public API)
- Rate limit: Unknown; browser UA used
S3 Persistence
- Archive: ⚠ Not yet implemented — requires record reconstruction (DB row → canonical JSON).
- Quarantine: ⚠ Not yet implemented — same reason.
- Likely reasons when implemented:
enrich-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.