From 726198d72f068d51af30df7725fc1b79fed8ebe2 Mon Sep 17 00:00:00 2001 From: "sec-check[bot]" Date: Fri, 11 Sep 2026 19:53:21 -0400 Subject: [PATCH] [architect] test: drift gate for SBOM stream-id consumers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit STREAM_SPECS in fetch-github-sbom.js is the authoritative SBOM stream-id namespace, but PRODUCT_SPECS (fetch-github-images.js), OS_STREAM_SPECS (fetch-firehose.js), and IMAGE_CONFIGS (fetch-update-churn.js) join against it by raw string with nothing keeping the join resolvable. A typo or stream rename yields silently missing site data instead of a build failure — as already happened with bluefin-lts nvidiaSbomStreamId "bluefin-lts-nvidia", which rendered only through the gdx-lts fallback. Add scripts/sbom-stream-ids.test.js asserting every consumer sbomStreamId / streamId resolves to a declared stream, and every nvidiaSbomStreamId either resolves or carries a resolvable nvidiaSbomFallbackStreamId (the documented forward-reference pattern). Export OS_STREAM_SPECS from fetch-firehose.js so the gate can reach it. fetch-github-driver-versions.js and lib/card-feed-parser.mjs stay out of scope while in-flight PRs occupy them. Fixes #1231 Signed-off-by: sec-check[bot] --- scripts/fetch-firehose.js | 1 + scripts/sbom-stream-ids.test.js | 95 +++++++++++++++++++++++++++++++++ 2 files changed, 96 insertions(+) create mode 100644 scripts/sbom-stream-ids.test.js diff --git a/scripts/fetch-firehose.js b/scripts/fetch-firehose.js index b5c0d330..c507a7f9 100644 --- a/scripts/fetch-firehose.js +++ b/scripts/fetch-firehose.js @@ -455,6 +455,7 @@ if (require.main === module) { } module.exports = { + OS_STREAM_SPECS, buildOsApp, buildOsInfo, computePackageDiff, diff --git a/scripts/sbom-stream-ids.test.js b/scripts/sbom-stream-ids.test.js new file mode 100644 index 00000000..f5ab640a --- /dev/null +++ b/scripts/sbom-stream-ids.test.js @@ -0,0 +1,95 @@ +/** + * Drift gate for the SBOM stream-id namespace. + * + * scripts/fetch-github-sbom.js STREAM_SPECS is the authoritative list of SBOM + * stream ids written into static/data/sbom-attestations.json. Four consumer + * sites join against that namespace by raw string: + * + * - fetch-github-images.js PRODUCT_SPECS (sbomStreamId / nvidiaSbomStreamId + * / nvidiaSbomFallbackStreamId) + * - fetch-firehose.js OS_STREAM_SPECS (streamId) + * - fetch-update-churn.js IMAGE_CONFIGS (sbomStreamId) + * + * Nothing else keeps those strings resolvable: a typo or a renamed stream + * yields silently missing data on the published site instead of a build + * failure. (Happened in practice: bluefin-lts named nvidiaSbomStreamId + * "bluefin-lts-nvidia" months before that stream existed — it only rendered + * because resolveNvidiaVersion fell back to bluefin-gdx-lts.) + * + * Rules enforced here: + * 1. Every sbomStreamId / streamId must be a declared STREAM_SPECS id. + * 2. Every nvidiaSbomStreamId must be declared, OR the spec must carry a + * declared nvidiaSbomFallbackStreamId — the documented forward-reference + * pattern used while a dedicated nvidia SBOM stream does not exist yet. + * + * Two consumers are intentionally out of scope until their files are free of + * in-flight PRs and export their tables: fetch-github-driver-versions.js + * (SBOM_STREAM_PREFIX) and lib/card-feed-parser.mjs. Tracked in issue #1231. + */ + +const test = require("node:test"); +const assert = require("node:assert"); + +const { STREAM_SPECS } = require("./fetch-github-sbom"); +const { PRODUCT_SPECS } = require("./fetch-github-images"); +const { OS_STREAM_SPECS } = require("./fetch-firehose"); +const { IMAGE_CONFIGS } = require("./fetch-update-churn"); + +const STREAM_IDS = new Set(STREAM_SPECS.map((s) => s.id)); + +test("every PRODUCT_SPECS sbomStreamId is a declared SBOM stream", () => { + for (const spec of PRODUCT_SPECS) { + assert.ok( + STREAM_IDS.has(spec.sbomStreamId), + `product ${spec.id} names sbomStreamId "${spec.sbomStreamId}", which is not in fetch-github-sbom.js STREAM_SPECS`, + ); + } +}); + +test("every PRODUCT_SPECS nvidia stream resolves directly or via fallback", () => { + for (const spec of PRODUCT_SPECS) { + if (!spec.nvidiaSbomStreamId) continue; + if (STREAM_IDS.has(spec.nvidiaSbomStreamId)) continue; + assert.ok( + spec.nvidiaSbomFallbackStreamId && + STREAM_IDS.has(spec.nvidiaSbomFallbackStreamId), + `product ${spec.id} names nvidiaSbomStreamId "${spec.nvidiaSbomStreamId}", which is not in STREAM_SPECS and has no resolvable nvidiaSbomFallbackStreamId`, + ); + } +}); + +test("every PRODUCT_SPECS nvidiaSbomFallbackStreamId is a declared SBOM stream", () => { + for (const spec of PRODUCT_SPECS) { + if (!spec.nvidiaSbomFallbackStreamId) continue; + assert.ok( + STREAM_IDS.has(spec.nvidiaSbomFallbackStreamId), + `product ${spec.id} names nvidiaSbomFallbackStreamId "${spec.nvidiaSbomFallbackStreamId}", which is not in STREAM_SPECS`, + ); + } +}); + +test("every OS_STREAM_SPECS streamId is a declared SBOM stream", () => { + for (const spec of OS_STREAM_SPECS) { + assert.ok( + STREAM_IDS.has(spec.streamId), + `firehose OS stream ${spec.appId} names streamId "${spec.streamId}", which is not in fetch-github-sbom.js STREAM_SPECS`, + ); + } +}); + +test("every IMAGE_CONFIGS sbomStreamId is a declared SBOM stream", () => { + for (const spec of IMAGE_CONFIGS) { + assert.ok( + STREAM_IDS.has(spec.sbomStreamId), + `churn image ${spec.id} names sbomStreamId "${spec.sbomStreamId}", which is not in fetch-github-sbom.js STREAM_SPECS`, + ); + } +}); + +test("STREAM_SPECS ids are unique", () => { + assert.strictEqual( + STREAM_IDS.size, + STREAM_SPECS.length, + "duplicate ids in fetch-github-sbom.js STREAM_SPECS", + ); +});