Ship serve-sim camera dylib as data and materialize it at runtime#7168
Merged
Conversation
This reverts commit ee8bdf1.
libSimCameraInjector.dylib targets the iOS-simulator platform, which Apple's notary service never tickets for arm64, so a raw copy inside the macOS bundle is permanently rejected by Gatekeeper assessment (#6877). Packaging now gzips the dylib in both bundled serve-sim copies before signing and drops Sources/ so serve-sim's build-from-source fallback cannot compile into the sealed bundle. At runtime the main process materializes the serve-sim package into userData once per version, restores the dylib from the gzip payload, strips quarantine, and runs serve-sim from that copy — the same ad-hoc, unquarantined shape an npm install of serve-sim uses. Fixes #6877 Co-authored-by: Orca <help@stably.ai>
This was referenced Jul 3, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes #6877.
libSimCameraInjector.dylibis built for the iOS-Simulator platform (LC_BUILD_VERSION platform 7) — it must be, since serve-sim injects it into simulator processes viaSIMCTL_CHILD_DYLD_INSERT_LIBRARIES. Apple's notary service never issues Gatekeeper tickets for iOS-simulator arm64 slices (verified against the shippedv1.4.120-rc.2artifact via Apple's ticket-delivery API — see #7165 for the full evidence chain), so no notarization approach can ever make a raw copy of this dylib passspctlinside the bundle. This PR removes the assessable artifact instead of trying to notarize it.This branch includes the #7165 revert commit ahead of the fix, because the two cannot land in the opposite order: the old
afterSignhook hard-requires the raw dylibs that this PR removes, so a mac release build with both the hook and this fix would fail. Merge #7165 first and this PR's diff shrinks to just the fix — or merge this PR alone and it lands the revert and the fix together (then close #7165).Design Approach
Two halves, both fully verifiable locally (no Apple service in the loop):
Packaging (
config/serve-sim-camera-packaging.cjs, called from the existingafterPackhook before signing, darwin-only): gziplibSimCameraInjector.dylibin place in both bundled serve-sim copies (Resources/serve-sim,Resources/node_modules/serve-sim) and delete the raw Mach-O. The bundle then ships the dylib as plain data — unsigned, unassessed, invisible to Gatekeeper sweeps and notary scanning.Sources/is removed at the same time because with the prebuilt dylib gone, serve-sim's build-from-source fallback would compile a dylib into the sealed bundle and break its code signature — a latent bug that already existed for the--force-buildpath.Runtime (
src/main/emulator/serve-sim-runtime-materializer.ts): on macOS packaged builds,resolveServeSimExecutablematerializes the bundled serve-sim package intouserData/serve-sim-runtime/<version>/once per app version — copy, restore the dylib from the gzip payload, chmod the helpers, stripcom.apple.quarantine(xattr -cr), commit via atomic rename, prune older versions — and runs serve-sim from that copy. serve-sim resolves the dylib and camera helper relative to its own entry, so the materialized layout satisfies its lookup unchanged. The materialized dylib is byte-identical to the npm original (ad-hoc linker-signed, unquarantined) — exactly the shape every standalonenpm install serve-simuser runs today. On materialization failure it falls back to the bundled entry so emulator streaming keeps working, with a warning.Dev builds are untouched (they run from
node_modules, which is already unquarantined). Windows/Linux packaging is untouched.What this fixes vs. what was unprovable
spctl -a -t executerejecting the packaged dylib asUnnotarized Developer ID— is eliminated categorically: there is no longer any unnotarizable Mach-O in the bundle to assess.syspolicyd"Malware rejection" events: I attempted a negative-control reproduction (quarantined, Developer-ID-unnotarized dylib from the realv1.4.120-rc.2artifact, DYLD-inserted into a simulator process) and it did not reproduce on my dev machine — quarantine enforcement for simulator dylib loads evidently varies by machine/macOS state. What this PR guarantees is that every surface tied to this dylib (per-file assessment, first-launch bundle scans, quarantined-load checks) is gone, because the shipped artifact no longer contains it and the runtime copy is quarantine-free.Validation
Unit/static (all passing):
pnpm exec vitest run --config config/vitest.config.ts config/scripts/serve-sim-camera-packaging.test.mjs src/main/emulator/serve-sim-runtime-materializer.test.ts config/scripts/electron-builder-config.test.mjs— 30 passed (11 new)pnpm exec vitest run --config config/vitest.config.ts src/main/emulator/— 199 passedpnpm run typecheck,oxlint,oxfmt --check,git diff --check— cleanEnd-to-end against a real local
pnpm build:unpack(signed with a dev identity, afterPack hook live):libSimCameraInjector.dylibunderResources;.gzpayload present in both copies;Sources/absent; camera helper intact and executable.com.apple.quarantineon every packed serve-sim file, then ran the real materializer against it: runtime produced, dylib restored byte-identical to the npm original, zero quarantine xattrs anywhere in the materialized tree.Injected camera into com.apple.Preferences,lsofconfirmed the materialized dylib mapped into the simulator process, the camera helper ran from the materialized copy, andlog showshowed zero syspolicyd rejections for the window.Not applicable: UI screenshots (no UI), mobile, SSH (emulator backend is local-only; serve-sim spawn paths unchanged off-macOS).
Made with Orca 🐋