# Security Model

## Core principles

- ARC Pi runs with the operator's local permissions; install only reviewed code.
- The parent decides; workers provide evidence.
- Delegations are bounded before execution.
- Analysis and review use read-only runner modes where available.
- Implementation writes are serialized by the external runner per project.
- Secrets and shipping mutations are outside normal worker authority.

## Trust boundaries

### Pi and this package

Pi extensions are arbitrary local code. The ARC extension can spawn the configured wrapper and read process output. Package installation therefore requires the same scrutiny as any executable dependency.

### Runner discovery

The wrapper prefers `ARC_ORCHESTRATOR_BIN`, including a saved explicit setup path, then the exact installed `@andysolomon/arc-orchestrator` dependency, known executable names on `PATH`, and known local layouts. It rejects paths that resolve back to itself. An attacker who controls explicit, PATH, or checkout locations can replace the runner. Prefer the signed npm dependency or an absolute reviewed executable path, and protect launcher configuration from untrusted writes.

### Worker processes

The external runner owns sandbox selection:

- Codex and Claude Code analyze/review are read-only;
- Codex and Claude Code implement use workspace-write access;
- Cursor-backed workers use Cursor plan mode for read-only analyze/review when the runner places them on the automatic chain or an explicit explore/check alias; implement uses workspace-write;
- write-capable runs serialize per checkout, while separate worktrees permit safe parallel writes.

ARC Pi does not independently sandbox the runner. A compromised or incorrectly configured runner can exceed these expectations.

### Parallel subagents

The session-scoped `subagent_*` extension gives each child an explicit
built-in tool allowlist and an isolated resource loader with no extension
resources or tools. This is a policy and resource boundary, not an OS
sandbox: the allowlisted local `bash` tool executes on the trusted host with
the parent process's user permissions. Child prompt restrictions prohibit
nested workers, ARC delegation, shipping mutations, secrets, and scope
expansion, but the host does not mechanically enforce those instructions.
Children are bounded to four active instances per parent session and are
cancelled and disposed during parent-session shutdown; they cannot reattach
across sessions. The interactive dashboard exposes normalized, size-capped
assistant/tool activity rather than raw provider events or a terminal. Steering
is size-capped and accepted only by an active in-process child; settled views
are read-only, and dashboard subscriptions are removed on close or shutdown.

## Input and process safety

- The extension and wrappers use process argument arrays with `shell: false`.
- Required task fields prevent accidental unscoped calls.
- Labels are normalized to short, non-sensitive characters.
- Stdout and stderr are capped independently to limit Pi context growth.
- Pi cancellation sends `SIGTERM` to the process group, then an unref'd 2s `SIGKILL` escalation that still targets the group even if the wrapper already exited (so SIGTERM-ignoring descendants cannot survive).
- `arc_delegate` also enforces `ARC_PI_DELEGATE_TIMEOUT_MS` (default 30 minutes): the same SIGTERM → group SIGKILL sequence runs when the deadline elapses, even without operator cancellation.
- Failed runner exits become failed Pi tool results.
- Provider CLI login/status commands are shell-free, output-bounded, and initiated only by explicit user commands.
- Interactive login cancellation sends `SIGTERM`, followed by `SIGKILL` if the Provider CLI does not exit.
- Temporary login instructions are cleared from the UI and never added to Pi conversation history or ARC traces.
- Authentication status exposes only `missing`, `authenticated`, `unauthenticated`, or `unknown`.

### Background terminals

- `arc_terminal_start` is intentionally shell-capable: its command is passed to `/bin/sh -lc` as one argument, with `shell: false` at the Node spawn boundary. Treat model-authored commands with the same care as commands entered in a terminal.
- Background terminals are Unix-only by policy. `terminalShellCommand` rejects `win32` explicitly rather than falling back to a shell without process-group cleanup.
- Working directories are validated with real paths: the request must resolve lexically inside the project, exist, be a directory, and its `realpath` must also stay inside the project's `realpath`, so symlinks cannot escape the project. These checks assume a trusted local filesystem without a hostile process concurrently swapping path components between validation and spawn (TOCTOU); they do not claim a security boundary for adversarial or network filesystems.
- Commands receive no stdin and cannot be interactively steered. Concurrency and retained history are bounded per extension runtime.
- Stdout and stderr have independent in-memory tails. Each tail stays within its configured UTF-8 byte bound even when a multibyte character is split at the cut. Incomplete sequences and invalid UTF-8 (overlong encodings, UTF-16 surrogates, and code points above U+10FFFF) are dropped rather than replaced, so U+FFFD cannot expand the tail past the bound. Complete streams spill only to mode-0600 files under a mode-0700 OS temporary directory, capped at 64 MiB per stream (`spillTruncated` marks the cap). Producers are paused on write-stream backpressure, and a failing spill file records `spillError` without affecting the process. Spill paths may still contain sensitive command output and should not be shared.
- Every settlement path reaps the detached process group: after natural shell exit, explicit kill, and session shutdown, surviving descendants (redirected, backgrounded, or TERM-ignoring) receive `SIGTERM`, then `SIGKILL` after a bounded grace period. Escalation uses referenced timers that are always cleared, so it cannot be skipped by an idle event loop, and group-exit polling uses a monotonic clock, so wall-clock adjustments cannot stall it. Signal errors (`EPERM`) from settlement reaping and explicit terminate are recorded on the terminal result, snapshot, and follow-up instead of thrown, never skip the bounded wait or the `SIGKILL` escalation, and never reject cleanup.
- Command text, labels, cwd, errors, and output tails are stripped of 7-bit ESC and C1 control sequences (including CSI/OSC/DCS/SOS/PM/APC and malformed or unterminated forms) and other control characters, and Unicode line/paragraph separators (U+2028/U+2029) are normalized to newlines, before they reach tool results, follow-up messages, or the `/arc-terminals` UI.
- Dashboard/status callbacks are best-effort: exceptions from UI surfaces are swallowed and never interrupt start, kill, settlement, or shutdown cleanup.
- Completion emits at most one bounded, sanitized follow-up, which Pi persists in conversation history. Shutdown suppresses late follow-ups and removes temporary spill directories; evicting a settled terminal from bounded history also removes its spill directory. After shutdown cleanup completes, that session's terminal registry records are dropped so reopening the same session ID cannot expose stale commands, statuses, tails, or deleted spill paths. Failed spill removals stay tracked independently so a later shutdown can retry them. Registry and spill state cannot resurrect a command in another session. Cleanup is not guaranteed if the host process is SIGKILL'd or crashes before shutdown runs.
- `/arc-terminals` sanitizes input and rejects embedded line separators (`LF`, U+2028, U+2029). Unknown-query notifications are single-line and do not echo raw multiline input.
- Background terminals are ordinary local processes. They do not use ARC runner sandboxes, routing, write locks, authorization gates, traces, or model-worker registries.

## Prohibited worker actions

Every normal delegation forbids workers from:

- committing or pushing;
- creating or merging pull requests;
- posting GitHub mutations;
- deploying;
- accessing or editing secrets;
- invoking nested model workers;
- touching unrelated files.

The external ARC system has no mechanical worker routes. Authorized ship operations are performed directly by the parent after reviewing worker evidence; workers remain prohibited from those mutations.

## Configuration and sensitive data

`arc-pi-launcher.json` stores a local executable path, not credentials. `arc-pi-install.json` stores only package identity, version, and installation root so first-launch setup can remain version-aware. Neither file contains npm or provider credentials. Pi Provider authentication remains in Pi/provider-owned stores. Subscription-backed Worker authentication remains in the normal Cursor Agent or Claude Code store; ARC Pi never reads, copies, refreshes, persists, or logs its token material.

The login commands may transiently display vendor URLs or device codes because the operator needs them to complete authentication. That raw output is held only in bounded process/UI memory and discarded after the command. Status parsers discard email, account identity, subscription tier, token-source metadata, URLs, and unknown JSON fields. Do not put API keys in settings, task text, labels, traces, or repository files.

Authentication commands are not model-callable tools and never run during startup or delegation. ARC Pi omits logout because changing a shared vendor credential store could disrupt unrelated CLI sessions. npm installation runs no home-mutating lifecycle script; profile setup happens only on explicit setup or first launch/package-version change. Installation, update, removal, and logout remain explicit operator actions.

Runner traces intentionally omit task text and absolute project paths, but operators should still review the external runner's trace and optional observability configuration before enabling exports.

## Session monitoring

The session-monitor library and CLI are read-only consumers of the orchestrator-owned run registry, session tip/growth, and runner traces. Soft intervene means `recommend_cancel` evidence and hang/token reasons only. Outside the Pi parent, `arc-pi-monitor` must not SIGTERM/SIGKILL worker process trees. Real cancel remains on Pi's abort path (or an explicit operator action). There is no model-callable watch tool and no IPC cancel channel from the monitor surfaces.

Token hard budgets may set `recommend_cancel`; they do not themselves terminate processes. Operators should treat monitor output as advisory evidence for the parent or human, not as an automatic kill switch.

## Residual risks

- This implementation has not been validated on Windows.
- Background terminals are Unix-only; Windows is rejected at start rather than run without process-group cleanup. A descendant that leaves the process group (for example via `setsid` or a double fork into a new session) cannot be reaped and only settlement of the pipes is bounded.
- Abrupt host termination (SIGKILL or crash of the Pi process) skips session shutdown, so descendant process groups and spill directories are not guaranteed to be cleaned up.
- Shell commands can access anything available to the operator account; do not place secrets directly in command text because Pi session history records tool arguments.
- An alternate `cwd` supplied to `arc_delegate` is trusted as part of the parent-authored contract.
- Slash-command argument parsing is whitespace-based; it is not a general shell parser. `/arc-terminals` rejects embedded line separators rather than rendering them as extra notification lines.
- Pi package/API updates can require recompilation or adaptation.
- The runner and Provider CLIs are separately installed and versioned, so incompatible command or status-schema changes can produce `unknown` status or break delegation.
- Non-TUI login requires an explicit provider argument and cannot offer Pi's interactive cancellation UI; use the vendor CLI directly when a terminal-mediated flow is required.
