binarly-rss-processor
Status: Live, running weekly, but structurally blind to CVE-less advisories Source: Binarly advisories RSS Type:
rssSource slug:binarlySchedule: weekly, Wednesdays 07:00 UTC —cron(0 7 ? * WED *)(terraform/go-schedules.tf:967), 256 CPU / 512 MB,expected_duration_minutes = 30
Overview
Binarly’s REsearch team finds vulnerabilities in UEFI firmware and reference
implementations (EDK2, vendor SMM drivers, bootloaders) — a layer almost nothing
else in the corpus covers. A firmware bug reaches thousands of OEM models
through a shared reference implementation, so the CVE↔affected-vendor mapping is
weak and Binarly’s own BRLY-YYYY-NNN identifier is often the only stable handle
on the finding for months before a CVE exists.
The processor reads the RSS feed and, for every CVE id it finds in an advisory’s
description, writes a CVEMetadata row under source="binarly" carrying the
advisory title, URL and full description.
Feed
| Property | Value |
|---|---|
| URL | https://www.binarly.io/advisories/rss.xml |
| Auth | None — fully public |
| Format | RSS 2.0 with media namespace (xmlns:media="http://search.yahoo.com/mrss/"), decoded by fully-qualified namespace at main.go:67 |
| Items | 211 with 159 distinct CVE ids (verified live 2026-08-06). A few advisories reference many CVEs; 16 reference none |
Parsing
| Field | Source |
|---|---|
| Advisory ID | Last path segment of <link>, uppercased (BRLY-2026-042) |
| Advisory URL | <link> (fallback: <guid>) |
| Title | <title> |
| Severity | Second token of <description> when it matches High|Medium|Low|Critical (main.go:46) |
| CVE IDs | All CVE-\d{4}-\d{4,} matches in <description>, deduped, order preserved |
| Published | <pubDate> parsed as RFC 1123Z → Unix seconds |
| Image URL | <media:content url="…"> (optional) |
Records produced
| Table | One row per | Notes |
|---|---|---|
CVEMetadata | CVE × advisory | source="binarly", cveId = the CVE, dataVersion="1.0", rawDataJSON carries advisory id / severity / description / image URL (main.go:230-239) |
CVEDescription | CVE × advisory | containerType="cna", lang="en", the full advisory description |
CVEMetadataReferences | CVE × advisory | type="advisory", referenceSource="binarly" |
CVEAlias | multi-CVE advisories only | (cveIDs[0], "binarly") → cveIDs[1:] (main.go:190) — see the defect below |
No CVEAffected and no CVEMetric rows: the affected-vendor set is not in the
feed, and the parsed severity word is only embedded in rawDataJSON, never
written as an other metric the way alas-rss-processor and
alinux2-rss-processor do. So Binarly severity is not queryable without parsing
the raw blob, and CVEMetadata.vectorString is NULL for all 164 rows (the feed
carries no CVSS vector, so that part is upstream-appropriate).
Known defects
Advisories with no CVE are discarded, which is most of the recent research.
main.go:153-155 does if len(cveIDs) == 0 { continue } before any store, so an
advisory that has not been assigned a CVE yet produces no record at all — not even
a BRLY-keyed one. 16 of the 211 live feed items are in that state, and they are
the newest ones: BRLY-2026-039 through BRLY-2026-042, all published
2026-07-01, all with a severity and a full technical description and no CVE. That
is why production’s newest datePublished for this source is 2025-12-12 even
though the feed carries July 2026 items and the weekly task ran successfully on
2026-08-05. Binarly publishing ahead of CVE assignment is the norm for firmware
research, so this is a systematic blind spot, not an edge case. acsc-rss-processor
solves the same problem by minting its own identifier; the same approach fits here
(BRLY-YYYY-NNN is already parsed and already uppercased).
Multi-CVE advisories generate false alias identity. main.go:190 calls
db.InsertAliases(tx, cveIDs[0], "binarly", cveIDs[1:]), which asserts that the
sibling CVEs are the same vulnerability as the first. They are not — a firmware
advisory routinely covers several distinct bugs. db.InsertAliases has bundle
suppression for exactly this, but it only fires when the alias list holds more
than one CVE, so a 2-CVE advisory slips straight through. Production holds 13
such false CVE↔CVE edges under discoveredFrom='binarly', e.g.
CVE-2024-10237 ↔ CVE-2025-7937.
Two smaller ones:
db.InsertAliasesis called only whenlen(cveIDs) > 1, so single-CVE advisories never trigger the same-cveIdcross-source backfill that AGENTS.md requires on every store. Production has 0 same-cveIdedges from this source.- A partial failure is reported as success. The per-CVE errors inside the batch
transaction are logged and counted but the closure returns
nil(main.go:177-197), so an advisory whose every CVE failed to store is still marked processed, archived to S3 and added to the in-memory resume set.
Flags
| Flag | Default | Description |
|---|---|---|
--all | false | Reprocess all advisories, not just new ones |
--limit | 0 | Maximum advisories to process per run (0 = unlimited) |
Unlike acsc-rss-processor, --all is safe here: the identifier is the CVE, not
a minted sequence number, so reprocessing re-upserts rather than duplicating.
Incremental strategy
On startup, load every sourceAdvisoryRef for source='binarly' into a set
(loadProcessedAdvisories, main.go:273). Per advisory: if the URL is in the set
and --all is false, skip; on success, add it. Note the ordering at
main.go:151-159 — the CVE-less skip happens before the resume check, so those
16 advisories are re-evaluated and re-dropped every run.
The soft deadline default is unconditional: main.go:91 uses 10 minutes when
EXPECTED_DURATION_MINUTES is unset and mins − 10 when it is set, and
main.go:137 then stops 5 minutes earlier still. At the shipped 30-minute budget
that leaves 15 minutes; with no env var it leaves 5, so a local --all run over a
larger feed would truncate silently.
Key files
| File | Purpose |
|---|---|
scripts/go-processors/cmd/binarly-rss-processor/main.go | The whole processor (422 lines, no internal package) |
schemas/binarly_rss_advisory.schema.json | JSON Schema Draft 7 for the parsed advisory object |
S3 Persistence
Compliant. The generator printed “Not used” here because
scripts/docs/s3-status.yaml recorded status: none for this slug; that entry
was wrong (cf. ORCH-09) and has been corrected.
- Archive path:
binarly/files/{sha256}/{BRLY-id}.json—uploader.ArchiveRecord(ctx, source, payload)atmain.go:204, success path only. The filename falls back to the first CVE id when the BRLY id cannot be parsed (main.go:166-169). - Quarantine path:
failed-feeds/binarly-rss-processor/{YYYY-MM-DD}/store-error/{BRLY-id}.json—main.go:199. - Failure reasons emitted:
store-erroronly.parse-erroris not emitted — a feed whose XML fails to unmarshal exits 1 atmain.go:124-128without writing the body anywhere.
Skipped entirely when S3_BUCKET_NAME is unset — s3client.NewFromEnv returns a
nil uploader and both methods are nil-receiver no-ops.