Security
Private by design, fail-closed by default
Open the session, not a secret link. View can read sensitive work; Control can steer it. The gateway treats both as bearer capabilities and fetches one only for an authorized launch.
Stock OMP >= 18.1.20 supplies the native collaboration controller and local registry.
No fork, custom OMP build, or gateway-specific OMP plugin is required. The gateway is still a
separate installation, and native integration does not relax the boundaries below.
- Loopback-only listeners — the production HTTP server binds only to
127.0.0.1and optionally::1; Tailscale Serve is the only supported remote path and Funnel is never enabled. - Allowlisted tailnet identity — production requires a verified
Tailscale-User-Loginthat matches an exact allowlist, and identity headers are believed only while Tailscale's tunnel device is present. A userspace-networking client makes every peer look local, so the daemon returns403rather than trusting the header. - No gateway capability store — View and Control links exist only in live OMP memory, a transient authenticated query and
no-storelaunch response, and volatile client JavaScript. The gateway never stores or caches them, even in its memory-only registry. Never in files, logs, diagnostics, URLs, cookies, push payloads, browser storage, or service-worker caches. - Metadata-only APIs — session-list and SSE responses carry bounded metadata; a capability is fetched only after an explicit tap and only for the expected generation, so stale cards fail instead of receiving a newer secret.
- OMP-owned discovery — the gateway reads private discovery files and queries each OMP-owned endpoint with its per-host token. Snapshots contain metadata only; a link query resolves one exact generation and role at launch. The gateway never writes or removes OMP's discovery files. Its own readiness token is separate and never sent to OMP.
- Generation-bound launch — OMP owns host replacement and revocation; stale cards cannot exchange an old generation for a newer capability. The gateway polls every 10 seconds by default. Missing discovery or definitive endpoint failure removes the card; transient query failures retain metadata only until the 35-second TTL expires.
- Hardened browser surface — exact
Originvalidation on state-changing requests, no wildcard CORS, strict CSP,Referrer-Policy: no-referrer, and a service worker that caches only immutable shell assets. - No gateway transcript storage — the directory never stores or renders transcripts; opening a session mounts the pinned OMP client, which connects directly through the encrypted relay. The relay still observes connection metadata, timing, and ciphertext sizes. OMP and the chosen model provider retain their own content-handling policies.
- Notification detail is a choice — explicit opt-in enables capability-free Push v2 alerts. Private has no body; Session (the default) includes bounded session/project labels. Preview currently falls back to Session. Visible text may persist in notification history, screenshots, or wearables; background delivery is best effort and not in the qualified core matrix.
Out of scope in v1: mutually untrusted local accounts on a shared shell host, Portal Tunnel, and self-hosted or proxied relays. Every release runs distinctive-synthetic-secret leak scans across logs, storage, caches, URLs, and CI artifacts before it is qualified.
The full threat model, boundaries, and acceptance gates: docs/SECURITY.md · report a vulnerability privately via the security policy.
Device lock and prompt tailnet revocation matter: a still-authorized unlocked phone can request Control. Passkey verification is proposed, not implemented. The accepted protection against upgrades interrupting a pending launch also has an unresolved implementation gap; see ADR-018.
Each stable release's core qualification names its exact OMP, Bun, Debian, macOS, and Pixel builds and a 1,800-second relay check, not eight hours; the current release's receipts and exclusions are on the status page. Testing more platforms and browsers in CI does not change this security boundary.