eukev-json-processor
Status: Live Source: enisaeu/CNW — advisories/eukev/eukev.json Type:
json(single flat array, no pagination, no auth) Source slug: n/a — writes noCVEMetadata.Kev.source = "enisa"; tracker source isenisa_eukev. Schedule: Runs daily at 08:30 UTC (cron(30 8 * * ? *)).
Overview
The EU counterpart to the CISA KEV catalog. ENISA publishes its own
known-exploited-vulnerabilities list inside the Cybersecurity News Watch (CNW)
repository; this processor lands it in the same Kev table the CISA catalog uses, under
source='enisa', so every KEV consumer (SSVC exploitation input, KEV urgency badges,
kev-watch, the KEV API) picks up EU-designated entries with no additional plumbing.
Three sources currently coexist in Kev: CISA, enisa, and vulnetix.
Overlap with CISA is partial and deliberate — ENISA lists vulnerabilities exploited against EU targets that CISA has not added, and vice versa. Losing this processor means EU-only exploitation designations silently disappear from urgency scoring.
Records produced
| Condition | Records |
|---|---|
Every entry with a non-empty cveID | Kev row, upsert on ("cveID","source") with source='enisa' |
| Every run | BulkDataDumpTracker row enisa_eukev (sha256 written empty — freshness is time-based) |
| Every stored entry | S3 archive; failures quarantined (see § S3 Persistence) |
Entries with an empty cveID are skipped outright. Records are written 500 at a time,
each batch in one transaction with a per-entry SAVEPOINT sp_kev.
Field mapping
ENISA’s schema does not line up 1:1 with CISA’s, so several Kev columns carry
ENISA-specific semantics. Consumers that assume CISA semantics for these columns will
misread source='enisa' rows.
Kev column | ENISA source | Note |
|---|---|---|
dateAdded | dateReported | COALESCEd to fetchedAt on first insert only; on conflict the stored value is preserved when the upstream date is unparseable, so a scheduled run can never roll the date forward to the run day |
dueDate | dateReported | ENISA publishes no remediation deadline — dueDate is set to the same value as dateAdded, not a CISA-style compliance deadline |
requiredAction | patchedSince, or the literal "Apply vendor patch when available" | |
knownRansomwareCampaignUse | threatActorsExploiting | Holds an actor name, not CISA’s "Known"/"Unknown" enum |
notes | notes + "Exploitation type: " + exploitationType | joined with "; " |
cwesJSON | cwes | comma-separated string split into a JSON array |
catalogVersion, catalogReleaseDate | — | always NULL; ENISA publishes no catalog version |
euvdID | — | not persisted — the field is parsed off the wire but never written to any column |
Every string field is normalised through normalizeDash, which maps the literal "-"
(ENISA’s “not applicable” marker) and whitespace-only values to empty.
parseEUKEVDate tries 2006/01/02, 2006-01-02, 02/01/2006, 02/01/06 in that
order — four-digit-year layouts first so 2026/07/10 can never be misread as
day-first. "", "-" and "TBC" are treated as absent, and the caller deliberately
does not substitute time.Now(), which is what would otherwise rewrite every
record’s date on every run.
Freshness gate
The run is skipped with task.no_work when
now - tracker.lastProcessedAt < tracker.frequency × 1000.
⚠ Known defect — the daily run skips every second day.
db.UpsertTrackerhardcodesfrequency = 86400and writeslastProcessedAtat the end of a successful run. The next 08:30 fire therefore seeselapsed < 86400000and exits early without fetching. The freshness window must be strictly shorter than the schedule interval. (cisa-kev-json-processorescapes this only because its live tracker row predates the helper and still carries21600.)
Deadline
EXPECTED_DURATION_MINUTES − 10, falling back to a hardcoded 15 minutes when the env
var is unset — the state the just go-eukev-json-backfill recipe creates. At ~35
entries the bound is never reached, but it contradicts the backfill-has-no-deadline
rule; the deadline should be left at its zero value when the env var is absent.
Local execution
just go-eukev-json-backfill # local DB
just go-eukev-json-backfill prod # .env.production
S3 Persistence
- Archive path:
eukev/files/{sha256}/{filename}✓ - Quarantine path:
failed-feeds/eukev-json-processor/{YYYY-MM-DD}/{reason}/{filename}✓ - Failure reasons emitted:
parse-error,store-error
Uses s3client.Uploader from internal/s3client/uploader.go. Skipped when S3_BUCKET_NAME is unset (local dev).
See the S3 Persistence Contract for the full reason taxonomy.