CERT-LV RSS Processor — Design
Overview
Fetches vulnerability advisories from the Latvian national CERT (CERT-LV / cert.lv) RSS feed. The feed republishes CERT/CC VU# notes and CISA alerts with English-language HTML descriptions containing structured sections.
Records stored under original CVE IDs (source=cert-lv). Items without
CVE IDs are skipped.
Feed
| Property | Value |
|---|---|
| URL | https://cert.lv/rss/attacks.xml |
| Auth | None — fully public |
| Format | RSS 2.0 |
| Language | lv (feed metadata), en (content) |
| Items | ~15 advisories |
Content Types
Two item types in the feed:
- CERT/CC VU# notes: Structured HTML with
<h3 id="...">sections (Overview, Description, Impact, Solution, Acknowledgements, References) and CVE IDs in<table>rows - CISA alerts: Simpler HTML with
<p>and<div>elements, sometimes containing CVE IDs in<a>links
Parsing
| Field | Source |
|---|---|
| Advisory ID | Last path segment of <link> (e.g., “294418”) |
| Title | <title> |
| Overview | <h3 id="overview"> section text |
| Description | <h3 id="description"> section text |
| Impact | <h3 id="impact"> section text |
| Solution | <h3 id="solution"> section text |
| CVE IDs | CVE-\d{4}-\d{4,} regex from full HTML |
| Reference URLs | <a href> from References section |
| Published | <pubDate> (format: YYYY-MM-DD HH:MM:SS) |
| Content Hash | SHA1(link|title|pubDate) |
Storage
No new tables or columns.
| Table | Rows inserted |
|---|---|
CVEMetadata | One per CVE ID; source="cert-lv" |
CVEDescription | One per CVE; lang="en" |
CVEMetadataReferences | Advisory URL + extracted reference URLs |
CVEAlias | Cross-references between CVEs in same advisory — only for two-CVE advisories; db.InsertAliases drops CVE↔CVE edges when a CVE-prefixed primary carries ≥2 CVE aliases (internal/db/cvealias.go:65). Same-cveId cross-source edges are always written. |
No Metrics (feed has no CVSS), no ProblemTypes (no CWE), no Affected (no product
data). CVEMetadata.affectedVendor / affectedProduct / vectorString are
therefore NULL for every cert-lv row — expected, given what the feed carries.
Incremental Strategy
URL-based dedup via sourceAdvisoryRef in CVEMetadata.
Flags
| Flag | Default | Description |
|---|---|---|
--all | false | Reprocess all advisories |
--limit | 0 | Maximum advisories (0 = unlimited) |
--force | false | Force reprocessing |
Error Alerting
Feed fetch or parse failure is fatal (notifier.Errored + os.Exit(1)). Per-CVE
transaction failures call notifier.RecordError and quarantine the advisory, but
the run always ends in notifier.Completed, which does not flush
RecordError messages (internal/notify/notify.go:213) — so those failures never
reach Slack.
ECS Schedule
Daily (cron(0 6 * * ? *)), schedule_enabled = true,
expected_duration_minutes = 30. Soft deadline is
EXPECTED_DURATION_MINUTES - 10 and the loop guard fires 5 minutes earlier
still, so an ECS run stops after 15 minutes. Unsetting
EXPECTED_DURATION_MINUTES for a local backfill falls back to a hard-coded
10-minute default (5-minute effective budget) rather than no deadline.
Key Files
| File | Purpose |
|---|---|
cmd/cert-lv-rss-processor/main.go | Main processor |
internal/certlv/types.go | RSS feed and advisory structs |
internal/certlv/parser.go | Feed parsing, HTML section extraction |
internal/certlv/mapper.go | Advisory → CVESourceData mapping |
schemas/cert_lv_rss_advisory.schema.json | JSON Schema Draft 7 |
S3 Persistence
- Archive path:
cert-lv/files/{sha256}/{filename}✓ - Quarantine path:
failed-feeds/cert-lv-rss-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.