Trust boundaries and threat model
ContextRelay is local developer tooling, not an OS sandbox and not a hosted service. It coordinates Claude Code and Codex inside one workstation and one project at a time. Knowing exactly what that means - what ContextRelay protects, what it deliberately leaves to you, and how problems surface - is the difference between trusting it appropriately and over-trusting it.
This page is a faithful summary of the project's
docs/THREAT_MODEL.md,
which is the authoritative document. If anything here ever drifts from that file,
the threat-model document wins.
The trust model in one sentence
Claude Code and Codex are trusted local processes that run with your account's privileges; ContextRelay is the mediation and record-keeping layer between them and the host. Everything inside your operator account is trusted to act with your authority. Everything outside the host should be unable to reach the daemon.
Operator account on one workstation
Claude Code + plugin <---- loopback WS ----\
\
Codex TUI / app-server <---- loopback -----> ContextRelay daemon
/
Codex MCP stdio server ---- loopback WS ----/
Browser viewer ------- loopback HTTP -------/
Local project state: .contextrelay/state
Local registry: OS app/state directory
Every control endpoint binds to 127.0.0.1 and is token-authenticated. The
daemon, the Codex proxy, and the browser viewer are not reachable from another
host. Tokens are split by purpose (control, proxy, viewer), stored in
owner-only files, and compared with constant-time checks.
What is in scope
- A single trusted operator on a single workstation.
- Claude Code connected through the ContextRelay Claude plugin.
- Codex connected through its app-server path and, optionally, the ContextRelay
Codex MCP stdio server (
ctxrelay codex-mcp install). - A daemon bound to loopback with project-scoped state under
.contextrelay/state. - Local tokens and identity checks that prevent accidental cross-project daemon reuse.
- Optional read-only backup agents - only when autonomy is explicitly enabled.
What is out of scope
- Remote access. The daemon and Codex proxy must not be reachable off-host.
- Multi-user / multi-tenant use. There is no RBAC layer.
- Protection from malware or another process running as the same OS user. Such a process can read local tokens, ledger files, source, and terminal state.
- Sandboxing Claude, Codex, backup agents, shell commands, or code under test.
- A tamper-proof audit trail. The ledger is local operational state.
Controls that are in place
These are real, enforced behaviors - not aspirations.
| Control | What it does |
|---|---|
| Loopback bind | Daemon, Codex proxy, and viewer listen only on 127.0.0.1, preventing remote ingress. |
| Split tokens + lockout | Separate control / proxy / viewer tokens, constant-time comparison, and an auth lockout that slows repeated bad attempts. |
| Daemon + project identity | A daemon is bound to its expected state directory and refuses reuse across projects or state dirs. |
| Owner-only file modes | State dir 0700, secret files 0600, where the OS supports it. |
| Frame validation + rate limits | Malformed control frames are rejected; control messages are size- and rate-capped. |
| Coordinator git-write policy | A single agent (collaboration.coordinator) owns git writes; non-coordinators are expected to hand off. |
| Permission mediation | Eight capabilities (read, write, shell, network, git, secrets, browser, external_api) plus a readonly flag. When a capability is denied, the daemon blocks the operation and returns a distinct reason. |
| Read-only-by-default autonomy | Backup agents are OFF until you run ctxrelay autonomy on; even then they are read-only. |
Fail-closed act:write | Autonomous edits are OFF by default behind a layered, fail-closed gate chain (detailed below). |
| Log redaction | Common secret patterns are redacted on rejection-path logs. |
Three of these are worth understanding in depth before you rely on them.
Control-message limits
The control WebSocket caps both message size and message rate per connection, and bounds relay recursion depth, so a runaway or hostile client cannot flood or deeply re-enter the daemon:
CONTEXTRELAY_MAX_CONTROL_MESSAGE_BYTES- default1000000(max accepted message size).CONTEXTRELAY_MAX_CONTROL_MESSAGES_PER_MINUTE- default120(per-connection rate limit).CONTEXTRELAY_MAX_DEPTH- default3(maximum relay recursion depth).
Permission mediation is a broker, not a kernel
ContextRelay records and brokers only the actions it mediates directly. In
readonly mode or when a capability is denied, the daemon blocks the operation
and hands the agent a reason. But provider-native tools still enforce their own
approvals - ContextRelay does not intercept every syscall. It is a policy broker
layered on top of Claude Code and Codex, not an operating-system permission kernel.
act:write is fail-closed by design
act:write lets an autonomous idle worker edit files. It is default-off and
built to fail closed. A write is authorized only when every gate passes, in order:
- Autonomy is on.
autonomy.writableAction.modeis"act".- A positive per-task cost budget (
costBudgetUsd). - The hard env kill-switch
CONTEXTRELAY_WRITE_MODE_ENABLEDis set. No config can supply this - a config alone can never enable a write. - Kind and owner allowlists match.
- Strict dual-idle quiescence (both agents idle), single-flight, and worktree containment.
On top of the gate chain, a separate per-day USD cap
(CONTEXTRELAY_WRITE_DAILY_CAP_USD) is AND-ed in by the daemon. A missing or
invalid cap, an invalid cost estimate, an unreadable or malformed spend ledger, or
an over-cap projection all fail closed.
Containment is the worktree boundary plus restricted tooling, not an OS jail:
- The worker runs with its working directory set to an ephemeral git worktree
on a fresh
contextrelay/write/branch; ContextRelay captures only that worktree's diff. AworktreeRootthat resolves into the primary tree is refused, and paths are canonicalized so a symlink or traversal cannot slip past. - ContextRelay never commits, merges, or pushes the worker's output. Codex
runs under
--sandbox workspace-write(a file-write sandbox, not a full OS jail) with no approvals bypass and no full-auto; Claude runs under aRead,Grep,Glob,LS,Edit,Writetool allowlist that excludesBashand network tools by omission. The worker is told to leave everything uncommitted; the daemon captures the diff and tears the worktree and branch down.
The result is recorded as an idle_write_result artifact - advisory until a
human or the git coordinator chooses to apply it.
Enabling act:write is covered step by step in
Enabling act:write (autonomous edits) safely.
The conceptual model lives in Read-only by default.
Explicit non-controls
Being honest about what ContextRelay does not do is as important as listing what it does. None of the items below are bugs - they are deliberate boundaries.
There is no OS sandbox and no TLS. Claude, Codex, tests, commands, and backup agents all run with your operator privileges. Loopback-only local traffic is the boundary. ContextRelay trusts the underlying Claude Code and Codex processes; it cannot defend against same-user local token theft or a malicious process running as your user.
The single-git-owner policy is written into agent instructions and surfaced in state, but git permissions are not changed at the OS level. A non-coordinator agent is expected to hand off git work - nothing in the OS stops it. The policy is a convention the agents follow, backed by instructions and visibility, not a hard lock.
A native Claude Code Workflow runs sub-agents that can perform git, gh, or
merge operations directly - outside the ledger and outside the coordinator
policy. ContextRelay does not try to block this. Bypass mode (a repo that
intentionally drives Workflows directly) is a documented operator choice in which
ContextRelay is not the git owner and does not claim to be.
There is no cryptographic ledger integrity - local files can be edited by you or any same-user process. The browser viewer can clear current-session history through an authenticated local endpoint. Named-session ledger writes are tagged but not fully scoped in the current slice.
deliberate_with_codex, deliberate_with_claude, and /contextrelay:deliberate
bound an exchange and record the synthesis. They do not validate truth -
their output is advisory, and so is anything a read-only backup agent reports
(read-only is enforced by prompt, command choice, and workflow, not by an OS
sandbox).
Residual risks of act:write
Live testing of the contained writable worker surfaced two real limitations the
automated suites do not yet catch. Operators who enable act:write should know them:
- Diff capture can miss brand-new / untracked files. Capture is built on the
worktree diff, which can overlook files that are entirely new and untracked.
Treat the captured
idle_write_resultdiff as a starting point, not a guarantee of completeness. - Codex worker spend may not be parsed into the ledger. When a worker's cost
is not reported back, accounting falls back to the reserved estimate, so spend
tracking is best-effort. Watch the daily cap (
CONTEXTRELAY_WRITE_DAILY_CAP_USD) rather than assuming per-task costs are always recorded precisely.
Because spend accounting is best-effort and the gate is the real backstop, keep
CONTEXTRELAY_WRITE_DAILY_CAP_USD conservative. If the spend ledger becomes
unreadable, the writable worker fails closed until the daemon restarts - that is
the intended behavior, not a malfunction.
The human-authority boundary as a control
The strongest safety property is not a token or a sandbox - it is the rule that certain actions require a human, and that agents must escalate rather than act. Agents should not, on their own authority:
- Spend beyond the configured opt-in budgets.
- Publish, release, or take outward/destructive actions.
- Kill or restart daemons.
- Change the coordinator or the git-write policy.
These are reserved for a human (or, where policy and runtime permissions allow, the configured coordinator). Treat scanner suggestions and worker outputs as evidence to weigh, not instructions to obey.
How failures surface
ContextRelay's bridge and daemon detect common failure modes and latch a disabled state until things are healthy again, recovering only when the same instance and state directory check out.
| Symptom | What it usually means | First move |
|---|---|---|
| "daemon disconnected" | Often the turn watchdog (CONTEXTRELAY_TURN_MAX_MS, default 300000 ms) resetting a long turn - not a crash. | Check ctxrelay status before restarting; bump the watchdog for genuinely long turns. |
| Version skew | Bridge and daemon are on mismatched bundles. | The bridge latches disabled and recovers when versions match; reconcile with ctxrelay upgrade. |
| Stale session / lock / PID | A process or lock survived an abnormal shutdown. | Startup and kill paths probe before adopting or signaling; see the runbook if auto-recovery fails. |
| Foreign daemon collision | A port belongs to another project. | Identity checks reject reuse; stop the other project or use a different port group. |
| Token leak | A token file was read by something it shouldn't be. | Stop the daemon, delete the leaked token file, restart, and treat the old token as compromised. |
| Prompt injection via transcript | Hostile content in ledger entries, files, or messages. | Treat all agent-visible transcript as untrusted input when it is fed into prompts. |
A "daemon disconnected" message is the most-commonly-misread symptom. Inspect
state with ctxrelay status first - a watchdog reset of a long turn looks
identical to a crash but needs a different fix. See
Troubleshooting and recovery.
Where the authoritative detail lives
This page is a map. The full territory is in two documents shipped with the source tree:
docs/THREAT_MODEL.md- trust boundaries, the full controls table, non-controls,
act:writecontainment, and failure modes.
- trust boundaries, the full controls table, non-controls,
docs/CONTEXTRELAY_V1.md- runtime adapter model, permission policy, write-action boundaries, and security limits.
Next steps
- Read-only by default: safety and containment - the safety posture this threat model implements.
- Architecture overview - how the daemon, bridge, ledger, and adapters fit together.
- Enabling act:write (autonomous edits) safely - turn on contained writes the right way, with the residual risks above in mind.
- Coordinator and git-write policy - the single-git-owner convention and how to configure it.
- Environment variables reference - every
CONTEXTRELAY_*knob named on this page.