Skip to main content

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.

ControlWhat it does
Loopback bindDaemon, Codex proxy, and viewer listen only on 127.0.0.1, preventing remote ingress.
Split tokens + lockoutSeparate control / proxy / viewer tokens, constant-time comparison, and an auth lockout that slows repeated bad attempts.
Daemon + project identityA daemon is bound to its expected state directory and refuses reuse across projects or state dirs.
Owner-only file modesState dir 0700, secret files 0600, where the OS supports it.
Frame validation + rate limitsMalformed control frames are rejected; control messages are size- and rate-capped.
Coordinator git-write policyA single agent (collaboration.coordinator) owns git writes; non-coordinators are expected to hand off.
Permission mediationEight 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 autonomyBackup agents are OFF until you run ctxrelay autonomy on; even then they are read-only.
Fail-closed act:writeAutonomous edits are OFF by default behind a layered, fail-closed gate chain (detailed below).
Log redactionCommon 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 - default 1000000 (max accepted message size).
  • CONTEXTRELAY_MAX_CONTROL_MESSAGES_PER_MINUTE - default 120 (per-connection rate limit).
  • CONTEXTRELAY_MAX_DEPTH - default 3 (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:

  1. Autonomy is on.
  2. autonomy.writableAction.mode is "act".
  3. A positive per-task cost budget (costBudgetUsd).
  4. The hard env kill-switch CONTEXTRELAY_WRITE_MODE_ENABLED is set. No config can supply this - a config alone can never enable a write.
  5. Kind and owner allowlists match.
  6. 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. A worktreeRoot that 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 a Read,Grep,Glob,LS,Edit,Write tool allowlist that excludes Bash and 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.

tip

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.

ContextRelay does not sandbox the agents from the host

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 coordinator git-write policy is not OS-enforced

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 Workflow can bypass ContextRelay entirely

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.

The ledger and viewer history are not permanent

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.

Deliberation does not prove correctness

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_result diff 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.
caution

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.

SymptomWhat it usually meansFirst 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 skewBridge and daemon are on mismatched bundles.The bridge latches disabled and recovers when versions match; reconcile with ctxrelay upgrade.
Stale session / lock / PIDA 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 collisionA port belongs to another project.Identity checks reject reuse; stop the other project or use a different port group.
Token leakA 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 transcriptHostile content in ledger entries, files, or messages.Treat all agent-visible transcript as untrusted input when it is fed into prompts.
tip

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:write containment, and failure modes.
  • docs/CONTEXTRELAY_V1.md
    • runtime adapter model, permission policy, write-action boundaries, and security limits.

Next steps