Files
punktfunk/clients/apple/Config/Punktfunk-macOS.entitlements
T
enricobuehler 4b0b775e8e fix(apple): allow CoreHaptics audioanalyticsd mach-lookup under the macOS sandbox
GCDeviceHaptics.createEngine returns a CHHapticEngine (the only controller-rumble
API on Apple platforms); starting it spins up CoreHaptics, which looks up the
system audio-analytics daemon over Mach. The App Sandbox denies that global-name
lookup and the framework's precondition turns the denial into a hard crash
("Process is sandboxed but com.apple.security.exception.mach-lookup.global-name
doesn't contain com.apple.audioanalyticsd") the moment a controller's rumble
engine starts.

Add the documented, App-Store-acceptable temporary-exception whitelisting exactly
that one service. Verified embedded into the signed binary (codesign -d
--entitlements) alongside the existing entitlements. macOS-only (iOS/tvOS reject
temporary-exception keys and don't need it). App Store: declare it in App Sandbox
Entitlement Usage Information.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 23:57:06 +02:00

78 lines
4.3 KiB
XML

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<!-- macOS-ONLY entitlements. App Sandbox is a macOS concept (iOS/tvOS are always
sandboxed and REJECT this key at upload), so the macOS target points here while
iOS/tvOS keep the shared Config/Punktfunk.entitlements. The single macOS app is
sandboxed for BOTH channels — the Developer ID DMG is codesigned with this same
file (App Sandbox is allowed, not just required, for Developer ID), so what we
test locally (⌘R / DMG) is exactly what Mac App Store / TestFlight users get. -->
<!-- Required for Mac App Store / TestFlight distribution. -->
<key>com.apple.security.app-sandbox</key>
<true/>
<!-- Outbound QUIC control plane + raw-UDP data plane to the host, and NWBrowser mDNS
discovery / NWConnection resolve. Every outbound socket (incl. the linked Rust
core's UDP binds) needs this under the sandbox. -->
<key>com.apple.security.network.client</key>
<true/>
<!-- NOT optional, despite the client being "outbound only": the App Sandbox gates the
bind() syscall itself as a network-bind ("server") operation. quinn binds its QUIC
endpoint socket (quic.rs Endpoint::client 0.0.0.0:0) and the raw-UDP data plane
binds a local socket to receive host→client datagrams (transport/udp.rs); both fail
with deny(1) network-bind / EPERM without this, so NO video/audio/rumble ever
arrives. (The classic QUIC-on-quinn-under-sandbox trap.) -->
<key>com.apple.security.network.server</key>
<true/>
<!-- Microphone uplink: SessionAudio installs an AVAudioEngine input tap → Opus → host
virtual mic. TCC blocks AVAudioEngine input under the sandbox without this even with
NSMicrophoneUsageDescription present. -->
<key>com.apple.security.device.audio-input</key>
<true/>
<!-- Game controllers over Bluetooth via the GameController framework
(GCController.startWirelessControllerDiscovery — Xbox/DualSense). No CoreBluetooth in
the app, so no NSBluetoothAlwaysUsageDescription is required, but the sandbox still
gates GameController's BT HID access on this key. -->
<key>com.apple.security.device.bluetooth</key>
<true/>
<!-- Game controllers over USB + USB HID mouse/keyboard via the GameController framework.
device.usb gates the IOHIDLibUserClient path the framework uses for wired devices
(per Apple DTS); without it, plugged-in controllers deliver no input. Justify in App
Review notes ("reads input from USB game controllers"). -->
<key>com.apple.security.device.usb</key>
<true/>
<!-- Controller rumble via CoreHaptics: GCDeviceHaptics.createEngine → CHHapticEngine
(GamepadFeedback's RumbleRenderer), and AVAudioEngine playback, reach the system
audio-analytics daemon `com.apple.audioanalyticsd` over Mach. The sandbox denies that
global-name lookup unless it's whitelisted here, and the framework's own precondition
turns the denial into a HARD CRASH ("Process is sandboxed but
com.apple.security.exception.mach-lookup.global-name doesn't contain
com.apple.audioanalyticsd") the moment a controller's haptics engine starts. This
temporary exception is the documented, App-Store-acceptable way to permit exactly that
lookup — and ONLY that service (the key takes exact names, no wildcards). App Store:
declare it in App Store Connect → App Sandbox Entitlement Usage Information ("CoreHaptics
gamepad rumble contacts the system audio-analytics daemon"). -->
<key>com.apple.security.temporary-exception.mach-lookup.global-name</key>
<array>
<string>com.apple.audioanalyticsd</string>
</array>
<!-- Keychain Sharing (unchanged from the shared file): a team-scoped access group so the
punktfunk/1 client identity in the data-protection keychain is gated by the app's
entitlement (team + bundle id), persisting across rebuilds with NO prompt — see
ClientIdentityStore. $(AppIdentifierPrefix) expands to the team prefix at signing
time (the Developer ID codesign step in release.yml resolves it via sed). -->
<key>keychain-access-groups</key>
<array>
<string>$(AppIdentifierPrefix)io.unom.punktfunk</string>
</array>
</dict>
</plist>