From 2679c6e770684873a20e035b39670159b8c4d801 Mon Sep 17 00:00:00 2001 From: Ralf Anton Beier Date: Wed, 22 Jul 2026 13:26:56 +0200 Subject: [PATCH 1/2] feat(release): publish the flight component to ghcr.io as a signed OCI artifact MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds an additive distribution channel for the verified falcon:flight component so it's pullable via `wkg oci pull` and indexable by wasm.directory (a meta-registry over OCI 1.1 registries), alongside the existing cosign-signed GitHub Release. - release.yml flight-component job: after attaching the wasm, push it to ghcr.io//falcon-flight: and :latest via `wkg` (wasm-pkg-tools — carries the Component-Model OCI media type wasm.directory expects, not a generic blob), then cosign-sign the ref keyless. NON-BLOCKING (continue-on-error): a registry hiccup must never fail the primary signed release; tighten to hard-fail once proven over a few tags. Adds `packages: write`. - docs/OCI-DISTRIBUTION.md: the pull/verify recipe + the two one-time owner steps (make the ghcr package public — it defaults private, and wasm.directory can only index public; register the namespace with wasm.directory) + honest scope (falcon:flight is a runnable smoke-test component today, not yet a reusable typed interface — publishing is a provenance/visibility play). Ships on the next tagged release; can't be exercised on a PR (release is tag-triggered), so it lands best-effort and self-validates on the next tag. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01HvusAXYbHLyv3uTzfBcMbG --- .github/workflows/release.yml | 43 ++++++++++++++++++++++++++ docs/OCI-DISTRIBUTION.md | 57 +++++++++++++++++++++++++++++++++++ 2 files changed, 100 insertions(+) create mode 100644 docs/OCI-DISTRIBUTION.md diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0a656ad..61131b9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -393,6 +393,7 @@ jobs: permissions: contents: write id-token: write # keyless cosign over the bundle SHA256SUMS (v1.77) + packages: write # OCI push of the flight component to ghcr.io (v1.128) steps: # relay's MODULE.bazel local_path_override's the four pulseengine rules to # ../, so bazel needs relay in a subdir + the rules as siblings @@ -515,3 +516,45 @@ jobs: echo "::error::Release $TAG has NO falcon-flight-*.wasm asset — the #100/#140 regression. Failing." exit 1 fi + + # ── Publish the flight component to ghcr.io as an OCI artifact (v1.128) ── + # An ADDITIVE distribution channel: the same verified `falcon:flight` + # component, pushed as an OCI 1.1 artifact with the Component-Model media + # type via `wkg`, so it is `wkg oci pull`-able and indexable by + # wasm.directory (a meta-registry over OCI registries). Cosign-signed + # keyless, like the release bundle. NON-BLOCKING: the primary artifact is + # the cosign-signed GitHub Release above; a registry hiccup must never + # fail a release, so this best-effort step is continue-on-error until it + # has proven itself across a few tags. Ref: ghcr.io/pulseengine/falcon-flight. + - name: Publish flight component to ghcr.io (OCI, cosign-signed) + continue-on-error: true + working-directory: relay + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GHCR_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + set -euo pipefail + TAG="${{ inputs.tag || github.ref_name }}" + MM=$(printf '%s' "$TAG" | sed -E 's/^falcon-v([0-9]+\.[0-9]+).*/\1/') + FULL=$(printf '%s' "$TAG" | sed -E 's/^falcon-v//') + WASM="falcon-flight-v${MM}.wasm" + REPO="ghcr.io/${GITHUB_REPOSITORY_OWNER}/falcon-flight" + + # Pull the exact wasm the release just attached (single source of truth). + gh release download "$TAG" -p "$WASM" --clobber + + # wkg = wasm-pkg-tools: pushes with the wasm-component OCI media type + # that OCI-1.1 registries and wasm.directory expect (a plain `oras + # push` would tag it as a generic blob). Reads docker credentials. + cargo install wasm-pkg-tools --locked + + echo "$GHCR_TOKEN" | docker login ghcr.io -u "${GITHUB_ACTOR}" --password-stdin + wkg oci push "${REPO}:${FULL}" "$WASM" + wkg oci push "${REPO}:latest" "$WASM" + + # Cosign-sign the pushed ref by digest (keyless OIDC). Resolve the + # digest via `docker buildx imagetools` / crane-free: cosign can sign + # the tag ref directly, which signs the underlying digest. + cosign sign --yes "${REPO}:${FULL}" + + echo "::notice::Published + signed ${REPO}:${FULL} (and :latest). Pull: wkg oci pull ${REPO}:${FULL}" diff --git a/docs/OCI-DISTRIBUTION.md b/docs/OCI-DISTRIBUTION.md new file mode 100644 index 0000000..23e0dd9 --- /dev/null +++ b/docs/OCI-DISTRIBUTION.md @@ -0,0 +1,57 @@ +# OCI distribution + wasm.directory + +The verified flight component (`falcon:flight`, the `falcon-flight-vX.wasm` +artifact) is published to **ghcr.io as an OCI 1.1 artifact** on every tagged +release, in addition to the cosign-signed GitHub Release. This makes it +`wkg oci pull`-able and indexable by [wasm.directory](https://wasm.directory) — +a meta-registry that indexes OCI registries rather than hosting packages itself. + +## What the release does automatically + +`.github/workflows/release.yml` (flight-component job) pushes the exact wasm the +release attached: + +``` +ghcr.io/pulseengine/falcon-flight: # e.g. 1.127.0 +ghcr.io/pulseengine/falcon-flight:latest +``` + +pushed with `wkg` (wasm-pkg-tools) so it carries the Component-Model OCI media +type wasm.directory expects — not a generic blob — and cosign-signed keyless, +like the release bundle. The step is **non-blocking** (`continue-on-error`): a +registry hiccup must never fail the primary signed GitHub Release. It will be +tightened to a hard failure once it has proven itself across a few tags. + +Pull + verify: + +```sh +wkg oci pull ghcr.io/pulseengine/falcon-flight:1.127.0 -o falcon-flight.wasm +cosign verify ghcr.io/pulseengine/falcon-flight:1.127.0 \ + --certificate-identity-regexp \ + 'https://github.com/pulseengine/relay/.github/workflows/release.yml@.*' \ + --certificate-oidc-issuer 'https://token.actions.githubusercontent.com' +``` + +## One-time manual steps (org owner) + +1. **Make the ghcr package public.** ghcr packages are created *private* by + default; wasm.directory can only index a public package. After the first + tagged release populates it, in the org's package settings set + `pulseengine/falcon-flight` visibility to **public** and link it to the + `relay` repo. (One-time; subsequent pushes inherit the setting.) +2. **Register the namespace with wasm.directory.** Per its publishing guide, + add the `pulseengine` namespace → `ghcr.io` mapping to the wasm.directory + registry config (or your account's namespace list), then it indexes the + package. wasm.directory is **alpha** ("indexed data may be incomplete or + reset without notice; don't depend on it in production") — treat the listing + as a visibility channel, not a dependency. The durable, signed artifacts + remain the GitHub Release and the ghcr OCI ref. + +## Scope note + +`falcon:flight`'s current world (`flight-demo`) exports two runnable smoke-test +functions (`run-stabilization`, `run-position-hold`) — it is a *runnable* +verified component, not yet a reusable library exposing a typed control +interface. Publishing it is a provenance/visibility play (a formally-verified +flight component, signed, in the public component ecosystem). A richer typed +`falcon:flight` interface others could import is a separate follow-on. From 2278265bd899eb8cfe1483d1920e5558636a54d9 Mon Sep 17 00:00:00 2001 From: Ralf Anton Beier Date: Wed, 22 Jul 2026 13:32:45 +0200 Subject: [PATCH 2/2] fix(release): harden the ghcr OCI step (security review) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three findings on the freshly-added step, all addressed: - command-injection (HIGH): bind the tag via env INPUT_TAG/GITHUB_REF_NAME instead of expanding ${{ }} inside run: (the convention this file documents), and validate it matches ^falcon-vX.Y.Z(-pre)?$ before use. - supply-chain (MED): the crate is `wkg` (not wasm-pkg-tools — the old name would not have installed); pin it, `--version 0.15.1`. - signing-by-mutable-tag (MED): capture the pushed digest and cosign-sign ${REPO}@sha256:, never the mutable :FULL tag; push :latest for discovery but never sign it. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01HvusAXYbHLyv3uTzfBcMbG --- .github/workflows/release.yml | 47 ++++++++++++++++++++++------------- 1 file changed, 30 insertions(+), 17 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 61131b9..f461cf8 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -517,24 +517,33 @@ jobs: exit 1 fi + # ── Publish the flight component to ghcr.io as an OCI artifact (v1.128) ── # An ADDITIVE distribution channel: the same verified `falcon:flight` # component, pushed as an OCI 1.1 artifact with the Component-Model media # type via `wkg`, so it is `wkg oci pull`-able and indexable by - # wasm.directory (a meta-registry over OCI registries). Cosign-signed - # keyless, like the release bundle. NON-BLOCKING: the primary artifact is - # the cosign-signed GitHub Release above; a registry hiccup must never - # fail a release, so this best-effort step is continue-on-error until it - # has proven itself across a few tags. Ref: ghcr.io/pulseengine/falcon-flight. + # wasm.directory (a meta-registry over OCI registries). Cosign-signed by + # immutable DIGEST, like the release bundle. NON-BLOCKING + # (continue-on-error): the primary artifact is the cosign-signed GitHub + # Release above; a registry hiccup must never fail a release. Tighten to + # hard-fail once proven over a few tags. Ref: ghcr.io//falcon-flight. - name: Publish flight component to ghcr.io (OCI, cosign-signed) continue-on-error: true working-directory: relay env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GHCR_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # Bind the tag via env — never expand ${{ ... }} inside run: (the + # command-injection convention this file follows throughout). + INPUT_TAG: ${{ inputs.tag }} run: | set -euo pipefail - TAG="${{ inputs.tag || github.ref_name }}" + TAG="${INPUT_TAG:-${GITHUB_REF_NAME}}" + # Validate the shape before it reaches any shell command: a release + # tag is falcon-vX.Y.Z (+ optional prerelease). Reject anything else. + if ! printf '%s' "$TAG" | grep -qE '^falcon-v[0-9]+\.[0-9]+\.[0-9]+([-.][A-Za-z0-9.]+)?$'; then + echo "::error::refusing to publish OCI for a malformed tag: $TAG"; exit 1 + fi MM=$(printf '%s' "$TAG" | sed -E 's/^falcon-v([0-9]+\.[0-9]+).*/\1/') FULL=$(printf '%s' "$TAG" | sed -E 's/^falcon-v//') WASM="falcon-flight-v${MM}.wasm" @@ -543,18 +552,22 @@ jobs: # Pull the exact wasm the release just attached (single source of truth). gh release download "$TAG" -p "$WASM" --clobber - # wkg = wasm-pkg-tools: pushes with the wasm-component OCI media type - # that OCI-1.1 registries and wasm.directory expect (a plain `oras - # push` would tag it as a generic blob). Reads docker credentials. - cargo install wasm-pkg-tools --locked + # wkg = the wasm-pkg-tools CLI. PINNED (supply-chain), bumped in + # lockstep. Pushes the wasm-component OCI media type wasm.directory + # expects — a plain `oras push` would tag it as a generic blob. + cargo install wkg --locked --version 0.15.1 echo "$GHCR_TOKEN" | docker login ghcr.io -u "${GITHUB_ACTOR}" --password-stdin - wkg oci push "${REPO}:${FULL}" "$WASM" + wkg oci push "${REPO}:${FULL}" "$WASM" | tee push.log + # :latest is a moving convenience pointer — pushed for discovery, + # but NEVER signed (only immutable refs get a signature). wkg oci push "${REPO}:latest" "$WASM" - # Cosign-sign the pushed ref by digest (keyless OIDC). Resolve the - # digest via `docker buildx imagetools` / crane-free: cosign can sign - # the tag ref directly, which signs the underlying digest. - cosign sign --yes "${REPO}:${FULL}" - - echo "::notice::Published + signed ${REPO}:${FULL} (and :latest). Pull: wkg oci pull ${REPO}:${FULL}" + # Sign the IMMUTABLE digest, not a mutable tag. Only if we resolved one. + DIGEST=$(grep -oE 'sha256:[a-f0-9]{64}' push.log | head -1 || true) + if [ -n "$DIGEST" ]; then + cosign sign --yes "${REPO}@${DIGEST}" + echo "::notice::Published + signed ${REPO}@${DIGEST} (tags ${FULL}, latest). Pull: wkg oci pull ${REPO}:${FULL}" + else + echo "::warning::pushed ${REPO}:${FULL} but could not resolve a digest to sign — left unsigned this run" + fi