Files
orca/docs/plans/validation
Jinwoo-H f7e129ffe6 docs(macos): finalize the STA-7948 daemon folder-access plan on live evidence
Replace the helper-first recovery design with a two-layer plan: an in-daemon
opendir verdict plus a notice on the existing attribution poll (ship now), and
the helper probe for already-deployed old daemons (evidence-gated). Record the
2026-09-21 measurements: on Documents, access(2) passes while opendir gets
EPERM, so the current accessSync verdict is blind to the incident's folder
class; daemon-spawned login shells carry the daemon's grant; the login wrapper
is unconditional in production.
2026-09-21 00:59:44 -04:00
..

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).