diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7edf5a7913..4ace6f5e6c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1034,16 +1034,15 @@ jobs: touch "desktop/src-tauri/binaries/buzz-dev-mcp-$TARGET" touch "desktop/src-tauri/binaries/git-credential-nostr-$TARGET" touch "desktop/src-tauri/binaries/buzz-$TARGET" - # Mesh rev is derived from Cargo.lock so a dependency bump needs no - # lockstep edit here; the cache key tracks it automatically. + # Mesh rev is derived from the desktop lockfile so a dependency bump + # needs no lockstep edit here; the cache key tracks it automatically. - name: Resolve mesh-llm rev id: mesh_rev run: | set -euo pipefail - REV=$(python3 -c 'import tomllib; d=tomllib.load(open("Cargo.lock", "rb")); p=next(p for p in d["package"] if p["name"] == "mesh-llm-sdk"); print(p["source"].rsplit("#", 1)[1])') - [[ -n "$REV" ]] || { echo "::error::could not resolve mesh-llm rev from Cargo.lock"; exit 1; } + REV=$(python3 -c 'import tomllib; d=tomllib.load(open("desktop/src-tauri/Cargo.lock", "rb")); p=next(p for p in d["package"] if p["name"] == "mesh-llm-sdk"); print(p["source"].rsplit("#", 1)[1])') + [[ -n "$REV" ]] || { echo "::error::could not resolve mesh-llm rev from desktop/src-tauri/Cargo.lock"; exit 1; } echo "rev=$REV" >> "$GITHUB_OUTPUT" - echo "short=${REV:0:7}" >> "$GITHUB_OUTPUT" - name: Restore mesh llama build cache id: llama_cache uses: actions/cache/restore@caa296126883cff596d87d8935842f9db880ef25 # v5 @@ -1052,15 +1051,18 @@ jobs: key: mesh-llama-${{ runner.os }}-metal-${{ steps.mesh_rev.outputs.rev }} - name: Build mesh llama native libraries if: steps.llama_cache.outputs.cache-hit != 'true' - env: - MESH_REV_SHORT: ${{ steps.mesh_rev.outputs.short }} run: | set -euo pipefail - cargo fetch --manifest-path desktop/src-tauri/Cargo.toml - SHORT="$MESH_REV_SHORT" - MESH_ROOT=$(find "${CARGO_HOME:-$HOME/.cargo}/git/checkouts" -path "*/$SHORT" -type d -name "$SHORT" | head -1) - if [[ -z "$MESH_ROOT" ]]; then - echo "::error::mesh-llm checkout for $SHORT not found after cargo fetch" + MESH_MANIFEST=$( + cargo metadata --locked --manifest-path desktop/src-tauri/Cargo.toml --features mesh-llm --format-version 1 | + python3 -c 'import json, sys; data = json.load(sys.stdin); print(next(package["manifest_path"] for package in data["packages"] if package["name"] == "mesh-llm-sdk"))' + ) + MESH_ROOT=$(dirname "$MESH_MANIFEST") + while [[ "$MESH_ROOT" != "/" && ! -x "$MESH_ROOT/scripts/prepare-llama.sh" ]]; do + MESH_ROOT=$(dirname "$MESH_ROOT") + done + if [[ ! -x "$MESH_ROOT/scripts/prepare-llama.sh" || ! -x "$MESH_ROOT/scripts/build-llama.sh" ]]; then + echo "::error::mesh-llm native build scripts not found from cargo metadata" exit 1 fi export LLAMA_STAGE_BACKEND=metal diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 52f21b28bc..c6e5dbd905 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -81,6 +81,8 @@ env: jobs: build: name: Build (${{ matrix.platform }}) + # Fork pull requests still compile, but only canonical upstream publishes. + if: github.event_name == 'pull_request' || github.repository == 'block/buzz' runs-on: ${{ matrix.runner }} timeout-minutes: 60 permissions: @@ -125,8 +127,8 @@ jobs: max-parallelism = 2 - name: Log in to GHCR - # Skip on pull_request from forks — no GHCR creds, build-only. - if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository + # Pull requests are build-only and never authenticate or write caches. + if: github.event_name != 'pull_request' uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0 with: registry: ghcr.io @@ -179,7 +181,7 @@ jobs: cache-from: | type=registry,ref=${{ env.IMAGE_NAME }}-buildcache:${{ matrix.arch }} cache-to: | - ${{ (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) && format('type=registry,ref={0}-buildcache:{1},mode=max,compression=zstd', env.IMAGE_NAME, matrix.arch) || '' }} + ${{ github.event_name != 'pull_request' && format('type=registry,ref={0}-buildcache:{1},mode=max,compression=zstd', env.IMAGE_NAME, matrix.arch) || '' }} - name: Build and push debug image by digest id: build-debug @@ -224,7 +226,7 @@ jobs: merge: name: Merge ${{ matrix.variant }} multi-arch manifest - if: github.event_name != 'pull_request' + if: github.event_name != 'pull_request' && github.repository == 'block/buzz' runs-on: ubuntu-24.04 needs: build timeout-minutes: 15 @@ -342,6 +344,8 @@ jobs: push-gateway-build: name: Build public push gateway (${{ matrix.platform }}) + # Fork pull requests still compile, but only canonical upstream publishes. + if: github.event_name == 'pull_request' || github.repository == 'block/buzz' runs-on: ${{ matrix.runner }} timeout-minutes: 60 permissions: @@ -377,7 +381,7 @@ jobs: [worker.oci] max-parallelism = 2 - name: Log in to GHCR - if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository + if: github.event_name != 'pull_request' uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0 with: registry: ghcr.io @@ -402,7 +406,7 @@ jobs: labels: ${{ steps.meta.outputs.labels }} outputs: type=image,name=ghcr.io/block/buzz-push-gateway,push-by-digest=true,name-canonical=true,push=${{ github.event_name != 'pull_request' }} cache-from: type=registry,ref=ghcr.io/block/buzz-push-gateway-buildcache:${{ matrix.arch }} - cache-to: ${{ (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) && format('type=registry,ref=ghcr.io/block/buzz-push-gateway-buildcache:{0},mode=max,compression=zstd', matrix.arch) || '' }} + cache-to: ${{ github.event_name != 'pull_request' && format('type=registry,ref=ghcr.io/block/buzz-push-gateway-buildcache:{0},mode=max,compression=zstd', matrix.arch) || '' }} - name: Export digest if: github.event_name != 'pull_request' env: @@ -419,7 +423,7 @@ jobs: push-gateway-merge: name: Publish public push gateway image - if: github.event_name != 'pull_request' + if: github.event_name != 'pull_request' && github.repository == 'block/buzz' runs-on: ubuntu-24.04 needs: push-gateway-build timeout-minutes: 15 diff --git a/.github/workflows/private-ca-release.yml b/.github/workflows/private-ca-release.yml index b6279c8b4e..c6466fff73 100644 --- a/.github/workflows/private-ca-release.yml +++ b/.github/workflows/private-ca-release.yml @@ -19,7 +19,8 @@ concurrency: env: UPSTREAM_REPOSITORY: block/buzz - PATCH_COMMIT: 6d03a38da5e3402bf97df1b3c46152887eb3778e + PRIVATE_CA_PATCH_COMMIT: 6d03a38da5e3402bf97df1b3c46152887eb3778e + SECURITY_PATCH_COMMIT: 7dbfcd785be0a9c002863a793c4fbab89a6258c3 jobs: monitor: @@ -44,25 +45,26 @@ jobs: work="$(mktemp -d)"; trap 'rm -rf "${work}"' EXIT git clone --depth 1 --branch "${tag}" "https://github.com/${UPSTREAM_REPOSITORY}.git" "${work}/source" source_sha="$(git -C "${work}/source" rev-parse HEAD)" - git -C "${work}/source" fetch --depth 2 "https://github.com/${GITHUB_REPOSITORY}.git" "${PATCH_COMMIT}" - if git -C "${work}/source" cherry-pick --no-commit "${PATCH_COMMIT}"; then + if for patch_commit in "${PRIVATE_CA_PATCH_COMMIT}" "${SECURITY_PATCH_COMMIT}"; do + git -C "${work}/source" fetch --depth 2 "https://github.com/${GITHUB_REPOSITORY}.git" "${patch_commit}" + git -C "${work}/source" cherry-pick --no-commit "${patch_commit}" + done; then patch_status=clean - git -C "${work}/source" cherry-pick --abort || git -C "${work}/source" reset --hard else patch_status=conflict - git -C "${work}/source" cherry-pick --abort || git -C "${work}/source" reset --hard fi + git -C "${work}/source" reset --hard "${source_sha}" for label in buzz-update build-approved skip remediation-required built accepted; do gh label create "${label}" --force --color 0E8A16 --description "Buzz private-CA release lifecycle" >/dev/null done cat >"${work}/issue.md" < + ## Buzz ${tag} is available Upstream release: $(jq -r '.html_url' <<<"${release}") Published: $(jq -r '.published_at' <<<"${release}") Immutable source SHA: \`${source_sha}\` - Patch applicability: **${patch_status}** + Certified patch-series applicability: **${patch_status}** ### Changelog @@ -97,25 +99,6 @@ jobs: gh issue comment "${{ github.event.issue.number }}" --repo "${GITHUB_REPOSITORY}" --body "Skipped by @BrianInAz; no private-CA package was built." gh issue close "${{ github.event.issue.number }}" --repo "${GITHUB_REPOSITORY}" - built: - if: >- - github.event_name == 'issues' && github.event.action == 'labeled' && - github.event.label.name == 'built' && github.actor == 'BrianInAz' - runs-on: ubuntu-latest - timeout-minutes: 5 - permissions: - issues: write - steps: - - name: Record the local package handoff - env: - GH_TOKEN: ${{ github.token }} - ISSUE_NUMBER: ${{ github.event.issue.number }} - run: | - set -euo pipefail - body="$(gh issue view "${ISSUE_NUMBER}" --repo "${GITHUB_REPOSITORY}" --json body --jq .body)" - grep -Eq '^$' <<<"${body}" || { echo "not a clean monitor-created ticket" >&2; exit 1; } - gh issue comment "${ISSUE_NUMBER}" --repo "${GITHUB_REPOSITORY}" --body "Built by @BrianInAz on the approved Apple Silicon Mac. Record the immutable artifact, checksum, private WSS gate, and local acceptance evidence before adding \`accepted\`." - accepted: if: >- github.event_name == 'issues' && github.event.action == 'labeled' && @@ -132,7 +115,7 @@ jobs: run: | set -euo pipefail body="$(gh issue view "${ISSUE_NUMBER}" --repo "${GITHUB_REPOSITORY}" --json body --jq .body)" - grep -Eq '^$' <<<"${body}" || { echo "not a clean monitor-created ticket" >&2; exit 1; } + grep -Eq '^$' <<<"${body}" || { echo "not a clean monitor-created ticket" >&2; exit 1; } labels="$(gh issue view "${ISSUE_NUMBER}" --repo "${GITHUB_REPOSITORY}" --json labels --jq '.labels[].name')" grep -Fxq built <<<"${labels}" || { echo "accepted requires the built lifecycle state" >&2; exit 1; } gh issue comment "${ISSUE_NUMBER}" --repo "${GITHUB_REPOSITORY}" --body "Accepted by @BrianInAz after private WSS, state-preserving install, restart, reconnect, and history-restoration evidence was recorded." @@ -159,12 +142,14 @@ jobs: run: | set -euo pipefail body="$(gh issue view "${ISSUE_NUMBER}" --repo "${GITHUB_REPOSITORY}" --json body --jq .body)" - marker="$(grep -E '^$' <<<"${body}")" + marker="$(grep -E '^$' <<<"${body}")" [[ -n "${marker}" ]] || { echo "not a clean monitor-created ticket" >&2; exit 1; } tag="$(sed -E 's/.*tag=([^ ]+).*/\1/' <<<"${marker}")" source_sha="$(sed -E 's/.*source_sha=([^ ]+).*/\1/' <<<"${marker}")" - patch_sha="$(sed -E 's/.*patch_sha=([^ ]+).*/\1/' <<<"${marker}")" - [[ "${patch_sha}" == "${PATCH_COMMIT}" ]] || { echo "wrong patch" >&2; exit 1; } + private_ca_patch_sha="$(sed -E 's/.*private_ca_patch_sha=([^ ]+).*/\1/' <<<"${marker}")" + security_patch_sha="$(sed -E 's/.*security_patch_sha=([^ ]+).*/\1/' <<<"${marker}")" + [[ "${private_ca_patch_sha}" == "${PRIVATE_CA_PATCH_COMMIT}" ]] || { echo "wrong private-CA patch" >&2; exit 1; } + [[ "${security_patch_sha}" == "${SECURITY_PATCH_COMMIT}" ]] || { echo "wrong security patch" >&2; exit 1; } remote_sha="$(git ls-remote "https://github.com/${UPSTREAM_REPOSITORY}.git" "refs/tags/${tag}^{}" | awk '{print $1}')" [[ -n "${remote_sha}" ]] || remote_sha="$(git ls-remote "https://github.com/${UPSTREAM_REPOSITORY}.git" "refs/tags/${tag}" | awk '{print $1}')" [[ "${remote_sha}" == "${source_sha}" ]] || { echo "tag SHA changed" >&2; exit 1; } @@ -182,14 +167,26 @@ jobs: - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 - uses: cashapp/activate-hermit@cea9af7913204a965fd488637a8d1811bba2e616 # v1 - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 - - name: Fetch immutable source and apply patch + - name: Fetch immutable source and apply certified patch series run: | set -euo pipefail git clone --depth 1 --branch "${{ needs.approval.outputs.tag }}" "https://github.com/${UPSTREAM_REPOSITORY}.git" "$RUNNER_TEMP/source" [[ "$(git -C "$RUNNER_TEMP/source" rev-parse HEAD)" == "${{ needs.approval.outputs.source_sha }}" ]] - git -C "$RUNNER_TEMP/source" fetch --depth 2 "https://github.com/${GITHUB_REPOSITORY}.git" "${PATCH_COMMIT}" - git -C "$RUNNER_TEMP/source" cherry-pick --no-commit "${PATCH_COMMIT}" + for patch_commit in "${PRIVATE_CA_PATCH_COMMIT}" "${SECURITY_PATCH_COMMIT}"; do + git -C "$RUNNER_TEMP/source" fetch --depth 2 "https://github.com/${GITHUB_REPOSITORY}.git" "${patch_commit}" + git -C "$RUNNER_TEMP/source" cherry-pick --no-commit "${patch_commit}" + done git -C "$RUNNER_TEMP/source" diff --check + - name: Reject vulnerable root and Apple Silicon desktop graphs + working-directory: ${{ runner.temp }}/source + run: | + set -euo pipefail + cargo-deny --locked check --config deny.toml advisories + cargo-deny --locked \ + --manifest-path desktop/src-tauri/Cargo.toml \ + --target aarch64-apple-darwin \ + --exclude-dev \ + check --config deny.toml advisories - name: Run full upstream CI working-directory: ${{ runner.temp }}/source run: just ci @@ -206,16 +203,61 @@ jobs: working-directory: ${{ runner.temp }}/source run: cargo test --manifest-path desktop/src-tauri/Cargo.toml native_websocket::tests::native_websocket_platform_tls_connector_is_available - local-macos-handoff: + package: + name: Build private-CA package on standard hosted Apple Silicon needs: [approval, validate] - runs-on: ubuntu-latest - timeout-minutes: 5 + runs-on: macos-15 + timeout-minutes: 90 permissions: + contents: read issues: write steps: - - name: Local macOS package handoff + - name: Require free standard public ARM64 runner + env: + REPOSITORY_IS_PRIVATE: ${{ github.event.repository.private }} + run: | + set -euo pipefail + [[ "${REPOSITORY_IS_PRIVATE}" == "false" ]] || { + echo "This package lane is approved only for free standard runners in a public repository." >&2 + exit 1 + } + [[ "$(uname -m)" == "arm64" ]] || { + echo "macos-15 must resolve to a standard Apple Silicon runner." >&2 + exit 1 + } + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 + with: + persist-credentials: false + - uses: cashapp/activate-hermit@cea9af7913204a965fd488637a8d1811bba2e616 # v1 + - name: Build and verify ad-hoc-signed package + env: + SOURCE_SHA: ${{ needs.approval.outputs.source_sha }} + TAG: ${{ needs.approval.outputs.tag }} + run: | + set -euo pipefail + output_directory="${RUNNER_TEMP}/private-ca/${TAG}" + scripts/build-private-ca-macos.sh "${TAG}" "${SOURCE_SHA}" "${output_directory}" + ( + cd "${output_directory}" + shasum -a 256 -c SHA256SUMS + ) + - name: Upload short-lived private-CA candidate + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 + with: + name: buzz-private-ca-${{ needs.approval.outputs.tag }}-${{ needs.approval.outputs.source_sha }} + path: ${{ runner.temp }}/private-ca/${{ needs.approval.outputs.tag }}/ + if-no-files-found: error + compression-level: 0 + retention-days: 7 + - name: Record hosted package evidence env: GH_TOKEN: ${{ github.token }} + ARTIFACT_NAME: buzz-private-ca-${{ needs.approval.outputs.tag }}-${{ needs.approval.outputs.source_sha }} + ISSUE_NUMBER: ${{ needs.approval.outputs.issue_number }} + SOURCE_SHA: ${{ needs.approval.outputs.source_sha }} + TAG: ${{ needs.approval.outputs.tag }} run: | set -euo pipefail - gh issue comment "${{ needs.approval.outputs.issue_number }}" --repo "${GITHUB_REPOSITORY}" --body "Remote validation passed. Package on the approved local Mac with scripts/build-private-ca-macos.sh ${{ needs.approval.outputs.tag }} ${{ needs.approval.outputs.source_sha }}. GitHub-hosted macOS runners are intentionally not used." + run_url="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}" + gh issue comment "${ISSUE_NUMBER}" --repo "${GITHUB_REPOSITORY}" --body "GitHub-hosted macOS package built on the free standard macos-15 Apple Silicon runner. Source: \`${SOURCE_SHA}\`. Certified patches: \`${PRIVATE_CA_PATCH_COMMIT}\`, \`${SECURITY_PATCH_COMMIT}\`. Artifact: \`${ARTIFACT_NAME}\` (retained 7 days). Run: ${run_url}. The app is ad-hoc signed; no Apple signing identity or secret was used. Next: run the private WSS gate, then download and perform state-preserving installation and acceptance on Brian's MacBook before adding \`accepted\`." + gh issue edit "${ISSUE_NUMBER}" --repo "${GITHUB_REPOSITORY}" --add-label built diff --git a/Cargo.lock b/Cargo.lock index 49104b22d2..4bdb1f84a2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5504,9 +5504,9 @@ dependencies = [ [[package]] name = "nostr-relay-pool" -version = "0.44.1" +version = "0.44.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91b2c039df4f96c4bf7dae52a74fd5516ad6dda83a11c0c69dea91b5255a4f37" +checksum = "fb94d61a467a869a6790b907838a9bea82c813d567d9fcbac995f207be8cee4b" dependencies = [ "async-utility", "async-wsocket", diff --git a/deny.toml b/deny.toml index d3c5fcd4bc..4c50606dbf 100644 --- a/deny.toml +++ b/deny.toml @@ -1,4 +1,8 @@ [advisories] +# Vulnerabilities remain fatal. Limit no-fix unmaintained notices to direct +# workspace dependencies so target-specific transitive notices stay visible +# without masking actionable vulnerability results. +unmaintained = "workspace" ignore = [ # instant 0.1.13 — unmaintained crate. Transitive dep: nostr → instant. # Will be resolved when nostr crate updates its dependencies. diff --git a/desktop/src-tauri/Cargo.lock b/desktop/src-tauri/Cargo.lock index bf84cd0d33..9ba5dea1fd 100644 --- a/desktop/src-tauri/Cargo.lock +++ b/desktop/src-tauri/Cargo.lock @@ -5961,9 +5961,9 @@ dependencies = [ [[package]] name = "nostr-relay-pool" -version = "0.44.1" +version = "0.44.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91b2c039df4f96c4bf7dae52a74fd5516ad6dda83a11c0c69dea91b5255a4f37" +checksum = "fb94d61a467a869a6790b907838a9bea82c813d567d9fcbac995f207be8cee4b" dependencies = [ "async-utility", "async-wsocket", diff --git a/docs/operations/private-ca-desktop-release-lifecycle.md b/docs/operations/private-ca-desktop-release-lifecycle.md index c7eb5f533f..7d68d15fe6 100644 --- a/docs/operations/private-ca-desktop-release-lifecycle.md +++ b/docs/operations/private-ca-desktop-release-lifecycle.md @@ -7,6 +7,12 @@ build that trusts certificate authorities installed in the macOS platform trust store. It exists while the upstream solution is under coordination in [BrianInAz/buzz#1](https://github.com/BrianInAz/buzz/issues/1). +The same lane also carries the temporary RUSTSEC-2026-0224 correction required +by Buzz Desktop's independently resolved Rust lockfile. That correction is +tracked upstream in [block/buzz#4251](https://github.com/block/buzz/issues/4251) +and [block/buzz#4256](https://github.com/block/buzz/pull/4256), and internally in +[BjzyLabs/homelab-playbooks#2285](https://github.com/BjzyLabs/homelab-playbooks/issues/2285). + The implementation is intentionally not an upstream release lane. It must not publish a general `latest` release, inject upstream signing material, or change the upstream auto-update path. @@ -23,21 +29,34 @@ the upstream auto-update path. - A GitHub Actions issue event is untrusted input. Only the repository owner can approve an immutable, verified upstream release build. -## Preserved validated patch +## Preserved certified patch series -The current implementation is preserved independently of this automation: +Every candidate applies these independent commits in order: -- Branch: `fix/macos-private-ca-websocket` -- Commit: `6d03a38da5e3402bf97df1b3c46152887eb3778e` -- Subject: `fix(desktop): trust platform CAs for native websockets` +1. Private-CA support: + - Branch: `fix/macos-private-ca-websocket` + - Commit: `6d03a38da5e3402bf97df1b3c46152887eb3778e` + - Subject: `fix(desktop): trust platform CAs for native websockets` +2. Nostr event-integrity correction: + - Commit: `7dbfcd785be0a9c002863a793c4fbab89a6258c3` + - Subject: `fix(deps): patch nostr signature verification bypass` + - Effect: pins `nostr-relay-pool` 0.44.2 in both `Cargo.lock` files and + keeps vulnerability findings fatal in target-specific cargo-deny scans. -It uses `rustls-platform-verifier` with an explicit Rustls connector and routes +The first uses `rustls-platform-verifier` with an explicit Rustls connector and routes the primary relay, pairing, and huddle WebSocket paths through it. The commit -has the required DCO sign-off. Do not rewrite it. A future upstream PR starts +has the required DCO sign-off. Both commits are immutable inputs; do not rewrite +them. A future upstream private-CA PR starts from then-current `upstream/main`, carries a DCO sign-off on every commit, and is opened only when the upstream coordination issue asks for it or competing work is withdrawn. +These deviations retire independently. An upstream trust-store fix does not +remove the security patch, and an upstream security fix does not remove the +private-CA patch. A release candidate must either apply the corresponding +commit cleanly or prove the equivalent upstream behavior before that commit is +removed from the series. + ## Repository layout and Git workflow Permanent checkouts are recreated from the fork, never copied from a temporary @@ -68,6 +87,11 @@ tests, URLs, blockers, and the exact next action there. Historical delivery evidence remains in the GitHub release tickets and must not be substituted for the open retirement tracker. +The security correction and retirement are additionally tracked by Beads item +`tailscale-vault-of5-213.6`. It stays open until an official desktop release +contains the fixed dependency in both independently resolved lockfiles and the +same locked root and Apple Silicon scans pass without the downstream commit. + Before resuming, another agent must read, in order: 1. This document. @@ -104,7 +128,8 @@ open duplicates. `build-approved` is accepted only when the actor is exactly `BrianInAz`, the issue was generated by the monitor, the source tag still resolves to the -recorded SHA, and the pinned patch applies cleanly. Public comments and issue +recorded SHA, both pinned patch SHAs match the workflow, and the complete patch +series applies cleanly. Public comments and issue text never become command input. `skip` closes the ticket without a build. A conflict or failed preflight applies `remediation-required` and publishes no package. `built` records the approved-Mac handoff but does not close the ticket. @@ -119,30 +144,45 @@ to the `desktop-vX.Y.Z` namespace. After this correction is merged, a manual dispatch must create exactly one unapproved `desktop-v0.5.3` decision ticket. That dispatch does not authorize a package build or installation. -## Hosted validation and local package +## Hosted validation and hosted package -Standard GitHub-hosted Ubuntu runners are used for bounded, reproducible source -validation. GitHub-hosted macOS and Windows runners are not used. The approved -local Mac performs the package step after remote validation succeeds. +Standard GitHub-hosted Ubuntu runners perform bounded, reproducible source +validation. After Brian approves the monitor-created ticket and Ubuntu is +green, the standard GitHub-hosted `macos-15` Apple Silicon runner builds and +packages the candidate. GitHub documents standard hosted runners as free and +unlimited for public repositories and identifies `macos-15` as a standard M1 +ARM64 label: +. + +The package job fails closed if the fork is private or the resolved runner is +not ARM64. Do not substitute a billable larger runner. The hosted job receives +no Apple certificate, signing key, OIDC signing permission, private endpoint, +or homelab secret. It applies an ad-hoc signature, verifies the app and +checksums, uploads one seven-day Actions artifact, records the run and artifact +on the decision ticket, and applies `built` only after every package step is +green. The approved build has three gates: -1. Ubuntu checks out the exact upstream tag, applies the pinned patch without - committing it, then runs formatting, Clippy, focused connector tests, and - `just ci`. -2. The approved local Apple Silicon Mac runs the platform-trust and private WSS - gates before installation; no identity key is supplied to either gate. -3. The same local Mac runs `scripts/build-private-ca-macos.sh` with the ticket's - immutable tag and SHA. It uses existing upstream sidecar and - Tauri conventions. The package has no updater keys, no notarization, no - Apple Developer signing identity, and no private infrastructure values. - The updater remains disabled. The app is ad-hoc signed and verified before - the DMG is rebuilt. - -The local helper writes the arm64 DMG, SHA256SUMS, applied patch, and a manifest -to `dist/private-ca//`. Publish it as a fork prerelease only after the -private WSS gate and local acceptance are green; it is never the general -latest release. +1. Ubuntu checks out the exact upstream tag and applies the certified patch + series without committing it. It rejects either a vulnerable root graph or + a vulnerable Apple Silicon desktop graph with locked cargo-deny scans, then + runs formatting, Clippy, focused connector tests, and `just ci`. +2. Standard `macos-15` runs `scripts/build-private-ca-macos.sh` with the + ticket's immutable tag and SHA. It resolves and builds the pinned mesh native + runtime, uses existing upstream sidecar and Tauri conventions, disables the + updater, ad-hoc signs and verifies the app, rebuilds the DMG, verifies + `SHA256SUMS`, and uploads the immutable candidate artifact. +3. The private WSS gate runs on the existing homelab-connected `ghRunner` with + no identity key. Brian then downloads the exact artifact to his MacBook and + performs the state-preserving installation and runtime acceptance. + +The hosted helper repeats both locked advisory scans, then writes the arm64 +DMG, SHA256SUMS, combined applied patch, and a manifest containing both patch +SHAs to `dist/private-ca//`. Publish it as a fork prerelease only after +the private WSS gate and MacBook acceptance are green; it is never the general +latest release. Actions artifact storage is separate from free runner compute, +so the intermediate candidate is retained for only seven days. ## Private WSS gate @@ -204,7 +244,9 @@ closed normally. bundle, and prove it launches; keep the dev instance available. - When a release contains a maintainer-recognized upstream fix, test the official signed DMG through the same lifecycle before retiring this process. - Close issue #1 only after the chosen upstream outcome is recorded. + Close issue #1 only after the private-CA outcome is recorded. Retire the + security entry only after both upstream lockfiles and the Apple Silicon scan + prove RUSTSEC-2026-0224 absent in an official release. ## Documentation and completion @@ -216,15 +258,24 @@ acceptance, rollback bundle, Beads evidence, and documentation are all green. ## Verified release execution (v0.5.2) +This is historical delivery evidence for the private-CA deviation. It predates +the RUSTSEC-2026-0224 correction and is therefore not an approved source for a +new or replacement installation. No later candidate may be built unless both +certified patches and both dependency gates are green. + - Upstream tag: `v0.5.2` (SHA: `3e48f1b2365d326ee1c9582448d86a99b44ecd5d`) - Validated patch: `6d03a38da5e3402bf97df1b3c46152887eb3778e` - Public issue: [BrianInAz/buzz#3](https://github.com/BrianInAz/buzz/issues/3) -- Private WSS gate: [BjzyLabs/homelab-playbooks run 30532490990](https://github.com/BjzyLabs/homelab-playbooks/actions/runs/30532490990) (PASSED) +- Private WSS gate: + [BjzyLabs/homelab-playbooks run 30532490990](https://github.com/BjzyLabs/homelab-playbooks/actions/runs/30532490990) + (PASSED) - Local build: `scripts/build-private-ca-macos.sh v0.5.2 3e48f1b2365d326ee1c9582448d86a99b44ecd5d` - Package DMG: `dist/private-ca/v0.5.2/Buzz-v0.5.2-private-ca-arm64.dmg` - SHA256: `4bd377ece998835b699e89ac7efdbaeb0b9f9bdade650e2e7a949461ecc07b00` - Published prerelease: [buzz-private-ca-v0.5.2-r1](https://github.com/BrianInAz/buzz/releases/tag/buzz-private-ca-v0.5.2-r1) -- Security tracking: [BjzyLabs/homelab-playbooks#2224](https://github.com/BjzyLabs/homelab-playbooks/issues/2224) (OPEN - remediation checklist posted; gate boundary enforced) +- Security tracking: + [BjzyLabs/homelab-playbooks#2224](https://github.com/BjzyLabs/homelab-playbooks/issues/2224) + (OPEN - remediation checklist posted; gate boundary enforced) - Process inventory: - Official: `/Applications/Buzz.app/Contents/MacOS/buzz-desktop` (PID 41857, `xyz.block.buzz.app`) - Development: `target/debug/buzz-desktop` (PID 76852, `xyz.block.buzz.app.dev.fix-macos-private-ca-websocket`) diff --git a/scripts/build-private-ca-macos.sh b/scripts/build-private-ca-macos.sh index 2d897573e0..89edf14a78 100755 --- a/scripts/build-private-ca-macos.sh +++ b/scripts/build-private-ca-macos.sh @@ -4,7 +4,8 @@ set -euo pipefail readonly upstream_repository="https://github.com/block/buzz.git" readonly fork_repository="https://github.com/BrianInAz/buzz.git" -readonly patch_commit="6d03a38da5e3402bf97df1b3c46152887eb3778e" +readonly private_ca_patch_commit="6d03a38da5e3402bf97df1b3c46152887eb3778e" +readonly security_patch_commit="7dbfcd785be0a9c002863a793c4fbab89a6258c3" tag="${1:?usage: $0 [output-directory]}" source_sha="${2:?usage: $0 [output-directory]}" @@ -14,8 +15,12 @@ output_directory="${3:-$PWD/dist/private-ca/${tag}}" echo "this packaging helper must run on macOS" >&2 exit 1 } +[[ "$(uname -m)" == "arm64" ]] || { + echo "this packaging helper requires Apple Silicon" >&2 + exit 1 +} -for command in git just pnpm codesign hdiutil shasum; do +for command in git just pnpm cargo-deny codesign hdiutil shasum; do command -v "${command}" >/dev/null || { echo "required command is unavailable: ${command}" >&2 exit 1 @@ -34,19 +39,49 @@ git clone --depth 1 --branch "${tag}" "${upstream_repository}" "${work_directory echo "upstream tag did not resolve to the approved SHA" >&2 exit 1 } -git -C "${work_directory}/source" fetch --depth 2 "${fork_repository}" "${patch_commit}" -git -C "${work_directory}/source" cherry-pick --no-commit "${patch_commit}" +for patch_commit in "${private_ca_patch_commit}" "${security_patch_commit}"; do + git -C "${work_directory}/source" fetch --depth 2 "${fork_repository}" "${patch_commit}" + git -C "${work_directory}/source" cherry-pick --no-commit "${patch_commit}" +done git -C "${work_directory}/source" diff --check ( cd "${work_directory}/source" + cargo-deny --locked check --config deny.toml advisories + cargo-deny --locked \ + --manifest-path desktop/src-tauri/Cargo.toml \ + --target aarch64-apple-darwin \ + --exclude-dev \ + check --config deny.toml advisories just desktop-install-ci just _ensure-sidecar-stubs + sdk_manifest="$({ + cargo metadata --locked --manifest-path desktop/src-tauri/Cargo.toml \ + --features mesh-llm --format-version 1 + } | python3 -c 'import json, sys; data = json.load(sys.stdin); print(next(package["manifest_path"] for package in data["packages"] if package["name"] == "mesh-llm-sdk"))')" + mesh_root="$(dirname "${sdk_manifest}")" + while [[ "${mesh_root}" != "/" && ! -x "${mesh_root}/scripts/prepare-llama.sh" ]]; do + mesh_root="$(dirname "${mesh_root}")" + done + [[ -x "${mesh_root}/scripts/prepare-llama.sh" && -x "${mesh_root}/scripts/build-llama.sh" ]] || { + echo "mesh-llm native build scripts are unavailable" >&2 + exit 1 + } + export LLAMA_STAGE_BACKEND=metal + export LLAMA_STAGE_BUILD_DIR="${work_directory}/mesh-llama/build-stage-abi-metal" + export CMAKE_OSX_DEPLOYMENT_TARGET=10.15 + "${mesh_root}/scripts/prepare-llama.sh" pinned + "${mesh_root}/scripts/build-llama.sh" -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 cat > desktop/src-tauri/tauri.private-ca.conf.json <<'EOF' { "bundle": { "macOS": { "minimumSystemVersion": "10.15" }, "createUpdaterArtifacts": false } } EOF cd desktop - pnpm tauri build --verbose --no-sign --features mesh-llm --config src-tauri/tauri.private-ca.conf.json + CMAKE_POLICY_VERSION_MINIMUM=3.5 \ + MACOSX_DEPLOYMENT_TARGET=10.15 \ + SKIPPY_LLAMA_AUTO_BUILD=0 \ + TAURI_BUNDLER_DMG_IGNORE_CI=true \ + pnpm tauri build --verbose --no-sign --features mesh-llm \ + --config src-tauri/tauri.private-ca.conf.json ) app_path="${work_directory}/source/desktop/src-tauri/target/release/bundle/macos/Buzz.app" @@ -57,7 +92,7 @@ cp -R "${app_path}" "${output_directory}/dmg-root/Buzz.app" hdiutil create -volname Buzz -srcfolder "${output_directory}/dmg-root" -ov -format UDZO "${dmg_path}" cat > "${output_directory}/manifest.json" < "${output_directory}/private-ca.patch" ( diff --git a/scripts/test-private-ca-release-contract.sh b/scripts/test-private-ca-release-contract.sh index 4fc3e59a02..8297958b0f 100755 --- a/scripts/test-private-ca-release-contract.sh +++ b/scripts/test-private-ca-release-contract.sh @@ -3,8 +3,11 @@ set -euo pipefail workflow=".github/workflows/private-ca-release.yml" -local_builder="scripts/build-private-ca-macos.sh" +ci_workflow=".github/workflows/ci.yml" +docker_workflow=".github/workflows/docker.yml" +hosted_builder="scripts/build-private-ca-macos.sh" tag_parser="scripts/normalize-private-ca-desktop-tag.sh" +lifecycle_doc="docs/operations/private-ca-desktop-release-lifecycle.md" if [[ ! -f "${workflow}" ]]; then echo "missing ${workflow}" >&2 @@ -27,6 +30,46 @@ forbid() { fi } +require_ci() { + local expected="$1" + if ! grep -F -q -- "${expected}" "${ci_workflow}"; then + echo "${ci_workflow} must contain: ${expected}" >&2 + exit 1 + fi +} + +forbid_ci() { + local prohibited="$1" + if grep -F -q -- "${prohibited}" "${ci_workflow}"; then + echo "${ci_workflow} must not contain: ${prohibited}" >&2 + exit 1 + fi +} + +require_docker() { + local expected="$1" + if ! grep -F -q -- "${expected}" "${docker_workflow}"; then + echo "${docker_workflow} must contain: ${expected}" >&2 + exit 1 + fi +} + +forbid_docker() { + local prohibited="$1" + if grep -F -q -- "${prohibited}" "${docker_workflow}"; then + echo "${docker_workflow} must not contain: ${prohibited}" >&2 + exit 1 + fi +} + +require_doc() { + local expected="$1" + if ! grep -F -q -- "${expected}" "${lifecycle_doc}"; then + echo "${lifecycle_doc} must contain: ${expected}" >&2 + exit 1 + fi +} + require 'cron: "5 15 * * *"' require 'issues:' require 'labeled' @@ -38,17 +81,38 @@ require "github.event.label.name == 'build-approved'" require "github.event.label.name == 'skip'" require 'refs/tags/' require '6d03a38da5e3402bf97df1b3c46152887eb3778e' +require 'PRIVATE_CA_PATCH_COMMIT:' +require 'SECURITY_PATCH_COMMIT:' +require 'private_ca_patch_sha=' +require 'security_patch_sha=' +# shellcheck disable=SC2016 # These are intentionally literal workflow fragments. +require 'for patch_commit in "${PRIVATE_CA_PATCH_COMMIT}" "${SECURITY_PATCH_COMMIT}"' +# shellcheck disable=SC2016 # These are intentionally literal workflow fragments. +require '[[ "${private_ca_patch_sha}" == "${PRIVATE_CA_PATCH_COMMIT}" ]]' +# shellcheck disable=SC2016 # These are intentionally literal workflow fragments. +require '[[ "${security_patch_sha}" == "${SECURITY_PATCH_COMMIT}" ]]' require 'cherry-pick --no-commit' +require 'cargo-deny --locked check --config deny.toml advisories' +require "cargo-deny --locked \\" +require "--manifest-path desktop/src-tauri/Cargo.toml \\" +require "--target aarch64-apple-darwin \\" +require "--exclude-dev \\" require 'just ci' require 'Stub Tauri sidecar binaries' require 'desktop/src-tauri/binaries' require 'gh issue close' -require 'Local macOS package handoff' +require 'runs-on: macos-15' +require 'Build private-CA package on standard hosted Apple Silicon' +require 'github.event.repository.private' +require 'actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a' +require 'retention-days: 7' +require 'if-no-files-found: error' +require 'GitHub-hosted macOS package built' +require "gh issue edit \"\${ISSUE_NUMBER}\" --repo \"\${GITHUB_REPOSITORY}\" --add-label built" require 'scripts/build-private-ca-macos.sh' require 'scripts/normalize-private-ca-desktop-tag.sh' require '--assignee BrianInAz' require 'ticket already exists' -require "github.event.label.name == 'built'" require "github.event.label.name == 'accepted'" require 'accepted requires the built lifecycle state' require 'not a clean monitor-created ticket' @@ -63,12 +127,41 @@ forbid 'tailscale' forbid 'VAULT_' forbid 'APPLE_CERTIFICATE' forbid 'TAURI_SIGNING_PRIVATE_KEY' -forbid 'runs-on: macos' +forbid 'Local macOS package handoff' +forbid 'approved local Mac' +forbid "github.event.label.name == 'built'" +forbid 'id-token: write' +forbid 'secrets.' forbid 'actions/attest-build-provenance@' forbid 'gh release create' +forbid ' patch_sha=' + +if grep -Eq '^ PATCH_COMMIT:' "${workflow}"; then + echo "${workflow} must not use the legacy single PATCH_COMMIT input" >&2 + exit 1 +fi + +require_ci 'cargo metadata --locked --manifest-path desktop/src-tauri/Cargo.toml --features mesh-llm --format-version 1' +require_ci 'package["manifest_path"] for package in data["packages"] if package["name"] == "mesh-llm-sdk"' +require_ci 'tomllib.load(open("desktop/src-tauri/Cargo.lock", "rb"))' +forbid_ci 'tomllib.load(open("Cargo.lock", "rb"))' +# shellcheck disable=SC2016 # This is an intentionally literal workflow fragment. +forbid_ci 'find "${CARGO_HOME:-$HOME/.cargo}/git/checkouts"' + +# Pull requests are build-only. They may read registry caches, but must not +# authenticate to GHCR or try to export caches into an upstream namespace. +require_docker "if: github.event_name != 'pull_request'" +forbid_docker "github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository" +require_docker "if: github.event_name == 'pull_request' || github.repository == 'block/buzz'" +require_docker "if: github.event_name != 'pull_request' && github.repository == 'block/buzz'" + +require_doc "standard GitHub-hosted \`macos-15\` Apple Silicon runner" +require_doc 'standard hosted runners as free and' +require_doc 'unlimited for public repositories' +require_doc "Brian then downloads the exact artifact to his MacBook" -if [[ ! -x "${local_builder}" ]]; then - echo "missing executable ${local_builder}" >&2 +if [[ ! -x "${hosted_builder}" ]]; then + echo "missing executable ${hosted_builder}" >&2 exit 1 fi @@ -91,15 +184,16 @@ for tag in relay-v0.5.3 desktop-v0.5.3-rc.1 v0.5.3-beta.1 nonsense; do fi done -for expected in 'set -euo pipefail' 'createUpdaterArtifacts": false' 'codesign --verify --deep --strict' 'hdiutil create'; do - if ! grep -F -q -- "${expected}" "${local_builder}"; then - echo "${local_builder} must contain: ${expected}" >&2 +# shellcheck disable=SC2016 # These are intentionally literal builder fragments. +for expected in 'set -euo pipefail' "[[ \"\$(uname -m)\" == \"arm64\" ]]" 'private_ca_patch_commit=' 'security_patch_commit=' 'for patch_commit in "${private_ca_patch_commit}" "${security_patch_commit}"' 'cargo-deny --locked check --config deny.toml advisories' "--target aarch64-apple-darwin \\" '"patch_shas":[' 'cargo metadata --locked --manifest-path' 'prepare-llama.sh' 'build-llama.sh' 'SKIPPY_LLAMA_AUTO_BUILD=0' 'createUpdaterArtifacts": false' 'codesign --force --deep --sign -' 'codesign --verify --deep --strict' 'hdiutil create'; do + if ! grep -F -q -- "${expected}" "${hosted_builder}"; then + echo "${hosted_builder} must contain: ${expected}" >&2 exit 1 fi done -if grep -i -F -q -- 'insecure_skip_verify' "${local_builder}"; then - echo "${local_builder} must not bypass TLS verification" >&2 +if grep -i -F -q -- 'insecure_skip_verify' "${hosted_builder}"; then + echo "${hosted_builder} must not bypass TLS verification" >&2 exit 1 fi