binarly-rss-processor

Status: Live, running weekly, but structurally blind to CVE-less advisories Source: Binarly advisories RSS Type: rss Source slug: binarly Schedule: 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

PropertyValue
URLhttps://www.binarly.io/advisories/rss.xml
AuthNone — fully public
FormatRSS 2.0 with media namespace (xmlns:media="http://search.yahoo.com/mrss/"), decoded by fully-qualified namespace at main.go:67
Items211 with 159 distinct CVE ids (verified live 2026-08-06). A few advisories reference many CVEs; 16 reference none

Parsing

FieldSource
Advisory IDLast path segment of <link>, uppercased (BRLY-2026-042)
Advisory URL<link> (fallback: <guid>)
Title<title>
SeveritySecond token of <description> when it matches High|Medium|Low|Critical (main.go:46)
CVE IDsAll 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

TableOne row perNotes
CVEMetadataCVE × advisorysource="binarly", cveId = the CVE, dataVersion="1.0", rawDataJSON carries advisory id / severity / description / image URL (main.go:230-239)
CVEDescriptionCVE × advisorycontainerType="cna", lang="en", the full advisory description
CVEMetadataReferencesCVE × advisorytype="advisory", referenceSource="binarly"
CVEAliasmulti-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.InsertAliases is called only when len(cveIDs) > 1, so single-CVE advisories never trigger the same-cveId cross-source backfill that AGENTS.md requires on every store. Production has 0 same-cveId edges 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

FlagDefaultDescription
--allfalseReprocess all advisories, not just new ones
--limit0Maximum 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

FilePurpose
scripts/go-processors/cmd/binarly-rss-processor/main.goThe whole processor (422 lines, no internal package)
schemas/binarly_rss_advisory.schema.jsonJSON 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}.jsonuploader.ArchiveRecord(ctx, source, payload) at main.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}.jsonmain.go:199.
  • Failure reasons emitted: store-error only. parse-error is not emitted — a feed whose XML fails to unmarshal exits 1 at main.go:124-128 without 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.