A relocatable bundle of non-SIP macOS command-line tools, shared by every implementation of the stackable-hooks framework.
macOS System Integrity Protection strips DYLD_INSERT_LIBRARIES when a binary
under /bin, /sbin, /usr/bin or /usr/sbin is exec'd. An interpose-based
monitor therefore goes blind the moment a monitored process shells out to
/bin/sh, /usr/bin/grep or /usr/bin/sed — and it goes blind silently,
which for a dependency-capturing monitor is the cardinal sin: an incomplete
input set reported as complete is a false cache hit.
The fix is to exec a different build of the same tool. It cannot be a copy
of the Apple binary: AMFI SIGKILLs a relocated copy of a platform binary on
launch, even ad-hoc re-signed. So the drop-in has to be a genuinely non-Apple
build — GNU coreutils, GNU grep, bash — laid out in the shape of the SIP
filesystem so a hook can rewrite /usr/bin/grep to <bundle>/usr/bin/grep.
Every stackable-hooks implementation needs the same tools:
| Consumer | Uses them for |
|---|---|
nim-stackable-hooks |
the Nim framework's SIP path rewriting (rewriteSipPath) |
io-mon |
dependency capture across a SIP boundary |
reprobuild |
monitored build actions (and, through them, the action cache) |
codetracer |
recording across SIP-protected children |
| Agent Harbor | the Rust implementation's sandbox-tools component |
The tools belong to none of them, and until this repository existed each
consumer either built its own bundle or scanned $PATH and hoped. So it is
one package, built once, depended on by name — see
metacraft-specs/infrastructure/package-distribution.md §8.
<bundle>/bin/<tool> # mirrors /bin (bin/sh is bash)
<bundle>/usr/bin/<tool> # mirrors /usr/bin
<bundle>/lib/*.dylib # bundled non-system dylibs
56 portable tools: the shells (bash, dash, zsh), the coreutils set, findutils,
GNU grep/sed/awk, tar/gzip/xz, and which/diff/cmp. Every Mach-O is
rewritten with install_name_tool so it carries no /nix/store
references and runs on a machine without Nix — the bundle is a package
payload, not a symlink farm.
build-bundle.sh is lifted from io-mon's scripts/build-sandbox-tools.sh,
which resolves each tool against the ambient $PATH and skips what it cannot
find. That is the right fallback for a repository with no pinned tool set, and
it is exactly what this package removes. Measured on a stock macOS shell:
| Source | Tools | Result for bash -c '/usr/bin/sed …; /usr/bin/grep …' |
|---|---|---|
$PATH scan |
41 of 56 (no grep, sed, awk, find, tar, xz, diff, …) |
2 event-losses — both SIP execs un-injectable |
| this package | 56 of 56 | 0 event-losses — subtree fully captured |
Two losses is not two missing records: an unknown-scope loss disables the
action-cache publish for the whole session. A bundle whose contents depend on
the host is therefore not a package, and build-bundle.sh here fails when
a tool cannot be resolved (SANDBOX_TOOLS_STRICT=0 to override).
nix build .#stackable-hooks-macos-tools # -> ./resultThe tool set comes from this flake's pinned nixpkgs; the bundle is a function
of flake.lock and nothing else.
Point the discovery variable at the bundle:
export CT_SANDBOX_TOOLS_DIR=$(nix build --no-link --print-out-paths .#default)Discovery order for a consumer is: the explicit variable, then the packaged
prefix installed by the package, then — as a last resort — a $PATH scan.
The variable name is being unified across implementations
(CT_SANDBOX_TOOLS_DIR in io-mon, AH_SANDBOX_TOOLS_DIR in Agent Harbor);
see the open question in the packaging spec.
- A C compiler. Not because the compiler does not need a drop-in — it very
much does,
/usr/bin/ccbeing a SIP-protectedxcode_selectshim — but because the right drop-in is not a shipped binary.xcrun -f clangresolves Apple's own compiler at a path outside every SIP prefix, signedflags=0x0(none), which takes the injection normally. So the toolchain drop-ins (cc,clang,c++,clang++,cpp,ld,as,ar,nm,strip,lipo,libtool) are host-resolved symlinks created at populate time, not payload: a redirect to the same binary the shim would have exec'd, so nothing about the build changes. See metacraft-labs/io-mon#18. Redistributing Apple's 290 MB toolchain would be neither legal nor useful. arch(1). There is no non-Apple build with Apple's semantics (arch -x86_64 …). It stays un-injectable until the EndpointSecurity backend lands. An explicit gap beats a drop-in that silently misbehaves.