docs/security.md.
Trust tiers
Continuum has six trust tiers.| Tier | Component | Trust | Notes |
|---|---|---|---|
| 1 | Local Mac daemon | Trusted root | Holds Keychain entries, spawns provider CLIs, owns the in-process HTTP/WebSocket daemon (AgentControlServer). Compromising the daemon compromises everything; no other tier has equivalent privilege. |
| 2 | Paired iPhone / Apple Watch | Trusted peer | Pairing uses QR + bearer token + per-pairing ECDH. The iPhone derives the same symmetric key as the Mac and can decrypt every relay frame and every APNS notification body. |
| 3 | Cloudflare relay Worker | Untrusted | Sees opaque XChaCha20-Poly1305 envelopes only. Never holds the session key. Audit log records sender role, envelope type, and byte length — never body content. |
| 4 | Cloudflare APNS gateway Worker | Untrusted for payload | Holds the operator’s APNS .p8 signing key. Forwards sealed payloads to Apple. Cannot decrypt the body; only the paired iPhone can. |
| 5 | Provider CLIs | Sandboxed children | Each CLI (claude, codex, opencode, cursor-agent, agy, grok) runs as a child process, owns its own auth state, and handles its own network egress. Continuum does not proxy or inspect their traffic. |
| 6 | Sparkle appcast + GitHub Releases | Authenticated release channel | Appcast served from GitHub Pages. Sparkle verifies EdDSA update signatures. The release script gates Developer ID signing, notarization, and asset verification before publishing. |
Cryptographic primitives
- Key exchange. X25519 ECDH between Mac and iPhone. Fresh ephemeral keypairs per pairing; private keys live in process memory and are zeroized on session close or 15-minute TTL expiry.
- Key derivation. HKDF-SHA256 with
salt = sessionIdand separateinfostrings for the relay channel ("clawdmeter.relay.v1") and the APNS payload channel ("clawdmeter.apns.v1"). Differentinfostrings derive sibling keys so a compromise in one channel does not affect the other. - Authenticated encryption. XChaCha20-Poly1305 AEAD with random 24-byte nonces. Forward secrecy by construction — nothing on disk can decrypt prior captured ciphertext.
- Replay protection. Monotonically increasing
seqcounters per direction. The wire-protocol version byte is bound into the HKDFinfostring so a version flip derives a different key and AEAD fails closed.
Per-peer bearer auth
The QR code issues two 256-bit bearer tokens:macTok for the Mac and iosTok for the iPhone. The relay Worker stores only their SHA-256 hashes. On every WebSocket open, the Worker constant-time-compares the presented bearer against both hashes and assigns a peer role. Cross-side reuse (presenting iosTok on the Mac side, or replaying a token from a different session) is rejected before any frame is forwarded.
The role check repeats on every envelope. If header.from does not match the role assigned at connect time, the socket is closed.
The QR TTL is 15 minutes. Stale codes expire automatically.
Tailscale Whois verification
Non-loopback connections to the daemon from the Tailscale network are additionally verified via Tailscale Whois (60-second cache). Whois is fail-closed: if verification is unavailable, the connection is rejected.Audit logs
The daemon writes audit entries for sends, model swaps, autopilot changes, and mobile command receipts to~/.clawdmeter/audit/. Entries record metadata — timestamps, session IDs, operation type, hashed identifiers — not content. Prompts, model responses, code, and repo paths are never written to audit logs.
The APNS gateway audit log records push attempt outcomes, hashed device tokens, and byte sizes — never the notification body. Relay audit records sender role, envelope type, and byte count — never body bytes. Retention is 90 days by default (KV TTL).
Kill-switch and rate limits
The APNS gateway has aAPNS_DISABLED kill-switch that runs before auth or rate-limit checks. Toggling it stops all push delivery within approximately 30 seconds across all Cloudflare edges. A per-device hourly push cap (default 60 pushes per device per hour) limits damage from a compromised operator .p8 key.
The daemon enforces a 1-send-per-second and 1-model-swap-per-5-seconds rate limit per session.
F3 HOME isolation
The type-level seam for per-provider HOME isolation (runningclaude_personal and claude_work as separate instances with isolated credentials) is in the codebase as ProviderInstanceId.homePathOverride. The runtime enforcement — env scrubbing on child spawn and Keychain partitioning — is not yet wired. Until F3-wire ships, all instances share the same Keychain access group and home directory per provider.
ACP file-system trust boundary
When Continuum drives Cursor or Grok over ACP, the agent can request file reads and writes. Continuum only advertises these capabilities for repos on the autopilot trust list. Every request is validated throughRepoTrustGate: the path is canonicalized, symlinks resolved, and the canonical result must sit at or under the repo root. The implementation is TOCTOU-aware: the gate returns the resolved path and all I/O uses that resolved path.
Update chain
Mac updates go through Sparkle 2.9.2. The release pipeline gates on:- Developer ID Application code signing
- Apple notarization and stapling
- Sparkle EdDSA signature for the update DMG
- Appcast hosted on GitHub Pages at
https://darshanbathija.github.io/Continuum/updates/appcast.xml
Threat model reference
The full 14-scenario threat model — including relay operator curiosity, leaked QR, MITM, replay, nonce reuse, lost device, forged APNS, DoS, operator Cloudflare account compromise, iOS WS suspension, enterprise proxy MITM, timing side-channels, and protocol downgrade — is documented indocs/security.md.
Threat #10 (operator Cloudflare account compromise) is the irreducible trust root and explicitly accepted. Mitigations are account-hygiene only; there is no in-product defense against a rogue operator.
Reporting a vulnerability
Email the maintainers directly rather than opening a public GitHub issue. Contacts are listed inCONTRIBUTING.md and infra/apns-gateway/ROTATION.md.