Skip to content

fix(release): grant V8 the JIT entitlements the Hardened Runtime requires - #20

Merged
amondnet merged 2 commits into
mainfrom
fix/hardened-runtime-entitlements
Jul 28, 2026
Merged

fix(release): grant V8 the JIT entitlements the Hardened Runtime requires#20
amondnet merged 2 commits into
mainfrom
fix/hardened-runtime-entitlements

Conversation

@amondnet

@amondnet amondnet commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

v0.2.0 is published and crashes on launch

Reported from a real install on macOS 15.6.1 / Intel: EXC_BREAKPOINT (SIGTRAP) on the Deno runtime thread (RuntimeLoader::RuntimeThreadlaufey_runtime_start), with V8 worker threads alive alongside it.

I caused this in #18. To satisfy the notary service I signed everything with --options runtime, but passed no entitlements. The Hardened Runtime denies a JIT its executable memory, so V8 aborts before the window opens. deno desktop had never enabled the Hardened Runtime — a plain build is flags=0x2(adhoc) — so the problem only appeared once signing was fixed.

Proof

Same binaries, only the signature differs:

Variant Result
Shipped build (runtime, no entitlements) exits 133 (SIGTRAP)
Re-signed with JIT entitlements stays up, Runtime started, Listening on http://127.0.0.1:56287/

Fix

packaging/macos/entitlements.plist grants the standard V8/CEF Hardened Runtime exceptions — allow-jit, allow-unsigned-executable-memory, disable-library-validation, allow-dyld-environment-variables — applied to every bundle that hosts V8: the app and the five CEF helper apps. Libraries are signed without entitlements; they inherit the host process's.

Why my verification missed it

I verified checksums, spctl, stapling and the cask, and called the release "genuinely installable". spctl --assess only says Gatekeeper will permit execution — it says nothing about whether the app runs. I never launched it.

Two checks close that gap:

  • The signing step asserts allow-jit on every V8 host and names the offending bundle.
  • A new smoke test launches the signed app, requires it to stay up for 15s and to log Listening on, and calls out exit 133 as a likely Hardened Runtime JIT denial. Cross-arch runs emit a ::warning:: saying the build is not launch-verified rather than passing silently.

The workflow header comment is corrected too — it asserted that deno desktop applies the Hardened Runtime, which is the belief that produced this bug.


Summary by cubic

Fixes the macOS launch crash by granting V8 the JIT entitlements required by the Hardened Runtime and verifying app startup in the release workflow. This ensures v0.2.0+ builds run instead of aborting with SIGTRAP.

  • Bug Fixes
    • Added packaging/macos/entitlements.plist with allow-jit, allow-unsigned-executable-memory, disable-library-validation, and allow-dyld-environment-variables; applied to the app and CEF helper apps (libraries inherit).
    • Re-signed all nested Mach-O binaries with the Hardened Runtime; assert allow-jit on all V8 hosts during signing.
    • Introduced a smoke test that launches the signed app, waits up to 15s for “Listening on”, flags exit 133 as a likely JIT denial, and runs wait outside errexit so failures report the exit code and logs (cross-arch runs warn and skip).
    • Moved macOS signing to the workflow, then rebuild/sign the DMG before notarization; updated comments to reflect the new flow.

Written for commit fe76ba0. Summary will update on new commits.

…ires

v0.2.0 notarized cleanly and then crashed on launch. Enabling the Hardened
Runtime to satisfy the notary service also denies V8 the executable memory it
needs, so the Deno runtime thread aborted with SIGTRAP before the window opened.
`deno desktop` had never enabled the Hardened Runtime, which is why this only
appeared once signing was fixed.

Sign every bundle that hosts V8 — the app and the CEF helper apps — with
allow-jit, allow-unsigned-executable-memory, disable-library-validation and
allow-dyld-environment-variables. Verified by re-signing the shipped build with
these entitlements and nothing else: it went from exiting 133 to serving the
studio bundle.

Add the two checks that would have caught it. The signing step now asserts
allow-jit on every V8 host, and a new step launches the signed app and requires
it to stay up and serve the bundle. Notarization only proves Gatekeeper permits
the app to run, which is what was verified for v0.2.0 and is not the same as it
working.

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request adds a new entitlements.plist file for macOS packaging to define Hardened Runtime exceptions required by the embedded V8 and CEF runtimes, including permissions for JIT compilation, unsigned executable memory, disabled library validation, and DYLD environment variables. I have no feedback to provide on these changes.

Comment thread .github/workflows/release.yml Outdated
@greptile-apps

greptile-apps Bot commented Jul 28, 2026

Copy link
Copy Markdown

Greptile Summary

This PR repairs the signed macOS release path and adds launch verification.

  • Adds Hardened Runtime JIT entitlements for the main application and CEF helper applications.
  • Re-signs nested code and validates that every V8-hosting bundle has allow-jit.
  • Smoke-tests native-architecture builds for startup and the expected listening log.
  • Rebuilds and signs the disk image from the newly signed application bundle.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
.github/workflows/release.yml Reworks macOS signing to apply JIT entitlements, validates V8 hosts, adds native-architecture launch testing, and rebuilds the disk image from the signed bundle; the previously reported wait diagnostic issue is fixed by temporarily disabling errexit.
packaging/macos/entitlements.plist Defines the Hardened Runtime exceptions required by the embedded Deno and CEF V8 processes.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Build macOS app bundle] --> B[Sign nested libraries]
    B --> C[Sign V8 host bundles with entitlements]
    C --> D[Verify signatures and allow-jit]
    D --> E{Runner matches target architecture?}
    E -->|Yes| F[Launch smoke test]
    E -->|No| G[Warn that launch is unverified]
    F --> H[Require process survival and Listening on log]
    G --> I[Rebuild disk image]
    H --> I
    I --> J[Sign disk image]
    J --> K[Notarize and staple]
Loading

Reviews (2): Last reviewed commit: "chore: apply AI code review suggestions" | Re-trigger Greptile

Run `wait` outside errexit in the smoke test. The app's exit status is
non-zero by definition on that branch, so `set -e` killed the step before it
could print the status, flag SIGTRAP, or dump the runtime log — the diagnostic
path in the diagnostic step never ran.
@sonarqubecloud

Copy link
Copy Markdown

@amondnet
amondnet merged commit aeec07c into main Jul 28, 2026
12 checks passed
@amondnet
amondnet deleted the fix/hardened-runtime-entitlements branch July 28, 2026 15:03
amondnet added a commit that referenced this pull request Jul 28, 2026
v0.2.1 notarized as Invalid on both arches with a single issue:

  path:    OpenHWP.app/Contents/MacOS/laufey
  message: The signature of the binary is invalid.

The signature was fine. On a successful start the app writes a 2-byte
marker, Contents/MacOS/laufey.dylib.update-ok, into its own bundle. That
adds a file the signature does not seal:

  codesign --verify --deep --strict
    a sealed resource is missing or invalid
    file added: .../Contents/MacOS/laufey.dylib.update-ok

so the smoke test added in #20 broke the very artifact it was verifying,
between signing and packaging. v0.2.0 notarized only because it never
launched the app.

Launch a byte-identical copy in $RUNNER_TEMP instead. The copy absorbs
the marker and the shipped bundle is never executed. Reproduced and
verified locally: the copy gets the marker, the shipped bundle does not
and still reports "valid on disk / satisfies its Designated Requirement",
and the smoke test still logs "Listening on".

Also re-verify the bundle immediately before hdiutil. Any future mutation
under Contents/ now fails in seconds with the offending path named,
instead of coming back as an opaque notarization rejection ten minutes
later. Confirmed to exit 1 with the marker present and 0 without it.
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