Skip to content

Sign nested plugins and frameworks explicitly (post-PR #2905 fix-forward) - #2906

Closed
lawrencecchen wants to merge 1 commit into
mainfrom
fix-signing-nested-code
Closed

lawrencecchen wants to merge 1 commit into
mainfrom
fix-signing-nested-code

Conversation

@lawrencecchen

@lawrencecchen lawrencecchen commented Apr 15, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Nightly run after #2905 failed at the Codesign step with:

build-universal/.../cmux NIGHTLY.app: code object is not signed at all
In subcomponent: .../Contents/PlugIns/CmuxDockTilePlugin.plugin

PR 2905 dropped --deep from the top-level sign so the main app's entitlements wouldn't overwrite the CLI helpers. But --deep was also the only thing signing the nested plugin and the Sparkle / Sentry frameworks, so they're left unsigned and the top-level sign refuses.

Proper inside-out order per Apple's docs: sign every nested code item first (deepest out), outer containers last.

Changes (to both nightly.yml and release.yml):

  1. CLI helpers signed with cmux-helper.entitlements (unchanged).
  2. New: loop Contents/PlugIns/* and sign each with --deep (no custom entitlements).
  3. New: loop Contents/Frameworks/* and sign each with --deep (no custom entitlements). Handles Sparkle's nested XPCServices/*.xpc and Updater.app too.
  4. Main app bundle signed last with full entitlements, no --deep (unchanged).

Test plan

  • Nightly workflow succeeds through Codesign + Notarize.
  • Published cmux NIGHTLY.app launches on macOS 26 Tahoe.
  • codesign -d --entitlements on the published artifact still shows main app with application-identifier + web-browser.public-key-credential, helpers without application-identifier.
  • Passkey registration + authentication on webauthn.io works in the new nightly's browser panel.

Summary by CodeRabbit

  • Chores
    • Updated macOS code signing process in CI/CD workflows to implement a structured signing sequence for nested bundle contents, ensuring proper security practices during the build and release pipeline.

Summary by cubic

Fix macOS codesigning by explicitly signing nested PlugIns and Frameworks before the app bundle, following Apple’s inside-out order. Nightly and release workflows now sign subcomponents so notarization and launch succeed after removing --deep from the top-level sign.

  • Bug Fixes
    • Sign each Contents/PlugIns/* and Contents/Frameworks/* with --deep and no entitlements; includes Sparkle’s XPCServices and Updater.app.
    • Keep CLI helpers signed with cmux-helper.entitlements.
    • Sign the main app last with full entitlements, without --deep.

Written for commit 1b3b2a1. Summary will update on new commits.

PR #2905 removed --deep from the top-level sign to avoid clobbering
the per-helper entitlements, but --deep was also what ensured nested
plugins (PlugIns/CmuxDockTilePlugin.plugin) and the Sparkle /
Sentry frameworks got signed. Without --deep the plugin is left
unsigned and 'codesign --entitlements ... <app>' fails with
'code object is not signed at all' in subcomponent.

Add explicit steps to sign each plugin and each framework with
--deep (no custom entitlements) before signing the main app bundle.
This matches Apple's documented inside-out signing flow: every
nested code item is signed first, outer containers last.
@vercel

vercel Bot commented Apr 15, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
cmux Ready Ready Preview, Comment Apr 15, 2026 4:21am

@coderabbitai

coderabbitai Bot commented Apr 15, 2026 •

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

GitHub Actions workflows for nightly and release builds updated to implement "inside-out" macOS bundle codesigning. Nested components—CLI helpers, PlugIns, and Frameworks—are signed before the main app, with varying entitlements policies applied at each stage.

Changes

Cohort / File(s) Summary
macOS Codesigning Workflow Updates
.github/workflows/nightly.yml, .github/workflows/release.yml
Reordered codesigning passes to sign nested bundles (PlugIns, Frameworks) and helper binaries before the main app bundle. Nested items signed with --deep and no entitlements; main app signed last with full entitlements and explicitly without --deep.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

🐰 Inside-out we hop and bound,
Sign the helpers first, safe and sound,
PlugIns deep, then Frameworks too,
The app comes last—that's how we do! 📦✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and accurately describes the main change: explicitly signing nested plugins and frameworks in the macOS codesigning workflow as a follow-up fix to PR #2905.
Description check ✅ Passed The description covers all critical sections: problem summary (the codesign failure), technical explanation of the root cause, detailed change list, and a test plan with specific verification steps.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix-signing-nested-code

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
.github/workflows/release.yml (1)

310-330: Keep the helper-entitlement invariant checked in release CI.

This step now signs the helpers correctly, but release still doesn't assert afterward that cmux and ghostty did not pick up application-identifier. That invariant is part of this PR's test plan, and nightly already guards it. Adding the same check here would fail fast before publishing a tagged build.

Suggested guard
           /usr/bin/codesign --force --options runtime --timestamp --sign "$APPLE_SIGNING_IDENTITY" --entitlements "$RELEASE_APP_ENT" "$APP_PATH"
           /usr/bin/codesign --verify --deep --strict --verbose=2 "$APP_PATH"
           /usr/bin/codesign -d --entitlements :- "$APP_PATH" 2>&1 | grep -q "com.apple.developer.web-browser.public-key-credential"
           /usr/bin/codesign -d --entitlements :- "$APP_PATH" 2>&1 | grep -q "7WLXT3NR37.com.cmuxterm.app"
+          for helper in "$CLI_PATH" "$HELPER_PATH"; do
+            if [ -f "$helper" ]; then
+              /usr/bin/codesign -d --entitlements :- "$helper" 2>&1 | grep -q "application-identifier" && {
+                echo "error: helper unexpectedly carries application-identifier: $helper" >&2
+                exit 1
+              } || true
+            fi
+          done
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/release.yml around lines 310 - 330, Add a post-signing
guard that asserts the helper binaries (the ones signed via CLI_PATH and
HELPER_PATH, e.g., cmux and ghostty) do NOT contain the application-identifier
entitlement: after the helper signing block (the section that signs CLI_PATH and
HELPER_PATH) run codesign --display --entitlements - on each helper (reference
CLI_PATH and HELPER_PATH or the actual helper filenames cmux and ghostty) and
fail the script (exit 1) if the output contains "application-identifier"; this
ensures the helper-entitlement invariant is enforced in release CI before
packaging/publishing.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In @.github/workflows/release.yml:
- Around line 310-330: Add a post-signing guard that asserts the helper binaries
(the ones signed via CLI_PATH and HELPER_PATH, e.g., cmux and ghostty) do NOT
contain the application-identifier entitlement: after the helper signing block
(the section that signs CLI_PATH and HELPER_PATH) run codesign --display
--entitlements - on each helper (reference CLI_PATH and HELPER_PATH or the
actual helper filenames cmux and ghostty) and fail the script (exit 1) if the
output contains "application-identifier"; this ensures the helper-entitlement
invariant is enforced in release CI before packaging/publishing.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 73a809cf-f4ad-4d95-ab32-70f482523da5

📥 Commits

Reviewing files that changed from the base of the PR and between 02f741c and 1b3b2a1.

📒 Files selected for processing (2)
  • .github/workflows/nightly.yml
  • .github/workflows/release.yml

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 2 files

@greptile-apps

greptile-apps Bot commented Apr 15, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a codesign failure introduced by PR #2905: removing --deep from the top-level app bundle sign left Contents/PlugIns/ and Contents/Frameworks/ unsigned, causing codesign to reject the bundle. The fix adds two inside-out loops — one for PlugIns/* and one for Frameworks/* — each signing with --deep (to handle Sparkle's nested XPC services and Updater.app) before the main bundle is signed last without --deep. The approach and signing order are correct for both nightly.yml and release.yml.

Confidence Score: 5/5

Safe to merge — the signing order and logic are correct; remaining findings are minor parity/style suggestions.

The fix correctly restores signing for nested PlugIns and Frameworks using proper inside-out order. The find -mindepth 1 -maxdepth 1 -print0 + null-byte read loop handles spaces in paths correctly. Using --deep per-bundle is the right targeted approach to cover Sparkle's nested XPC services without reintroducing the helper entitlement overwrite. All remaining comments are P2 style/parity suggestions that do not block merge.

No files require special attention beyond the P2 suggestions already noted.

Important Files Changed

Filename Overview
.github/workflows/nightly.yml Adds PlugIns and Frameworks signing loops before the main bundle sign; order and null-safe path handling are correct. Post-signing assertion covers cmux CLI but not the ghostty helper.
.github/workflows/release.yml Mirrors the nightly PlugIns/Frameworks signing loops correctly; missing the helper entitlement assertion that nightly.yml added in PR #2905.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Build app bundle\nCODE_SIGNING_ALLOWED=NO] --> B

    subgraph "Inside-out signing order"
        B["1. Sign CLI helpers\n(cmux + ghostty)\n--entitlements cmux-helper.entitlements"]
        B --> C["2. Sign each PlugIns/* bundle\n--deep, no entitlements\n(e.g. CmuxDockTilePlugin.plugin)"]
        C --> D["3. Sign each Frameworks/* bundle\n--deep, no entitlements\n(handles Sparkle XPC services,\nUpdater.app recursively)"]
        D --> E["4. Sign main app bundle LAST\n--entitlements app.entitlements\nNO --deep"]
    end

    E --> F[codesign --verify --deep --strict]
    F --> G{Assertions pass?}
    G -->|application-identifier present in main app\nWebAuthn entitlement present\nhelpers do NOT carry app-identifier| H[Notarize]
    G -->|Fail| X[❌ Step fails]
Loading

Comments Outside Diff (2)

  1. .github/workflows/nightly.yml, line 490-495 (link)

    P2 Ghostty helper not covered by the assertion

    The post-signing assertion only checks CLI_PATH (cmux) for an unexpected application-identifier. HELPER_PATH (ghostty) is signed with the same cmux-helper.entitlements and has the same constraint, but a signing regression there would go undetected.

  2. .github/workflows/release.yml, line 329-333 (link)

    P2 Missing helper entitlement assertion (parity with nightly.yml)

    nightly.yml asserts after signing that neither CLI helper carries application-identifier. release.yml has no equivalent guard, so a future signing regression in the release workflow would not be caught before publishing. Consider adding the same two assertions here for parity.

Reviews (1): Last reviewed commit: "Sign nested plugins and frameworks befor..." | Re-trigger Greptile

lawrencecchen added a commit that referenced this pull request Apr 15, 2026
…itlements

Two changes consolidate the inside-out signing work introduced by
PRs #2902, #2905, and #2906 into something a future reader can
understand without reading two 40-line YAML blocks:

- Check in cmux.release.entitlements and cmux.nightly.entitlements,
  each with the right application-identifier and team-identifier
  baked in. Replaces the PlistBuddy-at-sign-time injection that
  copies cmux.entitlements and mutates it per workflow run.
- Extract the five-step inside-out signing logic (helpers, plugins,
  frameworks, main bundle, verification) into
  scripts/sign-cmux-bundle.sh. Both nightly.yml and release.yml
  shrink to one line that calls the script with the right
  entitlements file.

No behavior change versus PR #2906 at steady state: same order, same
--deep boundaries, same grep-based post-sign asserts. The script
also refuses to sign if a helper ends up with the main app's
application-identifier, so future regressions surface at build time
rather than on launch under amfi.
@lawrencecchen

Copy link
Copy Markdown
Contributor Author

Superseded by #2908 which took the shared-script + checked-in-entitlements approach. Both PRs produced identical signed artifacts in CI; 2908 is significantly cleaner (scripts/sign-cmux-bundle.sh + cmux.{release,nightly}.entitlements).

lawrencecchen added a commit that referenced this pull request Apr 15, 2026
…itlements (#2908)

Two changes consolidate the inside-out signing work introduced by
PRs #2902, #2905, and #2906 into something a future reader can
understand without reading two 40-line YAML blocks:

- Check in cmux.release.entitlements and cmux.nightly.entitlements,
  each with the right application-identifier and team-identifier
  baked in. Replaces the PlistBuddy-at-sign-time injection that
  copies cmux.entitlements and mutates it per workflow run.
- Extract the five-step inside-out signing logic (helpers, plugins,
  frameworks, main bundle, verification) into
  scripts/sign-cmux-bundle.sh. Both nightly.yml and release.yml
  shrink to one line that calls the script with the right
  entitlements file.

No behavior change versus PR #2906 at steady state: same order, same
--deep boundaries, same grep-based post-sign asserts. The script
also refuses to sign if a helper ends up with the main app's
application-identifier, so future regressions surface at build time
rather than on launch under amfi.

Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>
@lawrencecchen
lawrencecchen deleted the fix-signing-nested-code branch April 15, 2026 05:56
rodchristiansen pushed a commit to rodchristiansen/cmux that referenced this pull request Sep 2, 2026
…itlements (manaflow-ai#2908)

Two changes consolidate the inside-out signing work introduced by
PRs manaflow-ai#2902, manaflow-ai#2905, and manaflow-ai#2906 into something a future reader can
understand without reading two 40-line YAML blocks:

- Check in cmux.release.entitlements and cmux.nightly.entitlements,
  each with the right application-identifier and team-identifier
  baked in. Replaces the PlistBuddy-at-sign-time injection that
  copies cmux.entitlements and mutates it per workflow run.
- Extract the five-step inside-out signing logic (helpers, plugins,
  frameworks, main bundle, verification) into
  scripts/sign-cmux-bundle.sh. Both nightly.yml and release.yml
  shrink to one line that calls the script with the right
  entitlements file.

No behavior change versus PR manaflow-ai#2906 at steady state: same order, same
--deep boundaries, same grep-based post-sign asserts. The script
also refuses to sign if a helper ends up with the main app's
application-identifier, so future regressions surface at build time
rather than on launch under amfi.

Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>

This branch was successfully deployed

1 active deployment
Preview — 1b3b2a1a Deployed Apr 15, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant