Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/pages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ jobs:
path: static/data
key: github-data-${{ hashFiles('scripts/fetch-*.js') }}
restore-keys: |
github-data-sbom-
github-data-

- name: Install dependencies
Expand Down
27 changes: 15 additions & 12 deletions .github/workflows/update-sbom-cache.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ concurrency:
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}

permissions:
contents: write # for git commit + push
contents: read

jobs:
update-sbom-cache:
Expand Down Expand Up @@ -46,6 +46,15 @@ jobs:
if: steps.cache-node-modules.outputs.cache-hit != 'true'
run: npm ci

- name: Restore existing SBOM data cache (for incremental updates)
uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5
with:
path: static/data
key: github-data-sbom-${{ github.run_id }}
restore-keys: |
github-data-sbom-
github-data-

- name: Install cosign
uses: sigstore/cosign-installer@d7d6bc7722e3daa8354c50bcb52f4837da5e9b6a # v3.8.1

Expand All @@ -65,14 +74,8 @@ jobs:
GITHUB_TOKEN: ${{ secrets.PROJECT_READ_TOKEN }}
run: npm run fetch-sbom

- name: Commit updated cache
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add static/data/sbom-attestations.json
if git diff --cached --quiet; then
echo "No changes to sbom-attestations.json — skipping commit."
else
git commit -m "chore(sbom): update attestation cache $(date -u +%Y-%m-%d)"
git push
fi
- name: Save SBOM data cache
uses: actions/cache/save@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5
with:
path: static/data
key: github-data-sbom-${{ github.run_id }}
2 changes: 0 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@
/docs/contributing.md
/static/feeds/*.json
/static/data/*.json
# Seed file for SBOM attestation cache — must exist at build time
!/static/data/sbom-attestations.json

# Misc
.DS_Store
Expand Down
4 changes: 3 additions & 1 deletion docs/devcontainers.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,13 @@ docker --version

By default, the Dev Containers extension uses docker.
To switch to podman, make the following changes in Dev Containers settings:
```

```json
"dev.containers.dockerComposePath": "podman-compose"
"dev.containers.dockerPath": "podman"
"dev.containers.dockerSocketPath": "/run/user/1000/podman/podman.sock"
```

You can use `systemctl --user status podman.socket` to find the socket path corresponding to your user id. Or if you want to run podman in rootful mode, use `/run/podman/podman.sock` instead.

## Getting Started
Expand Down
6 changes: 5 additions & 1 deletion scripts/fetch-github-images.js
Original file line number Diff line number Diff line change
Expand Up @@ -210,8 +210,12 @@ function parseFedoraFromImageVersion(imageVersion) {

function latestFeedItem(feeds, source) {
if (!source) return null;
// stable-daily has no dedicated release feed — returning stable metadata would
// misrepresent daily-only images as stable releases. Return null so callers
// render unknown values instead.
if (source.stream === "stable-daily") return null;
const items = source.feed === "lts" ? feeds.lts.items : feeds.bluefin.items;
const stream = source.stream === "stable-daily" ? "stable" : source.stream;
const stream = source.stream;

const match = items.find((item) => {
const title = (item.title || "").toLowerCase();
Expand Down
43 changes: 36 additions & 7 deletions scripts/fetch-github-sbom.js
Original file line number Diff line number Diff line change
Expand Up @@ -159,8 +159,11 @@
try {
batch = await fetchJson(url);
} catch (err) {
console.warn(` Packages API page ${page} failed: ${err.message}`);
break;
// Rethrow so the caller (main) can preserve existing cache for this package
// rather than silently returning an empty result and clearing all releases.
throw new Error(
`Packages API page ${page} for ${org}/${pkg} failed: ${err.message}`,
);
Comment on lines +162 to +166
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The comment on line 162 states that rethrowing allows the caller to preserve the existing cache. However, the main function (at line 731) catches this error and sets the package versions to an empty array (allVersionsByPackage.set(key, [])). This causes processStream to proceed with zero releases, effectively clearing the cache for all streams associated with that package. To achieve the stated goal of preserving the cache, the main function should be updated to skip processing or handle the error without clearing the package entry.

}
if (!Array.isArray(batch) || batch.length === 0) break;
versions.push(...batch);
Expand Down Expand Up @@ -238,7 +241,7 @@
maxBuffer: 4 * 1024 * 1024,
});
stdout = result.stdout;
stderr = result.stderr;

Check warning on line 244 in scripts/fetch-github-sbom.js

View workflow job for this annotation

GitHub Actions / Build Docusaurus

'stderr' is assigned a value but never used. Allowed unused vars must match /^_/u
} catch (err) {
const msg = (err.stderr || err.message || "").toLowerCase();
// cosign exits non-zero when no attestation exists
Expand All @@ -254,16 +257,21 @@
error: "no attestation",
};
}
// Unexpected tooling/registry/auth failure — do NOT claim present: true.
// Callers use present: false (no attestation) vs present: null (error/unknown)
// to distinguish absence from verification failure.
return {
present: true,
present: null,
verified: false,
predicateType: null,
errorKind: "tooling",
error: String(err.stderr || err.message),
Comment on lines +264 to 268
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The use of present: null and the addition of errorKind: "tooling" are inconsistent with the AttestationResult interface defined in src/types/sbom.ts, where present is a boolean and errorKind is not defined. Furthermore, errorKind is currently discarded during the reconstruction of the attestation object in processStream (line 628). This will cause type errors during build-time checks and results in the loss of the error classification data. The AttestationResult interface and the processStream function should be updated to support these new fields.

};
}

// cosign outputs NDJSON (one JSON object per line)
const lines = (stdout + stderr)
// cosign outputs NDJSON (one JSON object per line) on stdout only;
// stderr carries status messages ("Verification OK") that must not be parsed.
const lines = stdout
.split("\n")
.map((l) => l.trim())
.filter((l) => l.startsWith("{"));
Expand Down Expand Up @@ -547,6 +555,12 @@
const dateStr = extractDateFromTag(normalised);
if (!dateStr) continue;

// Enforce canonical tag: only exact `<streamPrefix>-YYYYMMDD` is accepted.
// Non-canonical variants like lts-hwe-testing-20260331 would normalise to
// lts-20260331 and silently overwrite valid canonical entries.
const expectedCanonical = `${spec.streamPrefix}-${dateStr}`;
if (normalised !== expectedCanonical) continue;

found.push({
tag: normalised,
cacheKey: buildCacheKey(spec.streamPrefix, dateStr),
Expand Down Expand Up @@ -577,7 +591,18 @@
*/
async function processStream(spec, allVersionsByPackage, existing) {
const pkgKey = `${spec.org}/${spec.package}`;
const allVersions = allVersionsByPackage.get(pkgKey) || [];
// If the key is absent the Packages API fetch failed — preserve existing cache.
if (!allVersionsByPackage.has(pkgKey)) {
if (existing?.streams?.[spec.id]) {
console.log(
` ${spec.id}: Packages API unavailable — keeping existing cache`,
);
return existing.streams[spec.id];
}
// No existing cache to fall back to; return empty stream.
return { ...spec, releases: {} };
}
const allVersions = allVersionsByPackage.get(pkgKey);
const recentTags = findRecentTagsForStream(allVersions, spec);

console.log(
Expand Down Expand Up @@ -616,6 +641,9 @@
verified: attestation.verified,
predicateType: attestation.predicateType,
slsaType: SLSA_TYPE,
...(attestation.errorKind !== undefined && {
errorKind: attestation.errorKind,
}),
error: attestation.error,
};
}
Expand Down Expand Up @@ -714,7 +742,8 @@
console.log(` ${versions.length} versions fetched`);
} catch (err) {
console.error(` Failed to fetch versions for ${key}: ${err.message}`);
allVersionsByPackage.set(key, []);
// Do NOT set an empty array — leave the key absent so processStream
// detects the fetch failure and preserves the existing cache instead.
}
}

Expand Down
15 changes: 15 additions & 0 deletions src/types/sbom-attestations.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/**
* Type declaration for the SBOM attestation cache data file.
*
* static/data/sbom-attestations.json is generated at CI time by
* scripts/fetch-github-sbom.js and restored from GitHub Actions cache before
* the build runs. It is gitignored and will not exist during local `tsc`
* typechecks — this ambient declaration satisfies the compiler without
* requiring the file to be present on disk.
*/

declare module "@site/static/data/sbom-attestations.json" {
import type { SbomAttestationsData } from "@site/src/types/sbom";
const data: SbomAttestationsData;
export default data;
}
14 changes: 12 additions & 2 deletions src/types/sbom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,29 @@
export interface AttestationResult {
/**
* Whether a SLSA provenance attestation was found for this image.
* true = attestation present and verified.
* false = no attestation published (the command will fail if run).
* null = verification could not complete due to a tooling/registry/auth
* error; attestation existence is unknown.
*/
present: boolean;
present: boolean | null;
/**
* Whether the attestation passed cosign signature verification.
* false with present:true = attestation exists but verification failed.
* false with present:true = attestation exists but verification failed.
* false with present:false = attestation does not exist.
* false with present:null = verification could not be attempted.
*/
verified: boolean;
/** SLSA predicate type URI, populated when present:true */
predicateType: string | null;
/** SLSA type URL used during verification */
slsaType: string;
/**
* Error classification for present:null results.
* "tooling" = cosign/oras/registry error (transient or auth failure).
* Absent (undefined) for present:true/false results.
*/
errorKind?: "tooling";
/** Human-readable error string when present:false or verified:false */
error: string | null;
}
Expand Down
1 change: 0 additions & 1 deletion static/data/sbom-attestations.json

This file was deleted.

Loading