Skip to content

Ship serve-sim camera dylib as data and materialize it at runtime#7168

Merged
brennanb2025 merged 2 commits into
mainfrom
brennanb2025/fix-6877-simcam-dylib-gatekeeper
Jul 3, 2026
Merged

Ship serve-sim camera dylib as data and materialize it at runtime#7168
brennanb2025 merged 2 commits into
mainfrom
brennanb2025/fix-6877-simcam-dylib-gatekeeper

Conversation

@brennanb2025

@brennanb2025 brennanb2025 commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes #6877.

libSimCameraInjector.dylib is built for the iOS-Simulator platform (LC_BUILD_VERSION platform 7) — it must be, since serve-sim injects it into simulator processes via SIMCTL_CHILD_DYLD_INSERT_LIBRARIES. Apple's notary service never issues Gatekeeper tickets for iOS-simulator arm64 slices (verified against the shipped v1.4.120-rc.2 artifact 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 pass spctl inside 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 afterSign hook 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 existing afterPack hook before signing, darwin-only): gzip libSimCameraInjector.dylib in 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-build path.

Runtime (src/main/emulator/serve-sim-runtime-materializer.ts): on macOS packaged builds, resolveServeSimExecutable materializes the bundled serve-sim package into userData/serve-sim-runtime/<version>/ once per app version — copy, restore the dylib from the gzip payload, chmod the helpers, strip com.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 standalone npm install serve-sim user 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

  • The issue's concrete, reproducible complaint — spctl -a -t execute rejecting the packaged dylib as Unnotarized Developer ID — is eliminated categorically: there is no longer any unnotarizable Mach-O in the bundle to assess.
  • The reporter's syspolicyd "Malware rejection" events: I attempted a negative-control reproduction (quarantined, Developer-ID-unnotarized dylib from the real v1.4.120-rc.2 artifact, 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 passed
  • pnpm run typecheck, oxlint, oxfmt --check, git diff --check — clean

End-to-end against a real local pnpm build:unpack (signed with a dev identity, afterPack hook live):

  • Packed layout: 0 raw libSimCameraInjector.dylib under Resources; .gz payload present in both copies; Sources/ absent; camera helper intact and executable.
  • Simulated a DMG install by stamping com.apple.quarantine on 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.
  • Live camera injection on a booted iPhone 17 Pro simulator (iOS 26.5) using the materialized runtime driven by the packed app's own Electron binary: serve-sim reported Injected camera into com.apple.Preferences, lsof confirmed the materialized dylib mapped into the simulator process, the camera helper ran from the materialized copy, and log show showed 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 🐋

brennanb2025 and others added 2 commits July 2, 2026 17:22
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>
@brennanb2025 brennanb2025 changed the base branch from brennanb2025/revert-7077-simcam-dylib-notarization to main July 3, 2026 00:57
@brennanb2025 brennanb2025 merged commit f44cb30 into main Jul 3, 2026
7 checks passed
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.

[Bug]: macOS 1.4.106: nested simcam dylib is unnotarized and syspolicyd reports Malware rejection

1 participant