forked from unom/punktfunk
ci: the supply chain accounts for itself — per-release SBOM, full-tree audits, a real license gate
CRA Annex I Part II groundwork (see punktfunk-planning design/cra-readiness.md, Phase 1): * sbom.yml + scripts/ci/gen-sbom.sh: every vX.Y.Z release gets a CycloneDX SBOM attached — syft over both Cargo.locks, all Bun/pnpm trees and the Swift Package.resolved (2,667 components), merged with compliance/sbom/manual-components.cdx.json for what no lockfile records (pyrowave/Granite/volk/Vulkan-Headers pins, libvpl, FFmpeg, SDL3, VB-CABLE, punktfunk-gamescope). * audit.yml: bun audit now covers sdk + plugin-kit (not just web), decky's pnpm tree is scanned, and docs-site runs non-blocking until its known CMS-chain advisories are cleared. All shipping trees verified green today. * license-gate: about.toml's allowlist claim is finally enforced — cargo-about 0.9.1 with --fail over BOTH workspaces. The old [crate.clarify] license-only syntax fails to deserialize under 0.9; migrated ring to a per-crate accepted extension and dropped the stale aws-lc-sys entry (workspace is ring-only). Both gates validated green locally. * drivers/Cargo.lock: sync the pf-dualsense→pf-gamepad rename — the crate rename updated the manifest but the Windows-only lockfile was never regenerated; cargo-about's metadata pass caught it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+97
-11
@@ -1,9 +1,22 @@
|
||||
# Supply-chain advisory scan for BOTH dependency trees the project ships to users:
|
||||
# Supply-chain advisory scan for EVERY dependency tree the project ships or publishes, plus the
|
||||
# license-allowlist gate (CRA Annex I Part II: know your components; catch a bad dep the moment
|
||||
# it lands).
|
||||
# * cargo-audit → the (network-facing, crypto-heavy) Rust tree, against the RustSec advisory DB.
|
||||
# * bun audit → the web management console (Nitro/Bun BFF) — the component that holds the login
|
||||
# gate, session sealing, and the mgmt bearer token, so its deps matter too.
|
||||
# Triggers: weekly (catch newly-disclosed CVEs in pinned deps), on every lockfile change (catch a bad
|
||||
# dep the moment it lands), and on demand.
|
||||
# * bun audit → each Bun-managed tree that ships or publishes: web (the mgmt console BFF —
|
||||
# login gate, session sealing, mgmt bearer token), sdk (@punktfunk/host),
|
||||
# plugin-kit (@punktfunk/plugin-kit).
|
||||
# * pnpm audit → clients/decky (the Steam Deck plugin).
|
||||
# * docs-site → scanned NON-blocking (continue-on-error): known transitive advisories ride in
|
||||
# via the CMS/UI chain (@unom/ui → payload → dompurify/monaco) and the nitropack
|
||||
# build chain (node-tar, brace-expansion); clearing them needs coordinated bumps
|
||||
# verified against the LIVE site (the docs don't build standalone) — tracked in
|
||||
# punktfunk-planning design/cra-readiness.md. Flip to blocking once clean.
|
||||
# * cargo-about → license-allowlist gate over BOTH Rust workspaces (about.toml `accepted`);
|
||||
# fails if any crate carries a license outside the allowlist — the regression
|
||||
# guard about.toml always promised. (The Android Gradle tree has no lockfile, so
|
||||
# nothing scans it — see the CRA roadmap.)
|
||||
# Triggers: weekly (catch newly-disclosed CVEs in pinned deps), on every lockfile/allowlist
|
||||
# change, and on demand.
|
||||
# To silence a known-unfixable Rust advisory, add it to `.cargo/audit.toml` ([advisories] ignore=[…]).
|
||||
name: audit
|
||||
|
||||
@@ -12,7 +25,16 @@ on:
|
||||
- cron: '0 6 * * 1' # Mondays 06:00 UTC
|
||||
push:
|
||||
branches: [main]
|
||||
paths: ['Cargo.lock', 'web/bun.lock', '.gitea/workflows/audit.yml']
|
||||
paths:
|
||||
- 'Cargo.lock'
|
||||
- 'packaging/windows/drivers/Cargo.lock'
|
||||
- 'web/bun.lock'
|
||||
- 'docs-site/bun.lock'
|
||||
- 'sdk/bun.lock'
|
||||
- 'plugin-kit/bun.lock'
|
||||
- 'clients/decky/pnpm-lock.yaml'
|
||||
- 'about.toml'
|
||||
- '.gitea/workflows/audit.yml'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
@@ -36,13 +58,17 @@ jobs:
|
||||
cargo audit
|
||||
|
||||
bun-audit:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
tree: [web, sdk, plugin-kit]
|
||||
runs-on: ubuntu-24.04
|
||||
container:
|
||||
image: oven/bun:1
|
||||
timeout-minutes: 15
|
||||
defaults:
|
||||
run:
|
||||
working-directory: web
|
||||
working-directory: ${{ matrix.tree }}
|
||||
steps:
|
||||
# oven/bun's slim base lacks a CA bundle + git — actions/checkout's HTTPS fetch needs them
|
||||
# (same preamble as web-screenshots.yml / ci.yml's web job).
|
||||
@@ -50,9 +76,69 @@ jobs:
|
||||
working-directory: /
|
||||
run: apt-get update && apt-get install -y --no-install-recommends ca-certificates git
|
||||
- uses: actions/checkout@v4
|
||||
# `bun audit` queries the registry advisory DB for the versions pinned in web/bun.lock. No
|
||||
# install/build needed — it reads the manifest + lockfile. Fails the job on any advisory, the
|
||||
# same fail-on-vulnerability stance as cargo-audit above; triage a finding by bumping the dep
|
||||
# (or, if genuinely unfixable + inapplicable, pinning a resolution and noting why here).
|
||||
# `bun audit` queries the registry advisory DB for the versions pinned in the tree's
|
||||
# bun.lock. No install/build needed — it reads the manifest + lockfile. Fails the job on any
|
||||
# advisory, the same fail-on-vulnerability stance as cargo-audit above; triage a finding by
|
||||
# bumping the dep (or, if genuinely unfixable + inapplicable, pinning a resolution and
|
||||
# noting why here).
|
||||
- name: bun audit
|
||||
run: bun audit
|
||||
|
||||
# Kept OUT of the bun-audit matrix so this tree's known-advisory state can't normalize failure
|
||||
# in a shipping tree. See the header for why this one is non-blocking.
|
||||
docs-site-audit:
|
||||
runs-on: ubuntu-24.04
|
||||
container:
|
||||
image: oven/bun:1
|
||||
timeout-minutes: 15
|
||||
continue-on-error: true
|
||||
defaults:
|
||||
run:
|
||||
working-directory: docs-site
|
||||
steps:
|
||||
- name: Install git + CA certs
|
||||
working-directory: /
|
||||
run: apt-get update && apt-get install -y --no-install-recommends ca-certificates git
|
||||
- uses: actions/checkout@v4
|
||||
- name: bun audit (non-blocking)
|
||||
run: bun audit
|
||||
|
||||
pnpm-audit:
|
||||
runs-on: ubuntu-24.04
|
||||
container:
|
||||
image: node:22-bookworm
|
||||
timeout-minutes: 15
|
||||
defaults:
|
||||
run:
|
||||
working-directory: clients/decky
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
# decky is pnpm-managed (pnpm-lock.yaml lockfileVersion 9.0 → pnpm 10 reads it). Like
|
||||
# bun audit, `pnpm audit` needs no install/build — lockfile + registry advisory DB only.
|
||||
- name: pnpm audit
|
||||
run: |
|
||||
npm install -g pnpm@10
|
||||
pnpm audit
|
||||
|
||||
# The regression guard about.toml documents: fail if any crate in either Rust workspace carries
|
||||
# a license outside the `accepted` allowlist (e.g. a copyleft dep silently entering the linked
|
||||
# set). cargo-about is version-pinned: the config uses the per-crate `accepted` syntax
|
||||
# validated against exactly this version.
|
||||
license-gate:
|
||||
runs-on: ubuntu-24.04
|
||||
container:
|
||||
image: git.unom.io/unom/punktfunk-rust-ci:latest
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
path: /usr/local/cargo
|
||||
key: cargo-about-0.9.1
|
||||
restore-keys: cargo-about-
|
||||
- name: cargo about license gate (host + driver workspaces)
|
||||
run: |
|
||||
git config --global --add safe.directory "$PWD"
|
||||
command -v cargo-about >/dev/null 2>&1 || cargo install --locked cargo-about --version 0.9.1 --features cli
|
||||
cargo about generate about.hbs --fail -o /dev/null
|
||||
cargo about generate -m packaging/windows/drivers/Cargo.toml -c about.toml about.hbs --fail -o /dev/null
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
# Per-release SBOM (CRA Annex I Part II §1: identify and document the components in the product,
|
||||
# in a commonly used machine-readable format — we emit CycloneDX JSON).
|
||||
#
|
||||
# Tag push → the SBOM is attached to the Gitea release, next to the artifacts it describes.
|
||||
# Release assets are never pruned (security updates must stay available ≥10 years, CRA Art. 13),
|
||||
# so the SBOM's retention rides on the release's.
|
||||
# workflow_dispatch on a non-tag ref → generated and uploaded as a workflow artifact only
|
||||
# (pipeline validation / an on-demand snapshot); no release is touched.
|
||||
#
|
||||
# What goes in: scripts/ci/gen-sbom.sh = syft over the checkout (every lockfile-pinned dep in
|
||||
# both Rust workspaces + the JS trees + Swift Package.resolved) merged with
|
||||
# compliance/sbom/manual-components.cdx.json (vendored C/C++, bundled DLLs, VB-CABLE, gamescope).
|
||||
name: sbom
|
||||
|
||||
on:
|
||||
push:
|
||||
tags: ['v*']
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
sbom:
|
||||
runs-on: ubuntu-24.04
|
||||
container:
|
||||
image: git.unom.io/unom/punktfunk-rust-ci:latest
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
# fetch-depth 0: the dispatch path derives the canary base from the tag history
|
||||
# (scripts/ci/pf-version.sh), which a shallow clone cannot see.
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
# Pinned syft (keep in sync with the version validated against this repo; bump deliberately).
|
||||
- name: Install syft
|
||||
run: |
|
||||
curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh \
|
||||
| sh -s -- -b /usr/local/bin v1.49.0
|
||||
- name: Generate SBOM
|
||||
run: |
|
||||
git config --global --add safe.directory "$PWD"
|
||||
case "$GITHUB_REF" in
|
||||
refs/tags/v*) VERSION="${GITHUB_REF_NAME#v}" ;;
|
||||
*) eval "$(bash scripts/ci/pf-version.sh)"; VERSION="${PF_BASE}-snapshot" ;;
|
||||
esac
|
||||
sh scripts/ci/gen-sbom.sh "$VERSION" "punktfunk-${VERSION}.cdx.json"
|
||||
echo "SBOM_FILE=punktfunk-${VERSION}.cdx.json" >> "$GITHUB_ENV"
|
||||
- name: Attach to release
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
env:
|
||||
GITEA_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
|
||||
run: |
|
||||
. scripts/ci/gitea-release.sh
|
||||
RID=$(ensure_release "$GITHUB_REF_NAME" "$GITHUB_REF_NAME" auto)
|
||||
upsert_asset "$RID" "$SBOM_FILE"
|
||||
# v3, not v4: Gitea's artifact backend rejects upload-artifact@v4 (see release.yml).
|
||||
- name: Upload artifact (non-tag runs)
|
||||
if: "!startsWith(github.ref, 'refs/tags/')"
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: sbom
|
||||
path: punktfunk-*.cdx.json
|
||||
+12
-10
@@ -37,13 +37,15 @@ accepted = [
|
||||
ignore-build-dependencies = true
|
||||
ignore-dev-dependencies = true
|
||||
|
||||
# r-efi offers an LGPL-2.1-or-later arm but is tri-licensed; take a permissive arm. (It is also
|
||||
# UEFI-target-gated out of every shipped build.)
|
||||
[r-efi.clarify]
|
||||
license = "MIT OR Apache-2.0"
|
||||
|
||||
[ring.clarify]
|
||||
license = "MIT AND ISC AND OpenSSL"
|
||||
|
||||
[aws-lc-sys.clarify]
|
||||
license = "ISC AND Apache-2.0 AND MIT AND BSD-3-Clause AND OpenSSL"
|
||||
# Per-crate license-acceptance additions (cargo-about ≥0.6 syntax; the old `[crate.clarify]`
|
||||
# license-only form fails to deserialize under cargo-about 0.9, which now wants checksummed file
|
||||
# clarifications — per-crate `accepted` extensions express the same intent without checksums).
|
||||
#
|
||||
# r-efi is tri-licensed with an LGPL-2.1-or-later arm; cargo-about resolves OR-expressions to an
|
||||
# accepted arm on its own (MIT/Apache-2.0 are globally accepted), so it needs no entry. (It is
|
||||
# also UEFI-target-gated out of every shipped build.)
|
||||
#
|
||||
# ring's license is an AND of permissive terms including the OpenSSL license; accept the
|
||||
# OpenSSL/ISC parts for this crate only, not globally.
|
||||
[ring]
|
||||
accepted = ["OpenSSL", "ISC"]
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
{
|
||||
"bomFormat": "CycloneDX",
|
||||
"specVersion": "1.6",
|
||||
"version": 1,
|
||||
"components": [
|
||||
{
|
||||
"type": "library",
|
||||
"name": "pyrowave",
|
||||
"version": "509e4f887b585a3f97471fcc804e9de649f2c16f",
|
||||
"description": "GPU wavelet codec, vendored at crates/pyrowave-sys/vendor/pyrowave (pruned tree) with local patches from crates/pyrowave-sys/patches/; commits recorded in vendor/pyrowave/PUNKTFUNK-VENDOR.txt",
|
||||
"licenses": [{ "license": { "id": "MIT" } }],
|
||||
"externalReferences": [{ "type": "vcs", "url": "https://github.com/Themaister/pyrowave" }]
|
||||
},
|
||||
{
|
||||
"type": "library",
|
||||
"name": "Granite",
|
||||
"version": "44362775d36e0c4139352f83efd96bab4e239f66",
|
||||
"description": "Vulkan backend subset vendored inside the pyrowave tree",
|
||||
"licenses": [{ "license": { "id": "MIT" } }],
|
||||
"externalReferences": [{ "type": "vcs", "url": "https://github.com/Themaister/Granite" }]
|
||||
},
|
||||
{
|
||||
"type": "library",
|
||||
"name": "volk",
|
||||
"version": "47cddf7ed97b94118a08aacb548a411188e016cc",
|
||||
"description": "Vulkan meta-loader vendored inside the pyrowave/Granite tree",
|
||||
"licenses": [{ "license": { "id": "MIT" } }],
|
||||
"externalReferences": [{ "type": "vcs", "url": "https://github.com/zeux/volk" }]
|
||||
},
|
||||
{
|
||||
"type": "library",
|
||||
"name": "Vulkan-Headers",
|
||||
"version": "015e25c3c91b70eb1a754d36fb14c4ba6ad9b0b9",
|
||||
"description": "Vulkan API headers vendored inside the pyrowave tree",
|
||||
"licenses": [{ "license": { "id": "Apache-2.0" } }, { "license": { "id": "MIT" } }],
|
||||
"externalReferences": [{ "type": "vcs", "url": "https://github.com/KhronosGroup/Vulkan-Headers" }]
|
||||
},
|
||||
{
|
||||
"type": "library",
|
||||
"name": "libvpl",
|
||||
"version": "2.17.0",
|
||||
"description": "Intel oneVPL dispatcher, vendored at crates/libvpl-sys/vendor/libvpl",
|
||||
"licenses": [{ "license": { "id": "MIT" } }],
|
||||
"externalReferences": [{ "type": "vcs", "url": "https://github.com/intel/libvpl" }]
|
||||
},
|
||||
{
|
||||
"type": "library",
|
||||
"name": "FFmpeg",
|
||||
"version": "7.x/8.x (system-provided on Linux; replaceable DLLs bundled with the Windows packages)",
|
||||
"description": "Dynamically linked libav* decode/encode; LGPL notice at packaging/windows/licenses/FFmpeg-LGPL-NOTICE.txt",
|
||||
"licenses": [{ "license": { "id": "LGPL-2.1-or-later" } }],
|
||||
"externalReferences": [{ "type": "website", "url": "https://ffmpeg.org" }]
|
||||
},
|
||||
{
|
||||
"type": "library",
|
||||
"name": "SDL3",
|
||||
"version": "3.x (system-provided or bundled per platform package)",
|
||||
"description": "Windowing/input layer for the desktop clients, dynamically linked",
|
||||
"licenses": [{ "license": { "id": "Zlib" } }],
|
||||
"externalReferences": [{ "type": "vcs", "url": "https://github.com/libsdl-org/SDL" }]
|
||||
},
|
||||
{
|
||||
"type": "application",
|
||||
"name": "VB-CABLE",
|
||||
"version": "redistributed installer, see packaging/windows/install-vbcable.ps1",
|
||||
"description": "Third-party kernel-mode virtual audio driver redistributed with the Windows host; notice at packaging/windows/licenses/VB-CABLE-NOTICE.txt. Planned to be replaced by an attestation-signed first-party driver.",
|
||||
"licenses": [{ "license": { "name": "Proprietary freeware (VB-Audio Software, redistribution permitted per notice)" } }],
|
||||
"externalReferences": [{ "type": "website", "url": "https://vb-audio.com/Cable/" }]
|
||||
},
|
||||
{
|
||||
"type": "application",
|
||||
"name": "punktfunk-gamescope",
|
||||
"version": "upstream gamescope pinned by packaging/nix/gamescope.nix (nixpkgs) or built by packaging/gamescope/build-punktfunk-gamescope.sh, plus 3 local patches from packaging/gamescope/patches/",
|
||||
"description": "Patched gamescope compositor distributed via sysext/Arch/nix channels alongside the host",
|
||||
"licenses": [{ "license": { "id": "BSD-2-Clause" } }],
|
||||
"externalReferences": [{ "type": "vcs", "url": "https://github.com/ValveSoftware/gamescope" }]
|
||||
}
|
||||
]
|
||||
}
|
||||
Generated
+1
-1
@@ -402,7 +402,7 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pf-dualsense"
|
||||
name = "pf-gamepad"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"pf-driver-proto",
|
||||
|
||||
Executable
+31
@@ -0,0 +1,31 @@
|
||||
#!/bin/sh
|
||||
# Generate the per-release CycloneDX SBOM (CRA Annex I Part II §1 — machine-readable component
|
||||
# inventory, attached to every stable release by .gitea/workflows/sbom.yml).
|
||||
#
|
||||
# syft walks the checkout and catalogs every lockfile-pinned dependency (both Rust workspaces via
|
||||
# their Cargo.locks, the Bun/pnpm/npm trees, the Swift Package.resolved);
|
||||
# compliance/sbom/manual-components.cdx.json contributes the components no lockfile records —
|
||||
# vendored C/C++ trees (pyrowave/Granite/volk/Vulkan-Headers, libvpl), dynamically-linked/bundled
|
||||
# libraries (FFmpeg, SDL3), the redistributed VB-CABLE driver, and the patched gamescope. Keep
|
||||
# that file current when vendoring changes (scripts/vendor-pyrowave.sh etc.).
|
||||
#
|
||||
# Usage: scripts/ci/gen-sbom.sh VERSION [OUTPUT]
|
||||
# Requires: syft (pinned install in the workflow), python3 (a proven runner dependency).
|
||||
set -eu
|
||||
VERSION="${1:?usage: gen-sbom.sh VERSION [OUTPUT]}"
|
||||
OUT="${2:-punktfunk-${VERSION}.cdx.json}"
|
||||
TMP="${OUT}.syft.tmp"
|
||||
|
||||
syft scan "dir:." --source-name punktfunk --source-version "$VERSION" \
|
||||
-o "cyclonedx-json=$TMP" -q
|
||||
|
||||
OUT="$OUT" TMP="$TMP" python3 - <<'PY'
|
||||
import json, os
|
||||
gen = json.load(open(os.environ["TMP"]))
|
||||
manual = json.load(open("compliance/sbom/manual-components.cdx.json"))
|
||||
gen.setdefault("components", []).extend(manual["components"])
|
||||
with open(os.environ["OUT"], "w") as f:
|
||||
json.dump(gen, f, indent=2)
|
||||
print("SBOM: %d components -> %s" % (len(gen["components"]), os.environ["OUT"]))
|
||||
PY
|
||||
rm -f "$TMP"
|
||||
Reference in New Issue
Block a user