From 3c422b18245519917c3df308d2e0d2efe4b08d98 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 18 Jun 2026 00:49:26 +0000 Subject: [PATCH] ci(cli): surface notary log on non-Accepted; record signing validation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit macOS validation (Developer ID D77MCV9NZ7) confirmed the hardened-runtime entitlements are correct and load-bearing: amy init derives a secp256k1 key cleanly, and dropping disable-library-validation reproduces the runtime dlopen Team-ID failure. The one unverified gap is whether Apple's notary service accepts the unsigned Mach-O dylibs embedded inside lib/*.jar (secp256k1/jna/sqlite/skiko), which it inspects recursively. - create-release.yml: the notarize step now submits with --output-format json, and on any non-Accepted status dumps `notarytool log` (per-file issues) and fails — so the first real run names the offending files instead of failing opaquely. No speculative in-jar signing yet; gather the log first. - BUILDING.md: record the validation result, the embedded-jar-native risk, the one-run way to decide it (workflow_dispatch dry_run with MAC_* secrets), and the staged fixes (sign-in-jar and/or strip the skiko/Compose leak). Note the desktop app shares the same jars and needs its own dry-run. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_015sso31DfSF9B6EFCVkEqWD --- .github/workflows/create-release.yml | 29 ++++++++++++++++++++++++---- BUILDING.md | 22 +++++++++++++++++++++ 2 files changed, 47 insertions(+), 4 deletions(-) diff --git a/.github/workflows/create-release.yml b/.github/workflows/create-release.yml index 7223786d79..39b6394407 100644 --- a/.github/workflows/create-release.yml +++ b/.github/workflows/create-release.yml @@ -323,10 +323,31 @@ jobs: done < <(find "$IMG" -type f) codesign --verify --strict --verbose=2 "$IMG/runtime/bin/java" # Notarize: zip the signed image, submit, wait for Apple's verdict. - ditto -c -k --keepParent "$IMG" "$RUNNER_TEMP/amy-notarize.zip" - xcrun notarytool submit "$RUNNER_TEMP/amy-notarize.zip" \ - --apple-id "$NOTARY_APPLE_ID" --password "$NOTARY_PASSWORD" \ - --team-id "$NOTARY_TEAM_ID" --wait + # The notary service recursively inspects the lib/*.jar files, so any + # unsigned Mach-O embedded in them (secp256k1/jna/sqlite/skiko natives) + # can come back Invalid. Surface the per-file log so the first real run + # is diagnostic rather than a bare failure. + ZIP="$RUNNER_TEMP/amy-notarize.zip" + OUT="$RUNNER_TEMP/notary-submit.json" + ditto -c -k --keepParent "$IMG" "$ZIP" + if ! xcrun notarytool submit "$ZIP" \ + --apple-id "$NOTARY_APPLE_ID" --password "$NOTARY_PASSWORD" \ + --team-id "$NOTARY_TEAM_ID" --wait --output-format json > "$OUT"; then + echo "::warning::notarytool submit exited non-zero" + fi + cat "$OUT" + STATUS="$(jq -r '.status // "Unknown"' "$OUT" 2>/dev/null || echo Unknown)" + SUBMISSION_ID="$(jq -r '.id // empty' "$OUT" 2>/dev/null || true)" + if [ "$STATUS" != "Accepted" ]; then + echo "::error::Notarization status: $STATUS" + if [ -n "$SUBMISSION_ID" ]; then + echo "----- notary log -----" + xcrun notarytool log "$SUBMISSION_ID" \ + --apple-id "$NOTARY_APPLE_ID" --password "$NOTARY_PASSWORD" \ + --team-id "$NOTARY_TEAM_ID" || true + fi + exit 1 + fi # jpackage pins libicu to the build host's version (libicu74 on # ubuntu-24.04). Rewrite the .deb so it installs across Debian/Ubuntu. diff --git a/BUILDING.md b/BUILDING.md index aefce7849b..1fcd052bca 100644 --- a/BUILDING.md +++ b/BUILDING.md @@ -281,6 +281,28 @@ Gatekeeper verifies notarization **online** on first run — fine for a CLI. Note the Homebrew-core jvm bundle (`amy--jvm.tar.gz`) is **not** signed: Homebrew removes the quarantine attribute on its own downloads. +> **Validated (Developer ID `D77MCV9NZ7`):** signing every Mach-O in the bundled +> JRE with hardened runtime + `amy.entitlements` lets `amy init` derive a key via +> secp256k1 with no library-validation crash. Dropping `disable-library-validation` +> reproduces `UnsatisfiedLinkError: … different Team IDs` on the runtime-extracted +> `libsecp256k1-jni.dylib` — so that entitlement is load-bearing, not decorative. +> +> **Open risk — embedded jar natives.** The notary service unpacks `lib/*.jar` +> recursively and checks every Mach-O for a signature + hardened runtime. Our +> sign loop only touches loose files, so 9 unsigned natives ride along inside +> jars on a macOS build: `secp256k1` (1, required at runtime), `jna` (2), +> `sqlite` (2), and `skiko` (4, dead weight — Compose UI the CLI never renders). +> Whether `notarytool` returns `Accepted` or `Invalid` on these is **unverified** +> (the local validation had no notary creds). **Decide it with one run:** set the +> six `MAC_*` secrets and trigger `create-release.yml` via `workflow_dispatch` +> with `dry_run=true` — the sign+notarize step runs regardless of `dry_run` and +> now prints the per-file notary log on a non-`Accepted` verdict. If it comes +> back `Invalid`, the fix is to codesign the dylibs *inside* those jars before +> zipping (and/or strip the unused `skiko`/Compose jars from the CLI image — the +> `:commons` core/ui split the size budget already flags). The **desktop** app +> bundles the same jars through Compose/jpackage notarization, so run a desktop +> dry-run too; its in-jar handling differs and is likewise unverified. + Generating the values: ```bash