Red Hat Security Errata (RHSA) RSS Processor — Design
Overview
Fetches Red Hat Security Errata advisories from the public RSS 2.0 feed
(https://security.access.redhat.com/data/meta/v1/rhsa.rss) and creates first-class
CVEMetadata rows (source=rhsa) for every CVE ID embedded in each advisory.
Feed
| Property | Value |
|---|---|
| URL | https://security.access.redhat.com/data/meta/v1/rhsa.rss |
| Auth | None — fully public |
| Format | RSS 2.0 |
| Items | Rolling list of recent RHSA advisories |
Parsing
| Field | Source |
|---|---|
| Advisory ID | Last path segment of <link>, e.g. RHSA-2026:4672 |
| Advisory URL | <link> (fallback: <guid>) |
| Title | <title> (format: RHSA-YYYY:NNNN: Severity: product security update) |
| Severity | Title prefix: Critical, Important, Moderate, Low |
| Product | Title text after severity prefix, stripped of “security update” suffix |
| CVE IDs | Fetched from errata HTML page (RSS description contains no CVE IDs) |
| Published | <pubDate> parsed as RFC 2822 → Unix seconds |
Note: The RSS feed <description> does not contain CVE IDs — it only says
“CVE link(s) in the References section”. Each advisory requires a follow-up
HTTP fetch of the errata page HTML to extract CVE IDs via regex.
Storage
No new tables or columns. All tables already exist.
| Table | Rows inserted |
|---|---|
CVEMetadata | One per CVE per advisory; source="rhsa", dataVersion="5.1" |
CVEDescription | One per CVE per advisory; containerType="cna", lang="en" |
CVEMetadataReferences | One per CVE per advisory; type="advisory", referenceSource="rhsa" |
CVEAffected | One per CVE per advisory; vendor="Red Hat", product from title |
CVEMetric | One per CVE per advisory (when severity present); metricType="other", otherType="rhsa-severity" |
CVEAlias | For multi-CVE advisories: (cveIDs[0], "rhsa") → each sibling; conflict = DO NOTHING |
Incremental Strategy
On startup, load all sourceAdvisoryRef values from CVEMetadata where source='rhsa'
into a map[string]bool. Per advisory: if the URL is in the set and --force is false, skip.
After successful processing, add the URL to the in-memory set.
Flags
| Flag | Default | Description |
|---|---|---|
--force | false | Reprocess all advisories, not just new ones |
--limit | 0 | Maximum advisories to process per run (0 = unlimited) |
ECS Schedule
Runs every 6 hours (cron(0 */6 * * ? *)).
Key Files
| File | Purpose |
|---|---|
cmd/rhsa-rss-processor/main.go | Main processor binary |
internal/rhsa/types.go | RSS XML structs and Advisory type |
internal/rhsa/parser.go | Feed parsing, title/CVE extraction |
internal/rhsa/mapper.go | Advisory → DB row mapping utilities |
schemas/rhsa_rss_advisory.schema.json | JSON Schema Draft 7 for parsed advisory object |
Verification Queries
-- Count ingested RHSA records
SELECT COUNT(*) FROM "CVEMetadata" WHERE source = 'rhsa';
-- Sample records
SELECT "cveId", "title", "sourceAdvisoryRef", "datePublished"
FROM "CVEMetadata" WHERE source = 'rhsa' ORDER BY "datePublished" DESC LIMIT 10;
-- References
SELECT COUNT(*) FROM "CVEMetadataReferences" WHERE "referenceSource" = 'rhsa';
-- Affected products
SELECT COUNT(*) FROM "CVEAffected" ca
JOIN "CVEMetadata" cm ON ca."cveId" = cm."cveId" AND ca.source = cm.source
WHERE cm.source = 'rhsa';
-- Aliases
SELECT COUNT(*) FROM "CVEAlias" WHERE "primarySource" = 'rhsa';
AI Enrichment (aienrich)
After each advisory’s transaction commits, the processor builds a target
list from the CVE ids whose per-CVE processOneCVE returned nil (i.e.
landed cleanly), then fires aienrich.RunBatch. Wire-in:
cmd/rhsa-rss-processor/main.go, immediately after
uploader.ArchiveRecord. Each target carries
Source = "rhsa" and Aliases = adv.CVEIDs (the full multi-CVE bundle).
Four passes fire per CVE (no ghsapoc — self-gated to source = "github"):
| Pass | Persists to |
|---|---|
vulnetix.affected | CVEAffected.{modules, programFiles, programRoutines} |
vulnetix.attack | CVEAttackTechnique + children |
vulnetix.cwe | CVEProblemType (descriptionType = "CWE", derivedBy = "vulnetix") |
vulnetix.treesitter | CVETreeSitterQuery + CVETreeSitterCapture + CVETreeSitterPredicate |
RunBatch is invoked outside the parent transaction. See the aienrich overview for the full activity contract, environment variables, and operator runbook.
S3 Persistence
Not used. This processor does not currently archive payloads or quarantine failures to S3. Per the S3 Persistence Contract this is non-compliant — see the compliance matrix for the implementation roadmap.
Expected paths when implemented:
- Archive:
rhsa/files/{sha256}/{filename} - Quarantine:
failed-feeds/rhsa-processor/{YYYY-MM-DD}/{reason}/{filename} - Likely reasons:
parse-error