Files
orca/docs/plans/validation/README.md
T
Jinwoo-H 27dabcbe90 docs(macos): fold PostHog field data into the STA-7948 plan and drop the helper probe
daemon_pty_cwd_denied fired for 1,438 users in 21 days (2.8% of users adopting
a different-version daemon, 96% on Documents/Desktop/Downloads), so the daemon's
existing verdict already observes the failure and the helper probe for old
daemons has no population left to cover. A fresh same-version daemon ended the
denial for about 69% of observable users; the notice copy now tells the rest to
re-allow the folder.
2026-09-21 01:25:33 -04:00

10 KiB
Raw Blame History

TCC recovery detector validation — 2026-09-14

Scope: validate the proposed old-daemon-compatible transport and lifecycle mechanics, then independently review the design. This is not a reproduction of TCC poisoning.

Evidence

The harness launches its own isolated daemon process using startDaemon, DaemonClient, and the production createPtySubprocess path. It creates fresh temporary runtime directories and uses a compiled C executable as shellOverride, with no command input. The parent driver is Node, not the installed Electron app. No existing user daemon was restarted, no user session was touched, and no TCC permission or app bundle was modified.

Both runs explicitly reported the actual launch strategy: direct and wrapped respectively.

Case Helper result Driver result Cleanup
Readable directory with spaces, quote, dollar sign, backticks, newline errno 0 ok No live or retained diagnostic session
Missing directory errno 2 ENOENT No live or retained diagnostic session
Fixture directory chmod 000 errno 13 EACCES No live or retained diagnostic session
Sleeping helper, client kills its session No access verdict inferred Not applicable PID absence confirmed with ESRCH
Sleeping helper, no client kill No access verdict inferred Not applicable Five-second helper alarm; PID absence confirmed

Both daemon processes exited with code 0 after the harness shut them down. Fixture directory permissions were restored after each run. The small temporary runtime directories were retained for inspection; generated bundle removal does not affect the retained source.

The first harness iteration used the wrong event field (payload.sessionId instead of event.sessionId); the second attempted to kill a session already reaped after normal exit. Both harness issues were corrected before the successful runs. Normal cleanup tolerates only the specific already-reaped error, not arbitrary failures.

Existing regression suites:

ORCA_BACKGROUND_LAUNCH=1 node_modules/.bin/vitest run --config config/vitest.config.ts \
  src/main/daemon/terminal-host-cwd-readability.test.ts \
  src/main/daemon/daemon-adoption-telemetry-event.test.ts \
  src/main/daemon/daemon-tcc-attribution.test.ts

Result: 3 files passed, 22 tests passed.

Historical source check: git show a7fda48fe3^:src/main/daemon/types.ts and the corresponding pty-subprocess/shell-launch-plan.ts confirmed that shellOverride, env, and Unix executable launch support existed before cwd-readability telemetry. No historical binary was executed.

Reproduce the transport checks

From this worktree, with host-compatible node-pty already available:

TCC_PROBE_BUILD_DIR=$(mktemp -d /tmp/orca-tcc-validation.XXXXXX)
clang -Wall -Wextra -O2 docs/plans/validation/tcc-directory-probe.c \
  -o "$TCC_PROBE_BUILD_DIR/directory-probe"
node_modules/.bin/esbuild docs/plans/validation/tcc-probe-validation.ts \
  --bundle --platform=node --format=cjs --packages=external \
  --outfile=docs/plans/validation/tcc-probe-validation.cjs
ORCA_BACKGROUND_LAUNCH=1 node docs/plans/validation/tcc-probe-validation.cjs \
  "$TCC_PROBE_BUILD_DIR/directory-probe"
ORCA_BACKGROUND_LAUNCH=1 ORCA_PROBE_WRAPPED=1 \
  node docs/plans/validation/tcc-probe-validation.cjs "$TCC_PROBE_BUILD_DIR/directory-probe"

The harness is a macOS experiment, not shipping code. Wrapped mode requests the production login preflight; check the emitted actual strategy, since an unavailable login wrapper can fall back to direct. The helper contains a test-only stall switch. Production packaging, strict protocol parsing, queue limits, absolute deadlines, and crash-path cleanup need separate implementation and tests.

Independent review

A second agent reviewed the current protocol, shell launch, session listing, login wrapper, and environment handling. It recommended using the tiny executable instead of a diagnostic shell command, avoiding permanent success caching, and treating old-reader visibility and late-create cleanup explicitly. Those findings are incorporated in the design. Its final review also caught a trigger gap: checks must include failed terminal admission, since permission denial can prevent creation. The rewritten design includes that correction.

What this does not prove

  • No known poisoned daemon was available in the validation fixture.
  • chmod denial is POSIX permission behavior, not TCC; both tested sides were denied.
  • The unsigned prototype does not establish the final signed helper's TCC attribution.
  • A child can acquire different attribution under the login wrapper or executable identity.
  • Current-daemon protocol execution plus historical source inspection is not old-binary proof.
  • The helper alarm bounds an ordinary sleep, not an uninterruptible filesystem operation.
  • No actual restart or rendered notice was tested; there is no production UI change yet.

The shipping gate remains: affected ordinary terminal denied, final packaged helper denied, Electron main succeeds, then terminal/helper reads succeed after explicit restart.


2026-09-21 live evidence (production adopted daemon)

Scripts: live-2026-09-21/. Full raw report (TCC rows, ps trees, logs) is kept outside the repo at ~/orca-lanes/sta-7948-evidence-20260921/report.md on the collecting machine because it contains account paths.

A. access(2) versus enumeration under TCC (access_vs_scandir.py)

Run as a launchd-owned /usr/bin/python3 via launchctl submit (responsible pid = itself, no TCC row). No tccutil, no window; the job was removed afterwards.

Target access(R_OK|X_OK) stat scandir
~/Documents True ok EPERM
~/Library/Safari (Full Disk Access class) False ok EPERM
~/Library/Mail (Full Disk Access class) False ok EPERM
/tmp True ok ok

The Documents run was executed twice with identical output. Conclusion: for the SystemPolicyDocumentsFolder class, accessSync(R_OK|X_OK) passes while enumeration is denied, so the daemon's current cwdReadableByDaemon verdict cannot observe the incident's failure.

B. Production daemon, healthy state (probe-daemon.mjs)

Target: the live adopted daemon (pid 1232, protocol v36, spawned by app 1.4.207-adhoc while the running app was 1.4.206; spawnerExecPath /Applications/Orca.app/Contents/MacOS/Orca). The client speaks the real NDJSON protocol on the control and stream sockets with its own clientId, creates one short-lived session per target with a sta7948-evidence-* session id, reads the probe line, kills the session, and confirms it is absent from listSessions.

Target daemon cwdReadableByDaemon (accessSync) login-wrapped shell scandir control shell
~/Documents true ok (5 entries) ok
/tmp true ok ok
~/Library/Safari true ok (36 entries) ok

Five sessions were created; all exited 0; session count was 43 before and after. No process was killed, signalled, or restarted; no window was shown.

C. TCC responsible-process identity (resp.c)

responsibility_get_pid_responsible_for_pid (dlsym) for the process tree:

86248 -> 86248   Orca main
25437 -> 86248   Orca Helper (Renderer), child of Orca main      (probe control: children resolve to main)
86324 -> 86248   /usr/bin/log forked by Orca main                (probe control)
1232  -> 1232    production daemon (Orca Helper)
123   -> -1      /usr/bin/login (root; query not permitted)
162   -> 162     -/bin/zsh under that login
73362 -> 73362   -/bin/zsh under another login
73502 -> 73502   claude under that zsh

The daemon and every login-wrapped shell resolve to themselves, never to Orca main. Yet the shells read ~/Library/Safari, which only com.stablyai.orca holds Full Disk Access for (com.stablyai.orca.helper, the daemon's own identifier, has no TCC row). The shells therefore carry Orca's grant through the daemon, not through a pid-level link this API exposes. The ps tree is daemon -> /usr/bin/login -> -/bin/zsh -> agent CLIs; the bash trampoline execs away and never appears.

D. Other facts

  • All 1734 macos-tcc-pty-spawn events in the local daemon log carry strategy: "wrapped".
  • daemon_pty_cwd_denied has never fired on this machine and no cwdReadableByDaemon=false appears in daemon.log or main.trace.ndjson; per section A this is uninformative for the Documents class.
  • /Applications/Orca.app and Orca Helper.app are signed Developer ID, TeamIdentifier 6CX3WHS9HZ, hardened runtime.

What this still does not prove

  • The broken state was not observed; sections B and C describe the healthy state only.
  • Whether the daemon's in-process opendir and its shells' reads diverge when the lineage is broken (gate G1 in the design).
  • Whether restart alone recovers, or restart plus tccutil reset and re-allow is required (G1).

PostHog field data (queried 2026-09-21, 21-day window)

daemon_pty_cwd_denied fires only on proven divergence: daemon accessSync denied, app accessSync succeeded, macOS only (#18043, shipped 2026-09-01).

cwd_class app_version_match events users
documents different 5,674 863
desktop different 2,379 404
outside-home different 480 58
downloads different 369 79
documents same 113 33
desktop same 113 25
other-home different 25 8
  • Distinct denied users: 1,438 (1,374 in documents/desktop/downloads). Users who adopted a different-version daemon in the window: 50,876. Denial rate among them: 2.8%.
  • Persistence: 910 users denied on one day, 413 on 23 days, 115 on 4 or more days.
  • Daily denied users ramped from single digits on 2026-09-07 to about 300/day by 2026-09-16 as the telemetry release rolled out, then about 110135/day over the weekend.
  • Remedy signal: among denied users with a later same-version daemon_adopted (a daemon forked by the current app), 151 had no further denial and 68 were denied again (about 69% / 31%). 1,219 had no same-version adoption inside the window.
  • daemon_adopted with tcc_attribution=severed (the existing notice's trigger): 45 users. The cwd-denial population is about 30 times larger.

Consequence: the daemon's existing accessSync verdict does observe the failure in the field. The grant-less launchd probe (section A above) shows a second TCC mode on ~/Documents where access() passes and opendir fails; the two modes are different TCC states, and enumeration covers both.