Concelier CVE & KEV Connector Operations

Last updated: 2026-06-22

Audience: Stella Ops operators rolling out and monitoring the Concelier CVE and CISA KEV connectors.

This playbook covers the steps required to roll out and monitor the CVE (source:cve:*, cvelistV5 / CVE Services) and CISA KEV (source:kev:*) connectors across environments.

1. CVE Connector (source:cve:*)

1.1 Prerequisites

concelier:
  sources:
    cve:
      baseEndpoint: "https://cveawg.mitre.org/api/"
      cveListV5Endpoint: "https://raw.githubusercontent.com/CVEProject/cvelistV5/main/cves/"
      apiOrg: "ORG123"
      apiUser: "user@example.org"
      apiKeyFile: "/var/run/secrets/concelier/cve-api-key"
      cveListV5BaselineZipPath: "/var/lib/stellaops/cve-baseline/2026-06-19_all_CVEs_at_midnight.zip"
      cveListV5BaselineZipSha256: "sha256:<64-hex-digest>"
      seedDirectory: "./src/__Tests/__Datasets/seed-data/cve"
      pageSize: 200
      maxPagesPerFetch: 5
      initialBackfill: "30.00:00:00"
      requestDelay: "00:00:00.250"
      failureBackoff: "00:10:00"

Note. Store the API key outside source control. When using apiKeyFile, mount the secret file into the container/host; alternatively supply apiKey via CONCELIER_SOURCES__CVE__APIKEY.

Note. When credentials are not available, the connector drains the configured local cvelistV5 baseline zip first, then reads public cvelistV5 deltaLog.json and changed CVE-*.json records. Configure seedDirectory only for offline/air-gap fallback material after those paths.

License / supply-chain note. Stella Ops does not vendor the cvelistV5 baseline archive in the repository. Operators provide it from their approved mirror/offline-kit process and should pin cveListV5BaselineZipSha256; a configured digest mismatch fails closed before ingesting documents.

1.2 Smoke Test (staging)

  1. Deploy the updated configuration and restart the Concelier service so the connector picks up the public endpoint or optional credentials.
  2. Trigger one end-to-end cycle:
    • Concelier CLI: run stella db fetch --source cve --stage fetch, then --stage parse, then --stage map.
    • REST fallback: POST /jobs/run { "kind": "source:cve:fetch", "chain": ["source:cve:parse", "source:cve:map"] }
  3. Observe the following metrics (exported via OTEL meter StellaOps.Concelier.Connector.Cve):
    • cve.fetch.attempts, cve.fetch.success, cve.fetch.documents, cve.fetch.failures, cve.fetch.unchanged
    • cve.parse.success, cve.parse.failures, cve.parse.quarantine
    • cve.map.success
  4. Verify Prometheus shows matching concelier.source.http.requests_total{concelier_source="cve"} deltas (list vs detail phases) while concelier.source.http.failures_total{concelier_source="cve"} stays flat.
  5. Confirm either the baseline summary log CVE List V5 baseline zip stored X document(s), the public cvelistV5 summary log CVE List V5 fetch stored X CVE document(s), or the authenticated CVE Services summary log CVEs fetch window ... pages=X detailDocuments=Y detailFailures=Z appears once per fetch run.
  6. Verify the PostgreSQL advisory store (schema vuln) contains fresh CVE advisories (advisoryKey prefix cve/) and that the source cursor (source_states table) advanced.

1.3 Production Monitoring

1.4 Staging smoke log (2025-10-15)

While Ops finalises long-lived CVE Services credentials, we validated the connector end-to-end against the recorded CVE-2024-0001 payloads used in regression tests:

The Grafana pack cve-kev-grafana-dashboard.json has been imported into staging so the panels referenced above render against these counters once the live API keys are in place.

2. CISA KEV Connector (source:kev:*)

2.1 Prerequisites

concelier:
  sources:
    kev:
      feedUri: "https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json"
      requestTimeout: "00:01:00"
      failureBackoff: "00:05:00"

2.2 Schema validation & anomaly handling

The connector validates each catalog against Schemas/kev-catalog.schema.json. Failures increment kev.parse.failures_total{reason="schema"} and the document is quarantined (status Failed). Additional failure reasons include download, invalidJson, deserialize, missingPayload, and emptyCatalog. Entry-level anomalies are surfaced through kev.parse.anomalies_total with reasons:

ReasonMeaning
missingCveIdCatalog entry omitted cveID; the entry is skipped.
countMismatchCatalog count field disagreed with the actual entry total.
nullEntryUpstream emitted a null entry object (rare upstream defect).

Treat repeated schema failures or growing anomaly counts as an upstream regression and coordinate with CISA or mirror maintainers.

2.3 Smoke Test (staging)

  1. Deploy the configuration and restart Concelier.
  2. Trigger a pipeline run:
    • CLI: run stella db fetch --source kev --stage fetch, then --stage parse, then --stage map.
    • REST: POST /jobs/run { "kind": "source:kev:fetch", "chain": ["source:kev:parse", "source:kev:map"] }
  3. Verify the metrics exposed by meter StellaOps.Concelier.Connector.Kev:
    • kev.fetch.attempts, kev.fetch.success, kev.fetch.unchanged, kev.fetch.failures
    • kev.parse.entries (tag catalogVersion), kev.parse.failures, kev.parse.anomalies (tag reason)
    • kev.map.advisories (tag catalogVersion)
  4. Confirm concelier.source.http.requests_total{concelier_source="kev"} increments once per fetch and that the paired concelier.source.http.failures_total stays flat (zero increase).
  5. Inspect the info logs Fetched KEV catalog document … pendingDocuments=… and Parsed KEV catalog document … entries=… — they should appear exactly once per run and Mapped X/Y… skipped=0 should match the kev.map.advisories delta.
  6. Confirm PostgreSQL records exist for the catalog JSON (raw_documents and dtos tables in schema vuln) and that advisories with prefix kev/ are written.

2.3.1 Cached Replay Recovery

Use this recovery when KEV fetches already succeeded against an unchanged upstream catalog, but a mapper or persistence fix needs to rebuild source_id bindings or canonical/source-edge projections from the latest cached catalog.

  1. Trigger a forced parse replay against the latest cached KEV document:
    • POST /jobs/source:kev:parse
    • Body: {"trigger":"api","parameters":{"force":"true"}}
  2. Wait for the parse run to finish with status=Succeeded via GET /jobs/{runId}.
  3. Trigger a forced map replay against the same cached document set:
    • POST /jobs/source:kev:map
    • Body: {"trigger":"api","parameters":{"force":"true"}}
  4. Wait for the map run to finish with status=Succeeded via GET /jobs/{runId}.
  5. Recheck GET /api/v1/advisory-sources/kev/freshness and confirm the KEV rollups are repopulated:
    • sourceDocumentCount > 0
    • canonicalAdvisoryCount > 0
    • cveCount > 0
    • totalAdvisories > 0
  6. Verify PostgreSQL recovery state:
    • vuln.advisories rows with advisory keys prefixed kev/ have non-null source_id
    • vuln.advisory_source_edge contains rows for the KEV source

This path reuses the most recent cached KEV catalog and does not wait for CISA to publish a newer document. It is the required operator recovery step after shipping KEV mapper or persistence fixes onto a host that already cached the current catalog.

2.4 Production Monitoring

2.5 Known good dashboard tiles

Add the following panels to the Concelier observability board:

MetricRecommended visualisation
rate(kev_fetch_success_total[30m])Single-stat (last 24 h) with warning threshold >0
rate(kev_parse_entries_total[1h]) by catalogVersionStacked area – highlights daily release size
sum_over_time(kev_parse_anomalies_total[1d]) by reasonTable – anomaly breakdown (matches dashboard panel)
rate(cve_map_success_total[15m]) vs rate(kev_map_advisories_total[24h])Comparative timeseries for advisories emitted

3. Runbook updates