fix(bundle): declare macOS TCC privacy keys for child processes (#323)

* fix(bundle): declare macOS TCC privacy keys for child processes

tty7 currently ships no NS*UsageDescription keys and no data-access
entitlements, so macOS falls back to a repeated "access other apps' data"
prompt whenever a child process (shell, coding agent, mole, etc.) touches a
protected folder such as ~/Library/Containers, Mail, Messages, or Calendar.
kitty and Kaku both declare these privacy intents, which converts the prompt
into a single, clear one-time grant.

Add the folder/volume usage descriptions and the matching personal-information
and device entitlements to the macOS bundle so the app behaves like its
terminal peers.

* fix(bundle): rework TCC usage strings per review

- Correct problem statement: describe child-process-denied-without-prompt
  instead of the Full Disk Access framing (no NS*UsageDescription key exists
  for that class).
- Add the full usage-string set (camera, microphone, contacts, calendars,
  reminders, photos, location, motion, local network, bluetooth, speech
  recognition, system administration, apple events), kitty-style wording.
- Use macOS spellings: NSCalendarsFullAccessUsageDescription /
  NSRemindersFullAccessUsageDescription / NSLocationUsageDescription.
- Drop every entitlement that has no matching usage string; keep only
  com.apple.security.automation.apple-events.
- Restore trailing newline at EOF in bundle-macos.sh.
- Document the Full Disk Access manual-grant requirement in docs/features.md.

* docs: rewrite macOS privacy as feature notes (en + zh-CN)

* fix(bundle): drop the apple-events entitlement, tidy the privacy docs

The entitlement did not do what its comment claimed. Nothing in tty7 or in
gpui's mac platform layer sends an Apple event, and it would not help the
case this change is about either: the hardened-runtime automation check runs
against the process actually sending the event, which is the pane's child
carrying its own signature. What TCC reads off tty7.app is the usage string
in Info.plist, which stays. Entitlements are per-executable and never
inherited, so granting this one only widened what injected code could reach
under an identity that already holds disable-library-validation.

Docs: spell out the four Full Disk Access paths instead of running them
together as one nested path, drop motion from the user-facing list (Core
Motion has no macOS implementation, though the key stays for kitty parity),
and place the section identically in the English and Chinese files.

---------

Co-authored-by: l0ng-ai <24760907+l0ng-ai@users.noreply.github.com>
This commit is contained in:
Gabi
2026-08-07 11:15:22 +08:00
committed by GitHub
co-authored by l0ng-ai
parent 17c0ea2935
commit e47b49dfdd
3 changed files with 82 additions and 0 deletions
+46
View File
@@ -68,6 +68,40 @@ cat > "$APP/Contents/Info.plist" <<PLIST
<key>CFBundlePackageType</key><string>APPL</string>
<key>NSHighResolutionCapable</key><true/>
<key>NSPrincipalClass</key><string>NSApplication</string>
<!-- tty7 is a terminal workbench: panes are forked from the bundled
executable, so macOS attributes a child process's protected-resource
requests to tty7.app. Without these usage strings a program you run in
a pane that asks for camera / microphone / contacts / calendar /
photos / location / reminders / Apple Events is denied outright with
no prompt, and cannot even be granted in System Settings. Declaring
them mirrors what kitty and Kaku ship for exactly this reason: Mac
TCC reads the responsible bundle's usage string, not the child's. -->
<key>NSCameraUsageDescription</key>
<string>A program running inside tty7 would like to access the camera.</string>
<key>NSMicrophoneUsageDescription</key>
<string>A program running inside tty7 would like to access the microphone.</string>
<key>NSContactsUsageDescription</key>
<string>A program running inside tty7 would like to access your contacts.</string>
<key>NSCalendarsFullAccessUsageDescription</key>
<string>A program running inside tty7 would like to access your calendar data.</string>
<key>NSRemindersFullAccessUsageDescription</key>
<string>A program running inside tty7 would like to access your reminders.</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>A program running inside tty7 would like to access your photo library.</string>
<key>NSLocationUsageDescription</key>
<string>A program running inside tty7 would like to access your location information.</string>
<key>NSMotionUsageDescription</key>
<string>A program running inside tty7 would like to access motion data.</string>
<key>NSLocalNetworkUsageDescription</key>
<string>A program running inside tty7 would like to access the local network.</string>
<key>NSBluetoothAlwaysUsageDescription</key>
<string>A program running inside tty7 would like to use Bluetooth.</string>
<key>NSSpeechRecognitionUsageDescription</key>
<string>A program running inside tty7 would like to use speech recognition.</string>
<key>NSSystemAdministrationUsageDescription</key>
<string>A program running inside tty7 requires elevated privileges.</string>
<key>NSAppleEventsUsageDescription</key>
<string>A program running inside tty7 would like to control other applications via Apple Events.</string>
</dict>
</plist>
PLIST
@@ -109,6 +143,18 @@ if [[ -n "$SIGN_ID" && -n "${APPLE_CERTIFICATE:-}" ]]; then
<key>com.apple.security.cs.allow-jit</key><true/>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key><true/>
<key>com.apple.security.cs.disable-library-validation</key><true/>
<!-- Deliberately nothing beyond those three, and in particular no TCC
entitlement to match the usage strings in Info.plist. Those strings
are about a *child* process's request: macOS attributes it to tty7.app
as the responsible process and reads the wording from its bundle. The
hardened-runtime entitlement, by contrast, is checked against the
process actually sending the request — the child, carrying its own
signature, since entitlements are per-executable and never inherited.
So camera / microphone / location / apple-events on tty7.app would do
nothing for a pane, while widening what injected code could reach
under tty7's identity; this bundle already carries
disable-library-validation. Same reasoning the comments below use to
keep the GUI's entitlements off the CLI. -->
</dict>
</plist>
ENT
+19
View File
@@ -122,6 +122,25 @@ a brief pause; `prefix` + an unbound key passes straight through.
- Hot paths are lock-free — a big `cat` never waits on drawing
- The daemon buffers up to 16 MiB ahead of the window before backpressure applies
## macOS privacy
Panes are forked from the bundled executable, so macOS attributes a program's
request for a protected resource to tty7.app. tty7 declares the matching TCC
usage strings (camera, microphone, contacts, calendar, reminders, photos,
location, local network, Bluetooth, speech recognition, Apple Events, system
administration) so that program gets the normal one-time prompt instead of
being denied outright with no prompt at all.
Not covered by usage strings:
- **Full Disk Access** — Apple defines no usage-string key for it. Reaching
`~/Library/Mail`, `~/Library/Messages`, `~/Library/Safari` or
`~/Library/Containers` needs a manual grant in System Settings.
Declaring a usage string is not the same as holding the permission: tty7.app
itself is granted none of these resources. Every prompt you see belongs to
whatever you ran in the pane, and you can revoke it under Privacy & Security.
## Localization
The GUI ships English and Simplified Chinese strings. Pick one in Settings →
+17
View File
@@ -118,6 +118,23 @@ Aider、Amp、OpenCode 等约 17 个)并在其外围加功能 —— 绝不包
- 热路径全程无锁 —— 再大的 `cat` 也不会阻塞在渲染上
- 触发背压前,守护进程最多可领先窗口缓冲 16 MiB
## macOS 隐私
窗格是从 app bundle 里的可执行文件 fork 出来的,所以程序申请受保护资源时,
macOS 会把这次请求算到 tty7.app 头上。tty7 声明了对应的 TCC usage strings
(摄像头、麦克风、通讯录、日历、提醒、照片、定位、本地网络、蓝牙、语音识别、
Apple Events、系统管理),这样程序才能正常弹出一次性授权窗口,而不是连弹窗都
没有就被直接拒绝。
不受 usage strings 覆盖的:
- **完全磁盘访问** —— 苹果没有为它定义 usage-string 键。要读写
`~/Library/Mail``~/Library/Messages``~/Library/Safari`
`~/Library/Containers`,需要在「系统设置」中手动授权。
声明 usage string 不等于持有权限:tty7.app 自己一项都没有拿到。你看到的每个
授权弹窗都属于你在窗格里运行的那个程序,也可以在「隐私与安全性」中撤销。
## 本地化
GUI 目前提供英文和简体中文两套文案。在「设置 → 外观 → 语言」中选择,或直接改