CERT BUND CSAF Processor

Status: Live Source: BSI CERT-BUND CSAF provider Type: csaf (CSAF 2.0 JSON, one document per advisory, plain-text index) Source slug: certbund Schedule: Daily at 13:30 UTC (cron(30 13 * * ? *)).

Data Source

  • Publisher: Bundesamt fur Sicherheit in der Informationstechnik (BSI), Germany
  • Format: CSAF 2.0 JSON advisories
  • Index: https://wid.cert-bund.de/.well-known/csaf/white/index.txt
  • Advisory base: https://wid.cert-bund.de/.well-known/csaf/white/
  • Volume: 12,815 advisory paths in the index as of 2026-08-06 (2013-present), each containing 1–30+ CVEs; ~39.5k CVEMetadata rows in production
  • Update frequency: Multiple advisories per day; existing advisories may be revised
  • Language: German (de)

Index Format

Plain text file with one relative path per line:

2013/wid-sec-w-2023-0505.json
2025/wid-sec-w-2025-0001.json
2025/wid-sec-w-2025-0864.json

Each path is appended to the base URL to construct the full advisory URL.

CSAF Advisory Structure

Standard CSAF 2.0 with these CERT BUND-specific patterns:

  • Publisher role: other (BSI is a government CERT, not a vendor)
  • Language: Advisory titles and notes are in German (lang: "de-DE")
  • Product status: Uses known_affected and last_affected
  • No CVSS scores: Advisories do not include CVSS score data
  • No CWEs: Advisories do not include CWE identifiers
  • Product tree: Hierarchical vendor/product branches with CPE identifiers; vendor extracted dynamically
  • Multi-vendor: A single advisory may cover products from multiple vendors (IBM, Oracle, Red Hat, etc.)
  • Aggregate severity: German text values (“niedrig”, “mittel”, “hoch”, “kritisch”)

Data Mapping

CSAF FieldCVEMetadata Field
vulnerabilities[].cvecveId
"certbund"source
"5.0"dataVersion
"PUBLISHED"state
tracking.initial_release_datedatePublished (Unix seconds)
tracking.current_release_datedateUpdated (Unix seconds)
(empty)vectorString
document.titletitle
Advisory URLsourceAdvisoryRef
Vendor from product treeaffectedVendor
First affected product nameaffectedProduct
SHA256 of advisory JSONsourceFileHash
Full CSAF documentrawDataJSON

Relations

  • CVEDescription: Vulnerability notes (category: description/summary/general), fallback to document notes; lang: "de"
  • CVEMetadataReferences: Advisory URL + document refs + vulnerability refs + remediation URLs
  • CVEMetric: Mapped from vulnerabilities[].scores[] when present (v2/v3/v4, deduplicated by vector) — in practice almost always empty because CERT BUND does not publish CVSS. The shared pipeline still writes one derived containerType="vulnetix" CVSS v4.0 row per record from the German description text.
  • CVEProblemType: Mapped from vulnerabilities[].cwe / cwes[] when present — in practice empty, as CERT BUND does not publish CWE identifiers.
  • CVEAffected: Products from product_status.known_affected + last_affected, vendor extracted from product tree branches

Incremental Processing

  1. Fetch index.txt, compute SHA256
  2. Compare against BulkDataDumpTracker key certbund_csaf_index
  3. If unchanged and --force not set, exit early
  4. Load per-CVE sourceFileHash resume set from DB
  5. Per advisory: compute SHA256, skip if hash matches existing record
  6. Update tracker on completion

Flags

FlagDefaultDescription
--forcefalseReprocess all advisories regardless of hash
--limit0Max advisories to fetch (0 = all)
--workers10Concurrent advisory fetch workers (shared 5 req/s ticker)

Records produced

ConditionRecords
Every vulnerabilities[] entry carrying a cveCVEMetadata (source="certbund", dataVersion="5.0", state="PUBLISHED"), CVEDescription (lang="de"), CVEMetadataReferences, CVEAffected (+ Dependency/PackageVersion enrichment via the shared pipeline), CVEAlias same-cveId cross-source edges
Description presentOne derived CVEMetric (containerType="vulnetix", cvssV4_0) from cvss.DeriveV4FromDescription
Advisory carries scores[]CVEMetric rows per vector — rare: BSI’s CSAF documents omit CVSS almost entirely (10 of 39,451 production rows have a vectorString)
S3 upload succeededArtifact + Link, and CVEMetadata.fileLinkId pointing at it
Advisory returns 403/404/410A minimal CVEMetadata row keyed by the WID advisory ID with state="DEFERRED" (cmd/certbund-csaf-processor/main.go:414-430)

Upstream has no CVSS and no CWE. A spot check of wid-sec-w-2026-2661.json shows vulnerabilities[] entries carrying only cve, product_status and release_date. The mapper does handle scores[] and cwe/cwes (internal/certbund/mapper.go:126-194) — the data simply is not published, so near-zero CVSS/CWE coverage for this source is upstream-driven, not a mapping defect. The value of this feed is the German-language advisory text, the vendor/product tree (34,436 of 39,451 rows carry an affectedProduct), and the vendor-fix reference URLs.

Non-canonical archive key. The S3 object is written to certbund/advisories/{sha256}/{cveId}.json (cmd/certbund-csaf-processor/main.go:359), not the {source}/files/{sha256}/{filename} path the S3 Persistence Contract requires (and which the generated section below quotes). Quarantine paths are contract-compliant.

Files

PathPurpose
cmd/certbund-csaf-processor/main.goEntry point, fetch/parse/batch loop
cmd/certbund-csaf-processor/s3.goS3 artifact upload helper
internal/certbund/types.goCSAF 2.0 type definitions
internal/certbund/parser.goIndex and advisory parsing
internal/certbund/mapper.goCSAF -> CVESourceData conversion

Local Development

# Direct Go run (fastest for testing)
just go-certbund-csaf-backfill

# With production DB
just go-certbund-csaf-backfill TARGET=prod

# Force reprocess all
just go-certbund-csaf-backfill FORCE=true

# Limit to 10 advisories
just go-certbund-csaf-backfill LIMIT=10

# Container build (mirrors ECS)
just certbund-csaf-processor

Deployment

  • ECS schedule: Daily at 13:30 UTC (cron(30 13 * * ? *)) — cron_match = "30 13 * * *" in scripts/task-manager.toml
  • Resources: 512 CPU / 1024 MB memory, expected_duration_minutes = 60
  • ECR tag: go-certbund-csaf-processor-latest
  • Log group: /ecs/vdb-scheduler/go-certbund-csaf-processor

S3 Persistence

  • Archive path: certbund/files/{sha256}/{filename}
  • Quarantine path: failed-feeds/certbund-csaf-processor/{YYYY-MM-DD}/{reason}/{filename}
  • Failure reasons emitted: parse-error, schema-violation

Uses s3client.Uploader from internal/s3client/uploader.go. Skipped when S3_BUCKET_NAME is unset (local dev).

flowchart LR SRC[Source feed] --> PROC[certbund-csaf-processor] PROC -->|success| ARCHIVE[("S3: certbund/files/{sha256}/{filename}")] PROC -->|failure| Q[("S3: failed-feeds/certbund-csaf-processor/{date}/{reason}/{filename}")] PROC --> DB[(PostgreSQL)]

See the S3 Persistence Contract for the full reason taxonomy.