Proposal: standalone Apple Silicon scripting-addition loader with SIP enabled
Question
Would you consider replacing the current Apple Silicon scripting-addition injection path with a standalone arm64e payload mapped into Dock through debugserver/RSP?
I am opening an issue first because pull requests are disabled for this repository, and because this is a larger architectural change whose maintenance trade-offs should be discussed before asking for a merge.
Main benefit: SIP stays enabled
With this approach, all SIP protections can remain enabled except Debugging Restrictions. The required Recovery configuration is:
csrutil enable --without debugIn particular, it does not require:
- unrestricted filesystem access /
--without fs; - the
-arm64e_preview_abiboot argument.
The loader still requires root and CSR_ALLOW_TASK_FOR_PID, as expected for attaching to Dock.
Why change the current path?
The existing Apple Silicon loader injects shellcode that calls dlopen on the arm64e payload bundle inside Dock. On current macOS, that path depends on the arm64e preview ABI boot argument and broader SIP configuration.
The proposed implementation instead:
- builds the scripting-addition payload as a standalone arm64e Mach-O with zero unresolved symbols;
- embeds that image in the loader;
- maps its segments directly into Dock, preserving layout and reserving full
vmsizeincluding BSS; - uses debugserver's Remote Serial Protocol to save a Dock thread's state, start the payload, wait for initialization, and restore the exact thread state;
- resolves runtime APIs from inside Dock through
dlsym.
The debugserver/RSP transport and thread-hijack flow are adapted from my retX0/amfree project.
macOS 26 behavior restored
The standalone port also contains the macOS 26 fixes required for the complete scripting-addition command set, including:
- correct Dock private calling conventions (
x20for Swift/privateself); - space create/destroy/reorder and cross-display movement;
- window move, opacity/fade, layer, sticky, shadow, scale and ordering;
- window-to-space and window-list-to-space;
- proxy in/out transactions;
- 16 KiB alignment for the animation patch;
- window focus through
WindowManager.shared,synchronouslyRequestLayoutControl(), andsynchronouslyActivateWindow(UInt32)instead of the old ShowFront helper pattern.
Verification
Tested on Apple Silicon, macOS 26.6.2 (25G83):
- clean build: pass;
- handshake:
2.1.31, capabilities0x7f; - standalone payload: zero unresolved symbols;
- every scripting-addition opcode was behaviorally exercised;
- same-display and cross-display space operations passed;
- opacity fade basic/retarget/cancel/multi-window tests passed;
- focus activated the application, made the requested NSWindow key, and moved it to WindowServer rank 0;
- proxy ordering changed in/out and captured system alpha changed
1.00 -> 0.00 -> 1.00; - Dock PID remained unchanged throughout the final regression run.
Implementation branch:
https://github.com/retX0/yabai/tree/feat/dock-hijack-loader
The work is split into three commits for review:
- debugserver/RSP injection support;
- standalone arm64e payload and Mach-O mapper;
- removal of obsolete loader prerequisites.
Trade-offs / questions
This adds more loader code and depends on Xcode's debugserver. The Dock patterns/private Swift symbols also remain OS-version-sensitive, as with the existing scripting addition.
Before doing further integration work, I would like feedback on:
- Is keeping SIP enabled except for debugging a worthwhile reason to replace the current arm64e
dlopenpath? - Is the debugserver/Xcode dependency acceptable for scripting-addition users?
- Would you prefer this as one architecture change, smaller incremental changes, or maintained only as an external fork?
Source: asmvik/yabai