Skip to main content
This page summarizes Continuum’s trust model and security mechanisms. The normative specification — including the 14-scenario threat model — ships inside the app’s in-product documentation; the highlights are reproduced here.

Trust tiers

Continuum’s trust model spans the host that runs your agents, the clients that drive it, the relay and push paths between them, the cloud backend that holds account state, the inference gateway, the provider CLIs it spawns, and the release channels.

Cryptographic primitives

These cover the relay and push path that lets a paired device reach your host when the two are not on the same network.
  • Key exchange. X25519 ECDH between host and client (CryptoKit Curve25519.KeyAgreement). Fresh ephemeral keypairs per pairing; the relay sees only the 32-byte public keys, never the shared secret.
  • Key derivation. HKDF-SHA256 with salt = sessionId and separate info strings for the relay channel and the push payload channel. Different info strings derive sibling keys, so a compromise in one channel does not affect the other.
  • Authenticated encryption. XChaCha20-Poly1305 AEAD with random 24-byte nonces from the platform CSPRNG. The 192-bit nonce never collides in practice, so there is no birthday-bound message ceiling. The envelope header (version, sender role, type) is bound into the AEAD associated data, so a captured host→client frame cannot be reflected back as client→host — a flipped role changes the AAD and verification fails.
  • Replay protection. Per-direction sequence numbers carried inside the encrypted plaintext, validated against a per-direction window that persists across reconnects. The protocol version byte is bound into the HKDF info string, so a downgrade derives a different key and AEAD fails closed.

Per-peer bearer auth

Every daemon endpoint — HTTP and WebSocket, loopback included — requires Authorization: Bearer <token>. The daemon accepts one of two tokens:
  • A pairing token presented by paired clients.
  • A per-launch loopback token generated fresh on each app launch for the host’s own in-process client. It is never persisted and never crosses the wire.
Token comparison is constant-time. For relay frames, only SHA-256 hashes of the bearer tokens are stored and compared. Cross-side reuse — presenting a client’s token on the host side, or replaying a token from a different session — is rejected before any frame is forwarded, and the role check repeats on every envelope: if the declared sender does not match the role assigned at connect time, the socket closes. A fail-closed lockout throttles bearer brute-forcing ahead of the identity check. Because remote frames are replayed into the daemon as loopback requests, every forwarded request carries an explicit relay origin marker. Trust-granting operations require an origin that is both loopback and not relay-forwarded, so arriving over the relay can never be mistaken for sitting at the machine.

Token and code lifetimes

There is no single pairing TTL — the artifacts have deliberately different lifetimes, and the short one is the one you handle by hand. The Mac shows the enrollment code’s countdown inline: “Code expires in 10 min.”
QR-code pairing has been removed from the shipping surfaces. Devices join through email sign-in, and hosts are added with a one-line enroll command or through the Advanced options (Tailscale, SSH, cloud). See Pairing.

Network peer filter

The daemon listener accepts connections only from loopback (127.0.0.0/8, ::1) and the Tailscale CGNAT range (100.64.0.0/10 and the fd7a:115c:a1e0::/48 IPv6 prefix). Any other peer is dropped on accept, before auth runs. Bearer auth is still required afterward, so the peer filter is defense-in-depth, not the sole gate. The daemon does not terminate TLS itself; transport confidentiality on the remote path comes from the sealed relay envelopes and, on a tailnet, from WireGuard.

Tailnet identity verification

Non-loopback connections from a Tailscale network are additionally verified via Tailscale Whois (60-second cache). This is fail-closed: a peer that cannot be resolved to a tailnet identity is rejected with a 401. It applies uniformly to the local daemon and to remote execution hosts.

Multi-account isolation

Continuum supports multiple accounts per provider on one machine (Settings → Providers → Add account…). Isolation is enforced at spawn time:
  • Config-dir isolation, not a HOME swap. Each secondary account gets a deterministic config root, and at spawn the daemon points that provider’s own config variable at it — CLAUDE_CONFIG_DIR for Claude, CODEX_HOME for Codex, XDG_DATA_HOME for the OpenCode-family providers, GROK_HOME for Grok, and a scoped HOME for Antigravity’s agy, which honors nothing narrower. HOME is deliberately not overridden in the general case: a full HOME swap broke git, ssh, gh, and node resolution inside worktrees. Cursor authenticates with an API key and has no config root to isolate.
  • Env scrubbing on child spawn. Before launching a per-instance child, the daemon strips every inherited CLAUDE_*, ANTHROPIC_*, CODEX_*, OPENAI_*, GEMINI_*, GOOGLE_APPLICATION_CREDENTIALS, OPENCODE_*, OPENROUTER_*, and CURSOR_* variable, so a stray key in the parent environment cannot leak into another account’s spawn. Per-instance credentials are injected after the scrub. Non-Anthropic agents always get the Claude credential scrub even on the primary account, so a subscription token can never leak into a third-party agent.
  • Per-instance keychain partitioning. Each non-primary instance reads its token from a distinct keychain partition — a per-instance access group plus a per-instance service-name suffix. Because the access group is part of the lookup key, a query scoped to account A cannot read account B’s entry, and the double bind means even a misconfigured entitlement can’t cross partitions.
  • Fail-closed spawn resolution. A session’s pinned account is resolved before every spawn. A pin to an account the registry no longer carries, a non-isolatable instance, or an account with no stored token all refuse to spawn with a re-authenticate error — never a silent fallback to your default subscription. The defended failure mode is wrong-account billing.
Account names are validated before any path is built (no /, \, NUL, whitespace, or leading .), which rejects the .. path escape that could otherwise let account removal delete a sibling account’s data.

Execution hosts

Another Mac, a Linux box, a VPS, your own cloud account, or a Continuum-managed runner can run agent sessions on behalf of a hub.
  • How the host is provisioned. On Linux, a bundled Go continuum-agent plus a service unit; the agent generates its own bearer token, which the hub captures. On macOS, Continuum itself runs in headless-agent mode under a per-user launch agent with a provisioned token. Token files are staged into a 0700 directory and written 0600, so no secret lands in a world-readable file.
  • What the hub trusts about the host. The hub reaches the remote daemon with a provisioned bearer. The remote daemon applies the same gates as the local one: peer filter, per-peer bearer, and fail-closed tailnet identity on every non-loopback request. A remote host trusts the hub only insofar as it presents a valid bearer from a verified peer; it is not an open relay.
  • Credentials on the host. When the hub needs a provider subscription there, it pushes that credential explicitly and refreshes it as it rotates. Hosts are compute, not credential custodians — they hold only what the hub deliberately pushes.
  • Repo-controlled hooks do not run. Worktrees the Mac provisions are created with core.hooksPath pointed at /dev/null, so cloning or checking out a repository cannot execute a hook script that repository controls.
  • GitHub access for managed cloud runs is short-lived and single-repo. Access is re-checked immediately before create or wake — a suspended installation, a revoked grant, an archived repo, or missing push permission fails closed. The token is scoped to one repository, refreshed before expiry, and delivered through a credential helper over an owner-only socket, so it never lands in git config, a command line, or shell history.
Remote interactive sessions are gated by wire version; a hub paired to an older remote daemon degrades visibly rather than failing silently.

Content sealing and erasure

Once you sign in, transcript pages mirrored to the cloud are sealed at rest with AES-256-GCM under a per-member key. Removing a member destroys that key, and every page encrypted under it becomes permanently unrecoverable — erasure that holds across replicas and backups without hunting down individual rows. Tenant tables have Postgres row-level security enabled and forced, with policies keyed on the requesting organization. Forced RLS means the policy applies even to the table owner, so an application bug that forgets a predicate still cannot read across tenants.
This seal is encryption at rest, not end-to-end: Continuum holds the master key and unwraps it to render your own history back to you. Privacy states the consequence plainly, along with the append-only audit trail that constrains it. If you never sign in, no transcript is mirrored and none of this applies.

Hosted inference: gateway-side enforcement

Organization policy for hosted inference is enforced by the gateway, not by the client UI. One code path serves every hosted entry point, and it checks, in order: the global model allowlist, model pricing, subscription entitlement, the organization’s model allowlist for that member or team, and an atomic budget reservation. A blocked model returns 403 with the reason — “…is not permitted for your team by your organization admin.” An exhausted allocation returns 429, distinguishing a free-tier allowance, an organization-set member cap, and a spent weekly budget. Because the checks live behind the API rather than in front of it, an older or modified client cannot route around them. Upstream provider credentials are server-side only, sealed with AES-256-GCM under a key held outside the database and bound to their purpose by the AEAD associated data. Clients never receive more than a four-character key hint. Publishing a transcript to continuumcode.ai/share/<id> runs the redactor server-side, before storage — no client, and not the host proxy, can widen what a link exposes. The redactor is an allowlist walker: each message is rebuilt from a known key set and anything unrecognized is dropped, so a transcript field added tomorrow cannot leak by default. Raw tool-input JSON — whole file contents, full shell commands, credentials passed as flags — is never stored at all, because the share page does not render it. On top of that, known secret shapes are masked: private key blocks, Anthropic, OpenAI, GitHub, Slack, AWS, and Google keys, JWTs, database connection strings, KEY=value dotenv lines, and api_key:-style assignments, plus home directory paths, email addresses, and private IP ranges. Terminal control characters are stripped so captured output cannot smuggle escape sequences into the page. Links can carry an expiry, can be revoked, and record a view count. Unlisted-ness rests on the id’s entropy, not on obscurity of the route.

Environment variables

Environment values never leave the machine, and the wire type enforces it: the record Continuum publishes for an environment variable has no value field at all — only the key name, a fingerprint, whether a value exists, and its rotation state. Reveal is a local action against your own keychain, with no endpoint that returns a value. Agents are briefed with the key names only. Keys that could hijack a process — loader, hook, and routing variables such as LD_PRELOAD, DYLD_INSERT_LIBRARIES, NODE_OPTIONS, PATH, BASH_ENV, GIT_SSH_COMMAND, GIT_ASKPASS, and the proxy variables — are refused on import, reported rather than silently dropped, and stripped again at spawn, swap, and revive time so a legacy stored key cannot reactivate.

Audit logs

The daemon writes append-only JSONL audit entries under ~/.clawdmeter/audit/. The directory is created 0700 and re-tightened on every launch, so peer IPs and repo paths stay private on a multi-user machine; files are created 0600. Current streams: sends.jsonl, swaps.jsonl, autopilot.jsonl, auto-router.jsonl, mobile-commands.jsonl, cowork.jsonl, creds.jsonl, and sidecar-ask.jsonl. Entries record metadata — timestamps, session ids, operation type, hashed prompt fingerprints, byte counts — not content. Files rotate at 1 MB or 7 days, whichever comes first. Prompt text is hashed by default, and there is no UI that turns that off. Recording plaintext is a deliberate defaults-key opt-in (clawdmeter.audit.includePlaintext, default false) intended for debugging your own machine; the same key gates whether the wire inspector retains request and response bodies. The push gateway’s log records attempt outcomes, hashed device tokens, and byte sizes — never the notification body. The relay’s records sender role, envelope type, and byte count — never body bytes. Retention there is 90 days by default.

Kill-switch and rate limits

The push gateway has an APNS_DISABLED kill-switch that short-circuits the request handler before schema validation, auth, or rate-limit checks — toggling it stops all push delivery within about 30 seconds. A per-sender hourly cap (default 60 pushes per hour, bound to the verified pairing identity rather than the supplied device token) limits damage from a compromised signing key. The daemon enforces 1 send per second and 1 swap per 5 seconds per session; model, effort, and mode swaps share the swap budget. Exceeding either returns a 429.

ACP file-system trust boundary

When Continuum drives Codex, Cursor, Grok, or Gemini over the ACP harness, the agent can request file reads and writes. Every request is validated through a repo trust gate: the path is canonicalized, symlinks resolved and .. collapsed, and the canonical result must sit at or under the repo root — defeating traversal, absolute-path escape, and symlink escape. The gate is TOCTOU-aware: it returns the resolved canonical path and the caller opens that exact path, never re-resolving the attacker-supplied string. Terminal and read output returned to the agent is capped as an anti-DoS measure.

Release channels

Two release gates worth naming, because they fail the build rather than shipping something weaker:
  • A tagged Windows release cannot publish unsigned. The pipeline refuses to proceed without a signing certificate, and after packaging it runs Authenticode verification on every executable, failing the release if any file does not verify. (Untagged development artifacts are explicitly built unsigned and say so.)
  • Dependencies cannot resolve without a lockfile. A missing lockfile aborts the build rather than silently resolving fresh versions.
The CLI archives are published unsigned by design — there is no cross-platform certificate — and say so in their manifest, backed by GitHub build provenance.
Unsigned Windows binaries can still be blocked by Smart App Control or WDAC, and a newly signed binary needs SmartScreen reputation to accumulate. If Windows warns on first launch, see Troubleshooting.

Threat model reference

The full 14-scenario threat model — including relay operator curiosity, leaked pairing material, MITM, replay, nonce reuse, lost device, forged push, DoS, operator account compromise, client WebSocket suspension, enterprise proxy MITM, timing side-channels, and protocol or transport downgrade — ships inside the app’s in-product documentation. The operator infrastructure compromise scenario is the irreducible trust root and is explicitly accepted. Mitigations are account-hygiene only; there is no in-product defense against a rogue relay or push operator. This is why those paths carry no plaintext — the operator sees only opaque envelopes either way. The cloud backend is a different case: it holds sealed content and the key to unseal it, which is why that tier is disclosed separately in Privacy.

Reporting a vulnerability

If you find a security issue, email the maintainers directly rather than filing it in public. Continuum is published by Montauk Analytics Inc; reach the team via continuumcode.ai.