ci(release): build, sign and notarize the DMG on a hosted macOS runner - #69
ci(release): build, sign and notarize the DMG on a hosted macOS runner#69div0-space wants to merge 10 commits into
Conversation
… a hosted macOS runner - new workflow release-dmg.yml (macos-15, workflow_dispatch + v* tags): checks out vibecrafted + donors vc-frame/vc-terminal, materializes $KEYS from secrets, runs make release, re-walks the DMG through the hygiene gate, scrubs keys, uploads DMG + receipts as artifact (no publish) - payload-hygiene.sh: PAYLOAD_HYGIENE_EPHEMERAL_ROOTS declares builder roots that identify nobody (/Users/runner, $GITHUB_WORKSPACE); not an allowlist of payload strings — every surviving literal is still refused; unset = operator boundary unchanged - why: operator decision 2026-08-22 — the local operator build cannot pass its own gate (rustc leaves ~411 $HOME literals in vc-frame via inlined upstream spans); a hosted runner has no operator identity to leak, so the gate's promise holds by construction - measured: three clean local builds of PR66/main all stop at 'names the build host in 3 place(s)' (vc-frame only) Authored-By: claude <agents@vetcoders.io> session_id: 67428033-45fe-4310-abb5-09b998f5da06 time: 2026-08-22T19:53:05+02:00 runtime: terminal
…s an encrypted asset - assets/fonts/SpotMono.ttc.enc: AES-256-CBC/PBKDF2 ciphertext of the licensed font (434 KB exceeds the 48 KB Actions secret limit; plaintext must not sit in a public repo) - release-dmg.yml: decrypts it into $KEYS/fonts with secret VC_FONT_PASSPHRASE; replaces the VC_SPOT_MONO_TTC_B64 secret - passphrase also kept in the operator's ~/.keys for rotation Authored-By: claude <agents@vetcoders.io> session_id: 67428033-45fe-4310-abb5-09b998f5da06 time: 2026-08-22T20:09:33+02:00 runtime: terminal
There was a problem hiding this comment.
An organization admin can view or raise the cap at claude.ai/admin-settings/claude-code. The cap resets at the start of the next billing period.
Once the cap resets or is raised, reopen this pull request to trigger a review.
…rst hosted run - workflow_dispatch is only registered once the file exists on main; a push trigger on this branch lets PR #69 prove the runner path before merge - remove before/at merge Authored-By: claude <agents@vetcoders.io> session_id: 67428033-45fe-4310-abb5-09b998f5da06 time: 2026-08-22T20:14:17+02:00 runtime: terminal
There was a problem hiding this comment.
Pull request overview
This PR adds a GitHub Actions workflow to build/sign/(optionally) notarize Vibecrafted DMGs on a hosted macOS runner, and updates the payload hygiene gate to optionally ignore caller-declared “ephemeral” runner roots that don’t identify a specific builder.
Changes:
- Add
.github/workflows/release-dmg.ymlto build onmacos-15, materialize signing material from repo secrets, runmake release, re-run the payload hygiene gate, and upload artifacts. - Extend
scripts/lib/payload-hygiene.shwithPAYLOAD_HYGIENE_EPHEMERAL_ROOTSsupport so CI can declare non-identifying roots (e.g., hosted runner home/workspace) to skip in the literal set.
Summary
Primary functional risk is around the workflow’s notarize toggle: the current job structure doesn’t align with what the release script actually produces in --no-notarize mode.
Files Changed
scripts/lib/payload-hygiene.sh.github/workflows/release-dmg.yml
Verification Performed
- None (review-only).
Verification Not Performed
- No hosted workflow run validation.
- No local
make release/make payload-hygieneexecution.
Risks Or Follow-Up
- The
workflow_dispatchnotarize: falsemode currently won’t work as intended (missing outputs + unconditional secrets), and needs either a separate build path or a release-script behavior change.
Reviewed changes
Copilot reviewed 2 out of 3 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
scripts/lib/payload-hygiene.sh |
Adds an opt-in mechanism to treat hosted-runner roots as “ephemeral” and exclude them from forbidden literal roots. |
.github/workflows/release-dmg.yml |
New workflow to build/sign/notarize DMGs on hosted macOS and upload build artifacts. |
Suppressed comments (1)
.github/workflows/release-dmg.yml:142
- When workflow_dispatch inputs.notarize=false, RELEASE_FLAGS becomes "--no-notarize" but the job still runs
make releaseand then asserts files that are only produced in the notarization path (dist/*.dmg.sha256 and dist/release-output.json). In scripts/build-vibecrafted-release.sh, MODE=dmg exits right after create_dmg (no checksum, no release-output tuple), so this step (and the upload paths below) will fail for sign-only builds.
-pass env:VC_FONT_PASSPHRASE
file "$KEYS/Certificates.p12" "$KEYS/fonts/SpotMono.ttc"
- name: Build, sign, notarize
working-directory: vibecrafted
env:
RELEASE_FLAGS: ${{ (github.event_name == 'workflow_dispatch' && inputs.notarize == false) && '--no-notarize' || '' }}
run: |
set -euo pipefail
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| for v in VC_CERT_P12_B64 VC_CERT_PASSWORD VC_SIGNING_IDENTITY VC_SIGNING_KEY VC_NOTARY_APPLE_ID VC_NOTARY_TEAM_ID VC_NOTARY_PASSWORD VC_FONT_PASSPHRASE; do | ||
| [[ -n "${!v:-}" ]] || { echo "::error::missing secret $v"; exit 1; } | ||
| done | ||
| umask 077 | ||
| mkdir -p "$KEYS/fonts" | ||
| printf '%s' "$VC_CERT_P12_B64" | base64 --decode > "$KEYS/Certificates.p12" | ||
| printf '%s\n' "$VC_CERT_PASSWORD" > "$KEYS/cert_password.txt" | ||
| printf '%s\n' "$VC_SIGNING_IDENTITY" > "$KEYS/signing-identity.txt" | ||
| printf '%s\n' "$VC_SIGNING_KEY" > "$KEYS/vibecrafted-signing.key" | ||
| printf 'NOTARY_APPLE_ID=%s\nNOTARY_TEAM_ID=%s\nNOTARY_PASSWORD=%s\n' \ | ||
| "$VC_NOTARY_APPLE_ID" "$VC_NOTARY_TEAM_ID" "$VC_NOTARY_PASSWORD" > "$KEYS/.notary.env" |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 2 out of 3 changed files in this pull request and generated 1 comment.
Suppressed comments (2)
Previously missed (1) — in code that hasn't changed since the last review.
scripts/lib/payload-hygiene.sh:80
payload_hygiene_is_ephemeraluses Bash pattern matching ([[ "$path" == "$root"/* ]]), so any glob metacharacters in an env-provided root (e.g.*,?,[) could unintentionally match more than the declared prefix and skip additional forbidden literals. Since this function is meant to treat roots as literal path prefixes, the check should avoid pattern semantics.
while IFS= read -r root; do
root="${root%/}"
[[ -n "$root" && "$root" != "/" ]] || continue
[[ "$path" == "$root" || "$path" == "$root"/* ]] && return 0
done <<< "$PAYLOAD_HYGIENE_EPHEMERAL_ROOTS"
.github/workflows/release-dmg.yml:114
- The workflow advertises
notarize: falseas “sign only”, but the “Materialize signing keys from secrets” step still requires the notary secrets and always writes$KEYS/.notary.env. This makes sign-only runs fail unnecessarily (and forces storing notary creds even when not used).
env:
VC_CERT_P12_B64: ${{ secrets.VC_CERT_P12_B64 }}
VC_CERT_PASSWORD: ${{ secrets.VC_CERT_PASSWORD }}
VC_SIGNING_IDENTITY: ${{ secrets.VC_SIGNING_IDENTITY }}
VC_SIGNING_KEY: ${{ secrets.VC_SIGNING_KEY }}
| set -euo pipefail | ||
| dmg="$(ls dist/Vibecrafted_*.dmg | sort | tail -1)" | ||
| bash scripts/payload-hygiene-artifact.sh "$dmg" | ||
| spctl -a -t open --context context:primary-signature -v "$dmg" || true |
- vetcoders/vc-frame main (7ab84069, 2026-08-14) lacks the release-binary target and the baked-manifest fix; develop (017e3839) is 13 ahead and is what the operator's local donor builds from - first hosted run 32590319025 proved toolchain, keys and vc-terminal; it stopped exactly on 'No rule to make target release-binary' Authored-By: claude <agents@vetcoders.io> session_id: 67428033-45fe-4310-abb5-09b998f5da06 time: 2026-08-22T20:29:46+02:00 runtime: terminal
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 2 out of 3 changed files in this pull request and generated 2 comments.
Suppressed comments (3)
Previously missed (1) — in code that hasn't changed since the last review.
scripts/lib/payload-hygiene.sh:75
- New behavior for
PAYLOAD_HYGIENE_EPHEMERAL_ROOTS(skipping caller-declared CI runner roots) isn’t covered by tests. There are existing contract tests forpayload_hygiene_literalsintests/tui/test_payload_hygiene.py, but none assert that ephemeral roots are filtered while non-ephemeral roots still fail the gate when the env var is set.
payload_hygiene_is_ephemeral() {
local path="${1%/}" root
[[ -n "${PAYLOAD_HYGIENE_EPHEMERAL_ROOTS:-}" ]] || return 1
while IFS= read -r root; do
root="${root%/}"
.github/workflows/release-dmg.yml:153
- This
spctlassessment is currently ignored (|| true), which means the workflow can upload a DMG even if Gatekeeper assessment fails. Since this job is meant to produce a canonical release artifact, the assessment should fail the run.
spctl -a -t open --context context:primary-signature -v "$dmg" || true
.github/workflows/release-dmg.yml:123
- The workflow supports
notarize: false(sign-only), but this step still requires the notary secrets and always writes.notary.env. That makes sign-only dispatch runs fail even though notarization is disabled.
for v in VC_CERT_P12_B64 VC_CERT_PASSWORD VC_SIGNING_IDENTITY VC_SIGNING_KEY VC_NOTARY_APPLE_ID VC_NOTARY_TEAM_ID VC_NOTARY_PASSWORD VC_FONT_PASSPHRASE; do
[[ -n "${!v:-}" ]] || { echo "::error::missing secret $v"; exit 1; }
done
| set -euo pipefail | ||
| sudo xcode-select -s "$(ls -d /Applications/Xcode*.app | sort -V | tail -1)/Contents/Developer" | ||
| xcodebuild -version |
| done | ||
| umask 077 | ||
| mkdir -p "$KEYS/fonts" | ||
| printf '%s' "$VC_CERT_P12_B64" | base64 --decode > "$KEYS/Certificates.p12" |
- run 32591086444 reached xtask build and panicked in prost-build: no protoc on macos-15; brew protobuf provides it Authored-By: claude <agents@vetcoders.io> session_id: 67428033-45fe-4310-abb5-09b998f5da06 time: 2026-08-22T20:41:58+02:00 runtime: terminal
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 2 out of 3 changed files in this pull request and generated 1 comment.
Suppressed comments (3)
Previously missed (1) — in code that hasn't changed since the last review.
.github/workflows/release-dmg.yml:144
- The
notarizeinput is described as “false = sign only”, but in that modeRELEASE_FLAGSadds--no-notarizewhile still runningmake release. The underlying builder exits early in--no-notarizemode (no release tuple, no.sha256), so later steps that expectdist/*.dmg.sha256anddist/release-output.jsonwill fail (and the upload list includes those files unconditionally). This likely needs an explicit branch: runmake releaseonly when notarizing, otherwise run the appropriatemake dmg/make dmg-signedtarget and adjust the follow-up checks and uploaded artifacts accordingly.
RELEASE_FLAGS: ${{ (github.event_name == 'workflow_dispatch' && inputs.notarize == false) && '--no-notarize' || '' }}
run: |
set -euo pipefail
rustup show active-toolchain || true
make release RELEASE_FLAGS="$RELEASE_FLAGS" KEYS="$KEYS"
.github/workflows/release-dmg.yml:153
- This
spctlassessment is currently allowed to fail (|| true), which can result in uploading a DMG that Gatekeeper would reject (especially in a future “sign only” path). If this is meant as a safety check, it should fail the job on error (or be conditioned on an explicit “informational only” mode).
dmg="$(ls dist/Vibecrafted_*.dmg | sort | tail -1)"
bash scripts/payload-hygiene-artifact.sh "$dmg"
spctl -a -t open --context context:primary-signature -v "$dmg" || true
.github/workflows/release-dmg.yml:123
notarize: falseis intended to allow a sign-only run, but this step hard-requires the notary secrets (VC_NOTARY_*) and always writes$KEYS/.notary.env. This makes the sign-only option unusable (even before considering the downstream artifact expectations). Consider only requiring/materializing notary credentials when notarization is enabled.
set -euo pipefail
for v in VC_CERT_P12_B64 VC_CERT_PASSWORD VC_SIGNING_IDENTITY VC_SIGNING_KEY VC_NOTARY_APPLE_ID VC_NOTARY_TEAM_ID VC_NOTARY_PASSWORD VC_FONT_PASSPHRASE; do
[[ -n "${!v:-}" ]] || { echo "::error::missing secret $v"; exit 1; }
done
| with: | ||
| path: vibecrafted | ||
| ref: ${{ inputs.ref || github.ref }} | ||
| fetch-depth: 0 | ||
| persist-credentials: false |
…wn for the server shell - run 32591700337 built vc-frame and vc-start on the runner; build-server-release stopped on missing cargo-leptos - pinned to 0.3.7, the version the operator's machine releases with Authored-By: claude <agents@vetcoders.io> session_id: 67428033-45fe-4310-abb5-09b998f5da06 time: 2026-08-22T21:08:29+02:00 runtime: terminal
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 2 out of 3 changed files in this pull request and generated no new comments.
Suppressed comments (5)
Previously missed (2) — in code that hasn't changed since the last review.
.github/workflows/release-dmg.yml:50
- The workflow overrides
permissionsto onlycontents: read, butactions/upload-artifactrequires a token that can write workflow run artifacts. With the current permissions block, the upload step is likely to fail with "Resource not accessible by integration" / 403.
This issue also appears in the following locations of the same file:
- line 139
- line 139
permissions:
contents: read
scripts/lib/payload-hygiene.sh:79
payload_hygiene_is_ephemeralchanges the gate’s literal set based on a new environment variable, but there’s no corresponding contract test coverage. The repo already has payload-hygiene contract tests (e.g.tests/tui/test_payload_hygiene.pywithrun_library(...)), so adding a test that verifiespayload_hygiene_literalsexcludes candidates underPAYLOAD_HYGIENE_EPHEMERAL_ROOTSwould help prevent regressions.
payload_hygiene_is_ephemeral() {
local path="${1%/}" root
[[ -n "${PAYLOAD_HYGIENE_EPHEMERAL_ROOTS:-}" ]] || return 1
while IFS= read -r root; do
root="${root%/}"
[[ -n "$root" && "$root" != "/" ]] || continue
[[ "$path" == "$root" || "$path" == "$root"/* ]] && return 0
done <<< "$PAYLOAD_HYGIENE_EPHEMERAL_ROOTS"
return 1
.github/workflows/release-dmg.yml:125
- The "notarize" input isn’t honored in the secrets materialization step: even when
inputs.notarizeis false (sign-only), this step still requires the notary secrets and always writes$KEYS/.notary.env.scripts/build-vibecrafted-release.shonly needs notary credentials when notarizing, so this currently prevents running a sign-only build without also configuring the notary secrets.
run: |
set -euo pipefail
for v in VC_CERT_P12_B64 VC_CERT_PASSWORD VC_SIGNING_IDENTITY VC_SIGNING_KEY VC_NOTARY_APPLE_ID VC_NOTARY_TEAM_ID VC_NOTARY_PASSWORD VC_FONT_PASSPHRASE; do
[[ -n "${!v:-}" ]] || { echo "::error::missing secret $v"; exit 1; }
done
.github/workflows/release-dmg.yml:146
- This workflow currently doesn’t enable
--snapshot-donors. The release builder warns that without it vc-frame’s git-tracked WASM plugin blobs ship as-is (potentially compiled elsewhere with embedded host paths), and only--snapshot-donorsrebuilds them under the release remaps (scripts/build-vibecrafted-release.sharound the vc-frame plugin rebuild). For a hosted release workflow, it’s safer to always build with--snapshot-donors.
- name: Build, sign, notarize
working-directory: vibecrafted
env:
RELEASE_FLAGS: ${{ (github.event_name == 'workflow_dispatch' && inputs.notarize == false) && '--no-notarize' || '' }}
run: |
set -euo pipefail
rustup show active-toolchain || true
make release RELEASE_FLAGS="$RELEASE_FLAGS" KEYS="$KEYS"
.github/workflows/release-dmg.yml:147
- When
inputs.notarizeis false the workflow passes--no-notarizeintomake release, but the builder’s--no-notarizemode exits aftercreate_dmgand does not emitdist/*.dmg.sha256nordist/release-output.json/.sig(seescripts/build-vibecrafted-release.sh: it exits early forMODE == dmg). This means the subsequentlsand the artifact upload paths will fail in the sign-only case.
- name: Build, sign, notarize
working-directory: vibecrafted
env:
RELEASE_FLAGS: ${{ (github.event_name == 'workflow_dispatch' && inputs.notarize == false) && '--no-notarize' || '' }}
run: |
set -euo pipefail
rustup show active-toolchain || true
make release RELEASE_FLAGS="$RELEASE_FLAGS" KEYS="$KEYS"
ls -la dist/*.dmg dist/*.dmg.sha256 dist/release-output.json
…not name the operator - run 32593008654 assembled the whole app on macos-15 and stopped at the gate with 'no --forbid literal given': every build-host root was ephemeral, so the scanner refused an empty proof — correctly - payload-hygiene.sh: PAYLOAD_HYGIENE_EXTRA_LITERALS appends caller literals; the workflow passes the operator's home and workshop, which is exactly what a signed artifact built elsewhere must not carry - these two paths already appear in the public git history; forbidding them leaks nothing Authored-By: claude <agents@vetcoders.io> session_id: 67428033-45fe-4310-abb5-09b998f5da06 time: 2026-08-22T21:48:37+02:00 runtime: terminal
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 2 out of 3 changed files in this pull request and generated 1 comment.
Suppressed comments (3)
Previously missed (1) — in code that hasn't changed since the last review.
.github/workflows/release-dmg.yml:151
- The
notarizetoggle is currently inconsistent with the build + artifact expectations: whenRELEASE_FLAGSincludes--no-notarize,scripts/build-vibecrafted-release.shexits aftercreate_dmgand does not writedist/*.dmg.sha256ordist/release-output.json(.sig). This makes thels -la ... dist/release-output.jsonline and the uploadpath:list fail for sign-only runs.
RELEASE_FLAGS: ${{ (github.event_name == 'workflow_dispatch' && inputs.notarize == false) && '--no-notarize' || '' }}
run: |
set -euo pipefail
rustup show active-toolchain || true
make release RELEASE_FLAGS="$RELEASE_FLAGS" KEYS="$KEYS"
.github/workflows/release-dmg.yml:142
base64 --decodeis not portable on macOS runners (BSDbase64typically lacks--decode), and the step currently always requires/writes notary credentials even wheninputs.notarizeis false. This makes the workflow brittle and prevents running a sign-only build without Apple notary secrets once that path is wired up.
set -euo pipefail
for v in VC_CERT_P12_B64 VC_CERT_PASSWORD VC_SIGNING_IDENTITY VC_SIGNING_KEY VC_NOTARY_APPLE_ID VC_NOTARY_TEAM_ID VC_NOTARY_PASSWORD VC_FONT_PASSPHRASE; do
[[ -n "${!v:-}" ]] || { echo "::error::missing secret $v"; exit 1; }
done
umask 077
mkdir -p "$KEYS/fonts"
printf '%s' "$VC_CERT_P12_B64" | base64 --decode > "$KEYS/Certificates.p12"
printf '%s\n' "$VC_CERT_PASSWORD" > "$KEYS/cert_password.txt"
printf '%s\n' "$VC_SIGNING_IDENTITY" > "$KEYS/signing-identity.txt"
printf '%s\n' "$VC_SIGNING_KEY" > "$KEYS/vibecrafted-signing.key"
printf 'NOTARY_APPLE_ID=%s\nNOTARY_TEAM_ID=%s\nNOTARY_PASSWORD=%s\n' \
"$VC_NOTARY_APPLE_ID" "$VC_NOTARY_TEAM_ID" "$VC_NOTARY_PASSWORD" > "$KEYS/.notary.env"
openssl enc -d -aes-256-cbc -pbkdf2 -iter 200000 \
-in vibecrafted/assets/fonts/SpotMono.ttc.enc -out "$KEYS/fonts/SpotMono.ttc" \
-pass env:VC_FONT_PASSPHRASE
file "$KEYS/Certificates.p12" "$KEYS/fonts/SpotMono.ttc"
.github/workflows/release-dmg.yml:160
spctlassessment is always ignored via|| true, so this step won't fail even if the DMG fails Gatekeeper assessment. If this is meant as a verification gate, it should be conditional (e.g. enforce for notarized builds) rather than unconditionally silenced.
spctl -a -t open --context context:primary-signature -v "$dmg" || true
| payload_hygiene_is_ephemeral() { | ||
| local path="${1%/}" root | ||
| [[ -n "${PAYLOAD_HYGIENE_EPHEMERAL_ROOTS:-}" ]] || return 1 | ||
| while IFS= read -r root; do | ||
| root="${root%/}" |
…pshot-donors) - run #5 assembled Vibecrafted.app but the hygiene gate refused it: 411x /Users/polyversai + 13x /Volumes/vc-workspace in Contents/Helpers/vc-frame - source: vc-frame's git-tracked zellij-utils/assets/plugins/*.wasm, built on the operator machine and embedded via include_bytes!; no runner flag can rewrite bytes compiled elsewhere - the builder already recompiles them under its remaps behind --snapshot-donors; on a hosted runner the donors are throwaway checkouts, so it is always on Authored-By: claude <agents@vetcoders.io> session_id: 67428033-45fe-4310-abb5-09b998f5da06 time: 2026-08-22T22:29:45+02:00 runtime: terminal
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 2 out of 3 changed files in this pull request and generated no new comments.
Suppressed comments (2)
.github/workflows/release-dmg.yml:130
- The step documents
notarize: false = sign only, but the secret validation loop still requires the notarytool credentials even when notarization is disabled, preventing sign-only runs when those secrets are intentionally unset.
set -euo pipefail
for v in VC_CERT_P12_B64 VC_CERT_PASSWORD VC_SIGNING_IDENTITY VC_SIGNING_KEY VC_NOTARY_APPLE_ID VC_NOTARY_TEAM_ID VC_NOTARY_PASSWORD VC_FONT_PASSPHRASE; do
[[ -n "${!v:-}" ]] || { echo "::error::missing secret $v"; exit 1; }
done
.github/workflows/release-dmg.yml:134
base64 --decodeis a GNU option and is not supported by the default macOS/usr/bin/base64on GitHub-hosted runners; this will likely fail to decode the .p12.
umask 077
mkdir -p "$KEYS/fonts"
printf '%s' "$VC_CERT_P12_B64" | base64 --decode > "$KEYS/Certificates.p12"
printf '%s\n' "$VC_CERT_PASSWORD" > "$KEYS/cert_password.txt"
…the search list - run #6 passed the hygiene gate (0 literals) and died in codesign: "The specified item could not be found in the keychain" although find-identity saw the Developer ID in the ephemeral keychain - codesign chains the leaf through the search list; the hosted image also lacks Apple's Developer ID G2 intermediate - builder: KEYCHAIN_SESSION_REGISTER_SEARCH_LIST now honours VIBECRAFTED_KEYCHAIN_SEARCH_LIST (default 0 — the operator machine keeps its Codescribe-safe behaviour) - workflow: sets it to 1 and imports DeveloperIDG2CA.cer into the runner's login keychain Authored-By: claude <agents@vetcoders.io> session_id: 67428033-45fe-4310-abb5-09b998f5da06 time: 2026-08-22T23:31:15+02:00 runtime: terminal
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 3 out of 4 changed files in this pull request and generated 1 comment.
Suppressed comments (2)
Previously missed (1) — in code that hasn't changed since the last review.
scripts/lib/payload-hygiene.sh:80
payload_hygiene_is_ephemeraluses Bash pattern matching for prefix checks ([[ "$path" == "$root"/* ]]). If a caller accidentally provides a root containing glob metacharacters (e.g.[or*) or an overly generic root like/Users, the comparison can match more than intended and silently drop critical forbid literals, weakening the hygiene gate.
while IFS= read -r root; do
root="${root%/}"
[[ -n "$root" && "$root" != "/" ]] || continue
[[ "$path" == "$root" || "$path" == "$root"/* ]] && return 0
done <<< "$PAYLOAD_HYGIENE_EPHEMERAL_ROOTS"
scripts/lib/payload-hygiene.sh:104
- This change adds two new environment-driven behaviors (
PAYLOAD_HYGIENE_EPHEMERAL_ROOTSandPAYLOAD_HYGIENE_EXTRA_LITERALS) that directly affect what literals are forbidden. There are existing contract tests forpayload_hygiene_literalsintests/tui/test_payload_hygiene.py, but none cover the new env vars, so regressions here could weaken the gate without being caught.
# PAYLOAD_HYGIENE_EXTRA_LITERALS — newline-separated literals a caller adds on
# top of the build-host set. A hosted runner declares its own roots ephemeral
# and then has nothing of its own to forbid; what it must still prove is that
# the payload does not name the OPERATOR whose keys sign it. The operator's
# home and workshop are those literals.
| curl -fsSL -o DeveloperIDG2CA.cer https://www.apple.com/certificateauthority/DeveloperIDG2CA.cer | ||
| security import DeveloperIDG2CA.cer -k "$HOME/Library/Keychains/login.keychain-db" -T /usr/bin/codesign | ||
| rm -f DeveloperIDG2CA.cer |
…r, document the hosted path - run 32600012658 produced Vibecrafted_4.2.4-20260822-8d041616.dmg: notarized, stapled, accepted by spctl, clean through the hygiene gate on the operator machine - the push trigger on ci/release-dmg-runner was only for bring-up; tags v* and workflow_dispatch remain - RELEASE_KICKOFF: the root workflow is no longer read-only; hosted runner vs operator machine, publication stays an explicit step Authored-By: claude <agents@vetcoders.io> session_id: 67428033-45fe-4310-abb5-09b998f5da06 time: 2026-08-23T00:19:26+02:00 runtime: claude-code
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 4 out of 5 changed files in this pull request and generated no new comments.
Suppressed comments (2)
Previously missed (1) — in code that hasn't changed since the last review.
.github/workflows/release-dmg.yml:177
- The “Scrub keys” step uses
rm -rf "$KEYS"without any guard. A defensive check helps avoid accidental deletion if the variable ever becomes empty or/due to a future edit or env change.
- name: Scrub keys
if: always()
run: rm -rf "$KEYS"
.github/workflows/release-dmg.yml:127
- In the “Materialize signing keys from secrets” step, (1) the script always requires notary secrets even when
inputs.notarizeis false (sign-only mode), and (2)base64 --decodeis GNU-specific and is likely to fail on the hosted macOS runner’s BSDbase64. Consider gating the notary secrets and.notary.envcreation on a computed NOTARIZE flag, and usebase64 -Don macOS.
for v in VC_CERT_P12_B64 VC_CERT_PASSWORD VC_SIGNING_IDENTITY VC_SIGNING_KEY VC_NOTARY_APPLE_ID VC_NOTARY_TEAM_ID VC_NOTARY_PASSWORD VC_FONT_PASSPHRASE; do
[[ -n "${!v:-}" ]] || { echo "::error::missing secret $v"; exit 1; }
done
Why
Three clean local
make releaseruns (PR66 and main) stop at the payload-hygiene gate:Helpers/vc-framecarries ~411$HOMEliterals that--remap-path-prefixcannot reach (inlined upstream spans + rustup std). No DMG since 4.1.0 (2026-08-17) passes the gate, and that one fails it in 17 places. The operator build cannot satisfy its own gate.A hosted macOS runner has no operator identity:
/Users/runnerand$GITHUB_WORKSPACEare identical on every runner. Operator decision 2026-08-22: signing and notarization may run there.What
.github/workflows/release-dmg.yml— macos-15,workflow_dispatch(ref, donor refs, notarize toggle) +v*tags. Checks out vibecrafted + donorsvc-frame/vc-terminal, materializes$KEYSfrom repository secrets,make release, re-walks the DMG throughpayload-hygiene-artifact.sh, scrubs keys, uploads DMG + receipts as a run artifact. No publish —publish-vibecrafted-release.shstays the explicit operator step.scripts/lib/payload-hygiene.sh—PAYLOAD_HYGIENE_EPHEMERAL_ROOTS: caller-declared builder roots that identify nobody. Not an allowlist of payload strings: every surviving literal is still refused. Unset = operator boundary unchanged.assets/fonts/SpotMono.ttc.enc— licensed font as AES-256-CBC/PBKDF2 ciphertext (434 KB > 48 KB secret limit; plaintext must not sit in a public repo). Decrypted on the runner withVC_FONT_PASSPHRASE.Secrets (repo-level, no collision with org)
VC_CERT_P12_B64,VC_CERT_PASSWORD,VC_SIGNING_IDENTITY,VC_SIGNING_KEY,VC_NOTARY_APPLE_ID,VC_NOTARY_TEAM_ID,VC_NOTARY_PASSWORD,VC_FONT_PASSPHRASE. Org secretVIBECRAFTED_SIGNING_KEY(2026-04-08, ALL) is not used by this workflow — to reconcile.Verification
First hosted run dispatched from this branch with
ref=main; result linked in comments.docs/RELEASE_KICKOFF.mdstill says "tag workflow is read-only" — to update once the run is green.🤖 Generated with Claude Code