WIP: feat: support CISA KEV catalog ingestion and expose known-exploited data - #2553
WIP: feat: support CISA KEV catalog ingestion and expose known-exploited data#2553waldemar-kindler wants to merge 14 commits into
Conversation
Reviewer's GuideAdds end-to-end support for ingesting the CISA Known Exploited Vulnerabilities (KEV) JSON catalog into a new normalized table and exposes catalog entries on the vulnerability details API, including importer configuration, ingestion pipeline wiring, schema/migration, and tests. Sequence diagram for KEV catalog ingestion pipelinesequenceDiagram
participant ImportRunner
participant KevWalker
participant CisaKevServer
participant IngestorService
participant KevLoader
participant KnownExploitedVulnerabilityEntity
ImportRunner->>KevWalker: run_once_kev_catalog(kev_catalog, continuation)
KevWalker->>CisaKevServer: reqwest::get(source)
CisaKevServer-->>KevWalker: known_exploited_vulnerabilities.json
KevWalker->>IngestorService: ingest(body, Format::CisaKev, labels, None, Cache::Skip, tx)
IngestorService->>KevLoader: load(labels, KevCatalog, digests, tx)
KevLoader->>KnownExploitedVulnerabilityEntity: delete_many().filter(Source.eq(catalog_source))
KevLoader->>KnownExploitedVulnerabilityEntity: insert_many(batch.chunked())
KevLoader-->>IngestorService: IngestResult
IngestorService-->>KevWalker: IngestResult
KevWalker-->>ImportRunner: LastModified continuation
Sequence diagram for exposing KEV data on vulnerability detailssequenceDiagram
actor Client
participant VulnerabilityService
participant KnownExploitation
participant KnownExploitedVulnerabilityEntity
Client->>VulnerabilityService: VulnerabilityDetails::from_vulnerability_id(id, tx)
VulnerabilityService->>KnownExploitation: find_by_vulnerability(id, tx)
KnownExploitation->>KnownExploitedVulnerabilityEntity: Entity::find().filter(CveId.eq(id)).all(tx)
KnownExploitedVulnerabilityEntity-->>KnownExploitation: Vec<Model>
KnownExploitation-->>VulnerabilityService: Vec<KnownExploitation>
VulnerabilityService-->>Client: VulnerabilityDetails{ known_exploited, ... }
Entity relationship diagram for vulnerability and known-exploited-vulnerabilityerDiagram
vulnerability {
string id PK
}
known_exploited_vulnerability {
string source PK
string cve_id PK
date date_added
date due_date
string required_action
string known_ransomware_campaign_use
}
vulnerability ||--o{ known_exploited_vulnerability : cve_id
File-Level Changes
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
33cdea7 to
ba293e1
Compare
Add a known_exploited_vulnerability table (source-qualified, joined to vulnerability by CVE id without a foreign key), a KevLoader with full-sync semantics so entries removed from the catalog are removed from the database, and Format::CisaKev with JSON content detection. Relates to guacsec#2545 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add KevImporter configuration and a runner that fetches the KEV JSON feed, skipping unchanged content via the Last-Modified header, same as the CWE catalog importer. Register a disabled-by-default sample importer and regenerate the importer/openapi schemas. Relates to guacsec#2545 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add a KnownExploitation model carrying source, date added, due date, required action and ransomware campaign use, and include all catalog entries referring to a vulnerability in the details response. Relates to guacsec#2545 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Warn instead of silently dropping unparsable dateAdded/dueDate values, cover a minimal catalog entry with a malformed date in tests, and limit the KEV catalog download to 64 MiB in the importer. Relates to guacsec#2545 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Index creation must use .if_not_exists(), per the migration conventions. The table creation in the same migration already did. Assisted-by: Claude Code Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
reqwest::get does not fail on 404/500 responses, so an error page body would flow into ingestion and surface as a confusing parse error, with meaningless Last-Modified semantics. Fail fast instead. Assisted-by: Claude Code Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Record that known_ransomware_campaign_use deliberately stays a free-form string: the value set is owned by the upstream catalog and new values must not break ingestion. Assisted-by: Claude Code Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Previously the loader stored and replaced entries under a hardcoded "cisa" source, so two KEV importers with different source URLs would clobber each other's rows. The importer configuration gains an optional catalog field, forwarded to the loader via the catalog label, falling back to "cisa" when absent. Each load replaces only the entries of its own catalog source. Assisted-by: Claude Code Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Uploads a CVE document and the KEV catalog, then asserts the catalog entry is exposed in the vulnerability details, and that a vulnerability not listed in the catalog reports an empty list. Assisted-by: Claude Code Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ba293e1 to
9a8a989
Compare
time::Date serializes as a (year, ordinal) tuple by default, e.g. [2025, 198], while the OpenAPI spec declares format: date. Serialize through a format-description module so the wire format is "2025-07-17", as documented. Assisted-by: Claude Code Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #2553 +/- ##
==========================================
+ Coverage 72.73% 72.88% +0.15%
==========================================
Files 483 488 +5
Lines 30161 30387 +226
Branches 30161 30387 +226
==========================================
+ Hits 21938 22149 +211
+ Misses 6983 6982 -1
- Partials 1240 1256 +16 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Assisted-by: Claude Code Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
d08fa33 to
72b8e20
Compare
The walker had no test coverage. Add three tests using wiremock, the pattern already used for the nvd walker: a successful download and ingest including the Last-Modified skip on the second run, failure on an http error status, and storing entries under a configured catalog source. Assisted-by: Claude Code Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
before we start adding more entities to trustify (which btw enriching with CISA data is great !) - we need to first define an overarching strategy to associating metadata generically in trustify ... otherwise we are going to experience 'sprawl' with concomitant maintenance ... I started scratching out a few thoughts here https://gist.github.com/rh-jfuller/845c1b17fefb79c9dc3dc3df194ee6d9 - I would close this PR and lets have a conversation. The idea is that we would provide some enrichment internals in trustify to be able to develop such things. |
Thanks for sharing your thoughts! Generally I follow your sentiments. Having an entity / data field sprawl was also one of my concerns. Just a little push back from my side. In my mental model KEV is closer to the NVD and CWE than to Exploit Intelligence, EPSS or Conforma. The distinction I'd draw is feed vs. service call. EI, Conforma and (to a lesser degree) EPSS are request/response: they need context (which SBOM, which component), they need auth, they're per-job, and they benefit from exactly the shared plumbing you describe: Job queue, HTTP client with retry/backoff, error classification. KEV has none of that shape. It's a single public static JSON document with a stable schema . Pretty much like the CWE catalog importer. On process: I'm glad to have the conversation, and I'll convert this to whatever shape you land on. One request: Could we keep it open as a draft rather than closed? It's working end-to-end so I think it's useful as a concrete reference for what the data looks like while we discuss. If you'd rather have it closed, that's fine too. I'll reopen once there's a direction. |
|
@waldemar-kindler no worries, lets keep this PR open ... and your comments are spot on (will incorporate to enrichment doc working on) ... generally we want to identify primary entities and give them an importer/exporter treatment - everything else is enrichment. I can be convinced that your approach (eg. exploits as primary) works but want to have a few more conversations with others on the team to get alignment. |
Motivation:
Trustify ingests applicability data (VEX/CSAF product status: affected / not affected / fixed) and severity (CVSS), but carries no curated signal for whether a vulnerability is being actively exploited in the wild. The CISA KEV catalog is the authoritative source for that signal and complements the existing data: applicability narrows findings to what affects you; exploitation status ranks them by real-world urgency (and carries BOD 22-01 remediation deadlines). See #2545 for the full design discussion.
Relation to Exploit Intelligence (#2534): EI answers "is this CVE exploitable in this specific product?" via an external analysis service, per SBOM+CVE job. KEV answers "is this CVE exploited in the wild, globally?" from a public catalog — no external service, no product context, no auth. The signals are complementary.
Changes
Ingestor
known_exploited_vulnerabilitytable (m0002300), keyed by(source, cve_id)so additional catalog providers (e.g. VulnCheck KEV) can be supported later. Intentionally no FK tovulnerability: catalog entries and CVE documents can be ingested in any order; consumers join onvulnerability.id.Format::CisaKevwith JSON content detection (catalogVersion+vulnerabilities), wired through theDocumentDetector.KevLoaderwith full-sync semantics: each load atomically replaces the source's entry set, so entries removed from KEV (this has happened) are removed from the database. Unparsable dates are dropped with a warning instead of failing the catalog.Importer
kevimporter kind fetching the KEV JSON feed, skipping unchanged content via theLast-Modifiedheader (same pattern as the CWE catalog importer), with a 64 MiB download cap.API
known_exploitedentries (source, dateAdded, dueDate, requiredAction, knownRansomwareCampaignUse).Validation: exercised end-to-end against the live CISA feed (1,656 entries): import,
Last-Modifiedskip on re-run, and the full-sync semantics confirmed against a real-world removal (CISA has since dropped CVE-2025-6965 from the catalog, and the entry disappears on reload as intended).Open questions for reviewers:
known_exploited_vulnerabilitykeyed by(source, cve_id)— source-qualified for future providers. Reasonable, or would you prefer a CISA-only shape for now?vulnerability(ingestion-order independence, same asvulnerability.cweshandling) — acceptable?known_exploitedon the API vs. the new Exploit Intelligence module — happy to rename if this is too confusable.Non-goals (per #2545)
🤖 Generated with Claude Code
Summary by Sourcery
Introduce ingestion and exposure of known-exploited-vulnerability catalog data, starting with the CISA KEV feed, and surface catalog entries on vulnerability detail responses.
New Features:
Enhancements:
Tests:
Chores: