ALLOW_ANY_ORIGIN, ALLOW_ANY_ORIGIN_MUSIC, and everything they gated. The flag
defaulted to ON, so none of it ran on a real install — what comes out is
documented defence in depth that was already switched off. The file said so
itself: "Both flags and their call sites come out once the tailnet is the
perimeter."
Origin was never authentication here in any case. An app's `officer://<hex>`
origin is chosen by the client, forgeable outside a browser, and extractable from
a shipped binary.
Gone: the two flags, isOriginAllowed, isOriginCheckDisabled, isMusicOriginExempt,
originValidationMiddleware, ORIGIN_RULES and the whole OFFICER_<APP>_ORIGIN
scheme, PUBLIC_URL's origin/host derivation, and origin-validation.test.ts, which
existed only to pin them. CORS now echoes whatever Origin it is given, which is
what every install already did.
What SURVIVES is the reason this needed care. origin-validation.ts held two
unrelated things, and the second was the global authorization gate — a valid
non-owner token reaches only what its role grants, deliberately NOT under the
flag because it is account-based rather than origin-based. Its own comment called
it "the airtight half". Deleting the file wholesale would have deleted
authorization.
So it moves to _middlewares/capability-gate.ts as capabilityGateMiddleware, with
the name matching what it does: nothing in it reads an Origin header any more.
hono.ts mounts it in the same position, ahead of every router.
origin-middleware.ts stays and is untouched — it extracts the Origin for six auth
handlers that log it, and for passkeys. Extraction, not validation.
Also updates every claim that rested on the old model: CLAUDE.md's security
section and repo map, docs/secret-store.md, docs/mobile-api-keys.md, and five
messages in machine-setup's Tailscale section which told the owner to set
ALLOW_ANY_ORIGIN=false when declining a tailnet. That advice is now impossible to
follow, and the honest version is different: with no tailnet the token is the
whole lock, so put a proxy in front and restrict who can reach it.
Not typechecked (empty node_modules, frozen installs). Every changed file parses;
the setup section was run and writes four variables now.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
there were four doors, not two. the ws upgrade in server.tsx and the
vault notifications socket each verified the jwt themselves, so a key
that worked against /api would have 401'd on cliamp — signed in and can
play audio would have been two different questions for the music app.
both now call resolveAuthToken. verified: owner key upgrades cliamp
(101), bogus key 401, member key 403 on terminal exactly as their jwt
is.
reset-password and verify-token deliberately keep verify() — they read
a purpose-scoped reset token and a key must not be spendable as one.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- token-store: shared "give me a valid Vaultwarden access token" (proactive
refresh) used by both the HTTP proxy and the WS; router refactored onto it.
- notifications WS: validates the platform session in `open` (deferred, owner
only), injects the stored Vaultwarden token into the upstream, and buffers
client frames during the async setup so the SignalR handshake isn't dropped.
The device connects with its platform JWT (?access_token=), never a vault one.
- lifecycle: logout drops the vault token set (keeps the protector); distress
(/auth/revoke) and panic wipe both token set and protector, forcing a
one-time master-password re-setup.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Transparent pass-through fronting a self-hosted Vaultwarden so the OffVault
(Bitwarden-SDK) app reaches it through the platform's per-app origin gate. True
out-of-process sidecar (officer-vault): it owns all Vaultwarden knowledge (URL,
paths, notifications WebSocket) on a random loopback port and registers via the
sidecar connector; the platform is a thin origin-gated forwarder that knows only
the sidecar's port. Never decrypts/parses/rewrites/logs bodies.
- sidecar/vault: HTTP + notifications-WS proxy to VAULTWARDEN_URL, /_health
- api/vault: sidecar-port discovery + thin forwarder + WS pipe + origin gate
- origin: OFFICER_VAULT_ORIGIN allow-listed, scoped to /api/vault
- mounted top-level (not protected) so the Bitwarden bearer token isn't 401'd
- protocol: vault:server event; ecosystem: officer-vault app
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>