macOS sandbox: hw.optional.neon missing from sysctl allowlist — all Qt 6 apps abort at startup on Apple Silicon
Summary
The macOS Seatbelt profile generated for the Bash sandbox allows three hw.optional.* sysctl prefixes but never hw.optional.neon, which matches none of them. Because the profile is (deny default ...), that read is denied.
Qt 6 reads hw.optional.neon from a library initializer, before main(). When the read fails, Qt concludes the CPU has no NEON, prints Incompatible processor, and calls qFatal() → abort(). Every Qt 6 application aborts at startup when run inside the sandbox on Apple Silicon.
I hit this with OpenSCAD, but it is not OpenSCAD-specific — it affects any Qt 6 app invoked from a sandboxed Bash command (directly or via make, a test harness, a build script, etc.).
Root cause
From the profile builder in the CLI binary (v2.1.220):
; sysctl - specific sysctls only
(allow sysctl-read
...
(sysctl-name-prefix "hw.optional.arm")
(sysctl-name-prefix "hw.optional.arm.")
(sysctl-name-prefix "hw.optional.armv8_")
(sysctl-name-prefix "hw.perflevel")
...
)
Qt 6.8 reads exactly three CPU-feature sysctls. Two are covered, one is not:
| sysctl | matched by | result |
|---|---|---|
| hw.optional.armv8_crc32 | prefix hw.optional.armv8_ | allowed |
| hw.optional.arm.FEAT_AES | prefix hw.optional.arm. | allowed |
| hw.optional.neon | — (neon does not start with arm) | denied |
The three names are confirmed present in QtCore:
$ strings -a QtCore | grep -i 'hw\.optional'
hw.optional.arm.FEAT_AES
hw.optional.armv8_crc32
hw.optional.neon
Reproduction
Isolating the single variable — everything else allowed:
cat > /tmp/noneon.sb <<'EOF'
(version 1)
(allow default)
(deny sysctl-read (sysctl-name "hw.optional.neon"))
EOF
sandbox-exec -f /tmp/noneon.sb /Applications/OpenSCAD.app/Contents/MacOS/OpenSCAD --info
Result:
Incompatible processor. This Qt build requires the following features:
neon
Removing that one deny line makes it start normally. Any Qt 6 binary reproduces it.
Evidence from a real sandboxed run
Kernel log at the moment of the crash:
Sandbox: OpenSCAD(48915) deny(1) sysctl-read hw.optional.neon
AMFI: Denying core dump for pid 48915 (OpenSCAD)
hw.optional.neon was the only CPU-feature sysctl denied — armv8_crc32 was allowed by the existing prefix, exactly as predicted. The resulting crash report is EXC_CRASH (SIGABRT), single thread, ~25 ms after launch, with the faulting stack inside dyld4::prepare → findAndRunAllInitializers:
libsystem_c.dylib abort
QtCore qAbort()
QtCore qDetectCpuFeatures.cold.1
QtCore qDetectCpuFeatures + 468
dyld dyld4::Loader::findAndRunAllInitializers(...)
dyld dyld4::prepare(...)
dyld start
Suggested fix
Add one entry to the (allow sysctl-read ...) block:
(sysctl-name-prefix "hw.optional.arm.")
(sysctl-name-prefix "hw.optional.armv8_")
+ (sysctl-name "hw.optional.neon")
(sysctl-name-prefix "hw.perflevel")
Alternatively, broaden to (sysctl-name-prefix "hw.optional."), which would also cover hw.optional.floatingpoint, hw.optional.AdvSIMD_HPFPCvt, and similar legacy names that other toolchains probe. These are read-only booleans describing public CPU capabilities, so allowing the family carries little information-disclosure risk.
One caveat: I verified that denying hw.optional.neon causes the abort and that allowing it clears that specific failure. I could not fully verify the one-line change is sufficient end-to-end under the complete shipped profile, since I could not reconstruct it faithfully outside the CLI.
Workaround
Add the invoking command to sandbox.excludedCommands so it runs outside the sandbox.
Environment
- Claude Code v2.1.220
- macOS 26.5.2 (25F84), arm64 (Apple Silicon)
- Qt 6.8.3 (as bundled in OpenSCAD 2026.06.12); reproducible with any Qt 6 binary
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗