Security pass: October 3 surfaces
A bounded review of the surfaces added on October 3 and what changed.
Bounded review of app-server, pi and its temporary bridge, threads and explicit snapshots, queue/steering,
PR watch, approval previews, saved tool outputs, compaction transcripts, usage resume and feature gating.
Baseline: e3ef75d. Threats: authenticated read-only/full peers, hostile repository executable lookup,
untrusted model/tool/PR content and secret persistence. This is not a whole-repository security certification.
Confirmed defects and corrections
| Surface | Trigger and impact | Correction |
|---|---|---|
| app-server | Internal exceptions or reflected method names could disclose credentials in JSON-RPC errors | Generic internal failures; redacted expected messages |
| pi approvals | Oversized input was clipped before approval; a previous session grant survived entry into plan mode | Refuse oversized arguments without running them; plan denies non-read tools before grants |
| pi traces | New credentials appeared in raw RPC lines, including split streaming frames | Redact trace lines; omit message-update payloads from traces |
| threads | wait read another session's answer as an unrestricted read | Same gate as read/search, explicit mention grants retained |
| peer model tools | Direct local-only guards did not cover indirect native model calls | Exclude threads/pr_watch on peer turns, preserve through synthetic continuation and restart; refuse peer pi turns |
| queue/steer | Full peers could inspect pending local prompts and steer them into a host turn | Queue inspection/edit/reorder/steer are local-only |
| PR watch | PR title/head/check metadata could be saved or exported with secrets; returned URLs could become next poll arguments; predictable temporary file followed a symlink | Redact before parsing/persistence; canonical GitHub URLs only; exclusive random temporary file |
| blast radius | Repository PATH could substitute git/find before approval | Fixed system executables, controlled PATH, loader variables removed |
| output filters | Newly discovered credentials reached full saved outputs; split tool deltas escaped per-event detection | Redact before saving/filtering/truncation; bounded line/private-key buffering for live output |
| output/transcript storage | Symlinked output directories redirected private files outside the selected store | Revalidate real private directories per save, exclusive files, atomic transcript writes; cleanup skips links |
| usage resume | Losing the recorded model permitted resume into a fallback | Ask when the model changes or disappears; peers cannot arm or manually resume host work |
| feature policy | Full peers bypassed tier/features guards via aliases or package/MCP resource commands | Host execution settings and execution-resource mutators are local-only |
Regression failures were observed before their corresponding minimal fixes; transport-level regression coverage also exercises local and full-peer command boundaries. New suites use temporary stores, synthetic credentials, scripted providers and fake gh/pi transports. No account, installation, real provider or external network is required.
Retained design choices for review
- pi remains off by default and permitted only by the full tier or explicit override. Following the lead's isolation
decision, untrusted project extension discovery is disabled (or refused when the flag is unavailable), session grants
cover exact tool arguments, and pi follows the native OS sandbox setting. Every mode except full requires a bridge
handshake before turns and compaction. The bridge does not impose native per-path permission rules or attest arbitrary
trusted extensions. With sandbox off, approved tools still have the user's rights. See
pi-engine.md; verification uses fake pi and sandbox processes, with no external pi installation. - Thread reads use the existing permission modes: full/edits can broadly authorize a write-class action. Decide whether
cross-session reads must always require an explicit mention or individual approval even in those modes. This pass
aligns
waitwithread/search; it does not redefine full access. - D1 auto-resume stays armed by default with a known reset, no silent fallback, and draft/quota/stale-turn checks.
Disabling
usage_resumeaffects new pauses; Cancel disarms an existing schedule. Changing that persistence rule requires a product decision. Peer tool restrictions persist independently of the schedule. - Safe mode is a startup ceiling, while local overrides outside safe mode remain intentional. The host already omits MCP, custom tools and extensions and denies pi in safe mode; a repository cannot change that startup option.
- Detection covers recognized secret formats and registered session values. It cannot prove arbitrary unlabeled text is harmless. General model-text streaming, external engine-owned session files and other legacy export paths are not certified by this bounded pass. A blanket ban on model text or on external-engine persistence needs a separate policy.
- Storage guards assume the selected data root is trusted. They do not provide isolation from a concurrent same-user process racing filesystem operations; that requires OS-level confinement. Existing disk/retention caps remain intact.
Validation
Run only the changed/new test files plus CLI footprint and TUI automation. Typecheck from the worktree root with
./node_modules/.bin/tsc -p tsconfig.base.json --noEmit. Full suite, live providers and real external tools are excluded.
The initial socket checks encountered EPERM; subsequent runs successfully exercised the local transports, full-peer
policy, TUI automation and a native peer turn resumed after daemon restart. Final results are recorded in the delivery.
Final targeted test run: 157 passed, zero failed across 14 files, including footprint and TUI automation.
Addendum 2026-10-05: skills, packages and project resources
Follow-up to an external review of skills, packages and custom views. Each finding was treated as a hypothesis and reproduced with a failing regression test (temp directories, fake MCP server, scripted provider, no network) before the fix.
| Surface | Trigger and impact | Correction |
|---|---|---|
| Project MCP servers | Servers from a project's mcp.json or packages were spawned or contacted before the directory was trusted, with the full environment | Held until /tools trust (needsTrust, no process, connection or discovery); credential-named variables are not inherited by project servers unless their env lists them. See mcp-oauth.md |
| Custom command inline shell | ! fragments ran in the daemon before the turn, outside approval, deny rules and sandbox; $1/$ARGUMENTS were pasted raw into shell text | Fragments run as shell tool calls of the turn (same permission, approval, sandbox and hooks); placeholders become single-quoted words, and unquotable positions are refused. See commands.md |
Custom approval and question views | A view could draw "Reject" over a clickable approving region, and its regions did not follow rows the terminal inserted | The terminal draws the option rows and owns their clicks; these slots have no actions; regions of other slots are rebased after any insertion. See views.md |
| Package and trust changes | The MCP server set was built once and not rebuilt on package install, enable, disable, remove or trust changes | Reconciled on each of those events |
| Daemon extensions | A first daemon(api) that threw after registering left its tools, commands, hooks, auth and provider live | Registrations are discarded when setup fails; the error stays in /extensions. See extensions.md |
| Claude agent files | An allow list of unknown names became "every tool"; permissionMode was dropped silently | Unknown-only lists grant none, qualifiers never widen, plan and acceptEdits cap the agent, unhonourable fields are listed as warnings. See agents.md |
Reviewed and left as is (reported, not changed): the other privileged click regions of custom views (notice queue send
and limit chips, slash.menu select, home and sidebar open) act on listed items and grant no permission; their arg is
validated against the model. Saved JSON workflow plans from an untrusted project layer are not blocked by trust (scripts are),
but starting any workflow needs an approval that lists its tasks and check commands. The status-line script trust check in
daemon.ts compares only canonical paths, so it fails closed for a non-canonical stored entry.
Validation: the new and changed tests (mcp, daemon custom commands and command execution, views, extensions, packages),
CLI footprint and TUI automation, and ./node_modules/.bin/tsc -p tsconfig.base.json --noEmit.