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>
92 lines
6.5 KiB
Bash
92 lines
6.5 KiB
Bash
# What officer-setup writes. Everything below this block is optional, or is on its way out.
|
|
PORT=9000
|
|
BROWSER_RELAY_PORT=18792
|
|
POSTGRES_URL="postgres://postgres:password@localhost:5432/officer"
|
|
|
|
# ── Moving to the secret store ─────────────────────────────────────────────────────────────────
|
|
# Still REQUIRED — jwt.ts throws at module load without JWT_SECRET, and crypto.ts throws without
|
|
# VAULT_STORE_KEY — but officer-setup no longer writes either. They are moving into the SQLite key
|
|
# store (docs/secret-store.md), which is designed and not yet built, so an install made by the
|
|
# current script will not boot until it is. That is deliberate sequencing, not an oversight.
|
|
JWT_SECRET="<generate with: openssl rand -base64 32>"
|
|
|
|
# NOT Vaultwarden's, despite the name and where it used to sit — it is the platform's at-rest key,
|
|
# encrypting every secret column in Postgres: Headscale admin API keys, app-store service
|
|
# credentials, Jellyfin tokens, wallet node credentials, and the wallet seed envelope on top of the
|
|
# owner passphrase that seals it.
|
|
#
|
|
# CHANGING IT MAKES ALL OF THAT UNREADABLE AT ONCE, and for the seed that is unrecoverable: the
|
|
# passphrase opens the inner envelope and this is the outer one.
|
|
VAULT_STORE_KEY="<generate with: openssl rand -base64 32>"
|
|
|
|
# ── Optional ───────────────────────────────────────────────────────────────────────────────────
|
|
# Where Officer is reached from a browser. Read by origin validation, the task API host check, and
|
|
# the CalDAV iOS profile builder — which is the only one that hard-requires it, and demands https.
|
|
# PUBLIC_URL=https://officer.example.com
|
|
|
|
# Guards (CORS origin checks, rate limits, password-strength rules) are ON unless this is set to
|
|
# "dev" or "development". Unset is hardened, which is why officer-setup no longer writes it — set it
|
|
# by hand, on a local machine you trust, to develop. Note that `bun dev` does NOT set it: that script
|
|
# only loads this file, so `bun dev` against a production .env runs fully hardened.
|
|
# PUBLIC_BUILD_ENV=dev
|
|
|
|
# DATA_PATH, OFFICER_ITEMS_DIR and HOME_DIR were here until 2026-08-12 and are no longer read.
|
|
# The install root is derived as the parent of the working directory (src/servers/data-path.ts), so
|
|
# data/, capabilities/ and dockers/ follow from it; the owner's home comes from the OS. Three values
|
|
# that had to agree with each other and with the disk became one that cannot disagree.
|
|
|
|
# ── Sidecars ────────────────────────────────────────────────────────────────────────────────────
|
|
# Each sidecar owns its upstream's credentials; the platform API is only a thin auth+forward proxy
|
|
# and never sees them. An unset upstream URL is not fatal — the sidecar logs a warning at boot and
|
|
# answers 503 until it is set, so you can run Officer with any subset of these configured.
|
|
|
|
# Transmission (officer-transmission) is configured from the app, not from here — Transmission →
|
|
# Connection. The daemon URL, the optional RPC auth and the RPC path live in `service_connections`,
|
|
# with the password encrypted, so nothing outside the sidecar can read it.
|
|
|
|
# InvoiceShelf (officer-invoiceshelf) is configured from the app, not from here — Invoices → Connection.
|
|
# Instances, their Sanctum tokens and the company each one is pinned to live encrypted in
|
|
# `invoiceshelf_accounts`, so nothing outside the sidecar can read a token.
|
|
|
|
# slskd (officer-slskd) is configured from the app, not from here — Soulseek → Connection. The
|
|
# daemon URL and its API key live encrypted in `service_connections`; the sidecar injects the key as
|
|
# X-API-Key on every forwarded request.
|
|
|
|
# Vaultwarden (officer-vault). VAULT_STORE_KEY is at the top of this file — it is the platform's
|
|
# key, not Vaultwarden's, however much the name and its old position here suggested otherwise.
|
|
VAULTWARDEN_URL=http://127.0.0.1:8222
|
|
|
|
# The Anthropic proxy (officer-anthropic-proxy) binds PORT + 1, derived rather than configured — see
|
|
# src/servers/officer-url.mjs. There is nothing to set. It holds no credential from this file either:
|
|
# the upstream token is the OAuth one `claude` writes to ~/.claude/.credentials.json, and the
|
|
# ANTHROPIC_API_KEY the agent presents to it is the proxy's own generated secret.
|
|
|
|
# ReClip — the self-hosted yt-dlp service the download-media capability talks to. Defaults to
|
|
# http://localhost:8899.
|
|
# RECLIP_URL=http://localhost:8899
|
|
|
|
# ── Headscale (/api/vpn) ────────────────────────────────────────────────────────────────────────
|
|
# These drive the /api/vpn router, NOT the officer-headscale sidecar. The sidecar deliberately reads
|
|
# neither, keeping its registered servers and their keys in Postgres so host env can never shadow
|
|
# one. Set these only if you use /api/vpn.
|
|
# HEADSCALE_URL=https://headscale.example.com
|
|
# HEADSCALE_API_KEY="<headscale admin api key>"
|
|
# HEADSCALE_USER=officer
|
|
|
|
# ── Bitcoin wallet (officer-wallet) ─────────────────────────────────────────────────────────────
|
|
# The chain data source is NOT here — it is configured from the app, at Wallet → Settings → Chain
|
|
# source, and stored per owner. Any Esplora-compatible API works (electrs, esplora, mempool.space);
|
|
# it defaults to the public mempool.space until you set one.
|
|
# WALLET_NETWORK=bitcoin # bitcoin | testnet | signet | regtest
|
|
#
|
|
# How long an unlocked wallet stays unlocked, in seconds. Default 900 (15 min). The root key is held
|
|
# in the sidecar's memory for exactly this long after an unlock, then wiped. Shorter is safer.
|
|
# WALLET_UNLOCK_TTL_SEC=900
|
|
#
|
|
# NOTE: seed material is encrypted with VAULT_STORE_KEY (above) on top of the owner passphrase that
|
|
# seals it. Both are required to spend. If you lose VAULT_STORE_KEY, every stored seed is
|
|
# unrecoverable — back up the mnemonics separately, offline.
|
|
|
|
# Immich (officer-photos) is configured from the app, not from here — Photos → Connection. Instances and
|
|
# their API keys live encrypted in `photos_config`, so the platform never sees a key.
|