Skip to content

fix(ci): sign the macOS .app ad-hoc when no certificate is available - #257

Merged
EtienneLescot merged 1 commit into
release/v1.9.0from
fix/macos-adhoc-signing
Aug 4, 2026
Merged

fix(ci): sign the macOS .app ad-hoc when no certificate is available#257
EtienneLescot merged 1 commit into
release/v1.9.0from
fix/macos-adhoc-signing

Conversation

@EtienneLescot

@EtienneLescot EtienneLescot commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator

Summary

v1.9.0-rc.1 cannot record on macOS. The app asks for Accessibility, the user grants it, and it asks again — forever. The permission code is not at fault: the .app in the published DMG is not signed at all, and macOS keys TCC grants to an app's code signature, so the grant has nothing to attach to.

Compare the DMGs:

Release Identifier Sealed Resources
v1.7.0 com.etiennelescot.openscreen v2, 796 files
v1.8.0-rc.7 / rc.8 / rc.9 com.etiennelescot.openscreen v2, 128 files
v1.9.0-rc.1 Electron none

Identifier=Electron with no sealed resources is the raw linker signature on the Electron binary. electron-builder never re-signed the bundle.

Root cause

The electron-builder ^26.8.1 → ^26.15.3 bump in package.json. CI sets CSC_IDENTITY_AUTO_DISCOVERY=false when the signing secrets are absent, which is every RC. In 26.8.1, macPackager.sign() ended at:

else if (noIdentity && fallBackToAdhoc) {
  log.warn(null, "falling back to ad-hoc signature for macOS application code signing")
  identity = new Identity("-", undefined)
}

26.15.3 replaced that whole block with findSigningIdentity, which returns null, and sign() leaves on return false. There is no ad-hoc fallback anywhere in 26.15.3's out/. No config changed between the two tags — the version bump is the only variable.

Why the symptom is total

requestMacCursorAccessibilityAccess reads accessibilityTrusted from the cursor helper, which is AXIsProcessTrusted(). That stays false however many times the box is ticked. The preflight in useScreenRecorder.ts returns before the countdown, and handlers.ts re-opens the deep-link dialog. With the HUD in its default editable-overlay cursor mode there is no way through.

Why nothing caught it

Verify .app code signature is gated on steps.signing.outputs.enabled == 'true' — so it never ran on the only builds that could be unsigned. Every macOS check in the job was green.

Changes

  • Ad-hoc sign the .app when no certificate is available, with the same --options runtime and macos.entitlements electron-builder would have applied. Both arches: 26.8.1 only fell back on arm64, so Intel DMGs were never signed at all.
  • Make signature verification unconditional, and assert the signing identifier against CFBundleIdentifier. codesign --verify --deep --strict passes on the bare linker signature too, so structure alone would not have caught this; the identifier is what separates a bundle macOS can attach permissions to from one it cannot.

Related issue

Refs #

Type of change

  • Bug fix

Release impact

  • Patch

Desktop impact

  • macOS
  • Installer / packaging

Testing

Verified against the three real bundles on an M-series Mac, since the failure is only observable in a packaged .app:

$ codesign --force --deep --sign - --options runtime \
    --entitlements macos.entitlements Openscreen.app     # copy of the rc.1 DMG
$ codesign --verify --deep --strict Openscreen.app       # passes
$ codesign -dv --verbose=2 Openscreen.app
Identifier=com.etiennelescot.openscreen
Sealed Resources version=2 rules=13 files=128

which is byte-for-byte the shape of the working v1.8.0-rc.9 bundle.

The new guard, run against all three:

v1.9.0-rc.1 (as published)   expected=com.etiennelescot.openscreen  actual=Electron                       -> rejected
v1.9.0-rc.1 (ad-hoc signed)  expected=com.etiennelescot.openscreen  actual=com.etiennelescot.openscreen   -> passes
v1.8.0-rc.9 (known good)     expected=com.etiennelescot.openscreen  actual=com.etiennelescot.openscreen   -> passes

Workflow YAML parses (js-yaml); npm run docs:check is OK.

Users already on rc.1 can unstick themselves without waiting for rc.2 — quit the app, re-sign it with the codesign line above, then tccutil reset All com.etiennelescot.openscreen to drop the orphaned entries.

Note

npm run build:mac has the same hole for anyone building locally without a certificate. Left out of this PR to keep it to the published-artifact path.

electron-builder signed the bundle itself until 26.15.3. Its macPackager
carried a `noIdentity && fallBackToAdhoc` branch handing back
`Identity("-")` when no certificate was found — mandatory on arm64, where
an unsigned binary will not launch. 26.15.3 replaced that path with
`findSigningIdentity`, which returns null instead, so `sign()` leaves on
`return false` and nothing signs the bundle. What ships is the bare
linker signature on the Electron binary: `Identifier=Electron`,
`Sealed Resources=none`.

macOS keys TCC grants to an app's code signature, so such a bundle can
never hold one. v1.9.0-rc.1 asked for Accessibility, the user granted it,
`AXIsProcessTrusted()` still returned false, and the editable-cursor
preflight re-opened the same dialog on every press of record. Recording
was impossible on macOS.

Sign ad-hoc ourselves with the runtime and entitlements electron-builder
would have applied, on both arches — 26.8.1 only fell back on arm64, so
Intel DMGs were never signed at all.

The verification step that should have caught this was gated on signing
being enabled, i.e. it never ran for the only builds that could be
unsigned. Make it unconditional, and assert the signing identifier
against the bundle id: `codesign --verify` passes on the bare linker
signature too, so the identifier is the only thing that separates a
bundle macOS can attach permissions to from one it cannot.
@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 0e25a4f6-1dd6-4758-a45d-91e6bcccee64

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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.

@EtienneLescot
EtienneLescot merged commit a61d7a2 into release/v1.9.0 Aug 4, 2026
12 checks passed
@EtienneLescot
EtienneLescot deleted the fix/macos-adhoc-signing branch August 4, 2026 20:58
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