remove origin validation
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>
This commit is contained in:
@@ -72,7 +72,7 @@ src/
|
||||
│ └── landing/ # marketing landing page
|
||||
├── servers/
|
||||
│ ├── hono.ts # router composition; everything under /api
|
||||
│ ├── _middlewares/ # auth, body parsing, origin validation, rate limiting
|
||||
│ ├── _middlewares/ # auth, body parsing, the capability gate, rate limiting
|
||||
│ ├── api/<feature>/ # one folder per feature, each exporting a router
|
||||
│ ├── channels/ # send-claude-code / send-opencode — how /chat drives an agent turn
|
||||
│ ├── queue/ # background job engine
|
||||
@@ -142,14 +142,14 @@ exceed Postgres's 63-character identifier limit: name it explicitly. See `src/da
|
||||
## Security Model
|
||||
|
||||
- `IS_DEV_BUILD` (`src/servers/build-env.ts`) is true **only** when `PUBLIC_BUILD_ENV` is explicitly
|
||||
`dev`/`development`. Everything else, including unset, is hardened. Origin validation, rate
|
||||
limiting and password rules all key off it — they fail closed.
|
||||
- Allowed origins come from `PUBLIC_URL`. Officer always sits behind an HTTPS reverse proxy, so the
|
||||
forwarded `Host` must equal `PUBLIC_URL`'s authority exactly.
|
||||
- **`ALLOW_ANY_ORIGIN` defaults to ON** — origin checking is off unless the var is explicitly `false`.
|
||||
A deliberate inversion of the usual rule, safe only because the perimeter is the tailnet and a valid
|
||||
token is still required on every protected route. It is defence in depth that is currently switched
|
||||
off, not the lock.
|
||||
`dev`/`development`. Everything else, including unset, is hardened. Rate limiting and password
|
||||
rules key off it — they fail closed.
|
||||
- **There is no origin checking.** It was removed on 2026-08-13, along with `ALLOW_ANY_ORIGIN` and
|
||||
`ALLOW_ANY_ORIGIN_MUSIC`. The flag defaulted to ON, so origin validation ran on no real install —
|
||||
what came out was documented defence in depth that was already switched off. Origin was never
|
||||
authentication here anyway: an app's `officer://<hex>` origin is chosen by the client, forgeable
|
||||
outside a browser, and extractable from a shipped binary. The perimeter is the tailnet, and the lock
|
||||
is a valid token on every protected route plus the capability gate below.
|
||||
- JWTs are 30-day, blacklisted on signout, and invalidated by a password change (`passwordChangedAt`).
|
||||
**The role is deliberately not a claim** — every authorization decision re-reads `users.role` from
|
||||
Postgres, so a grant or a revoke takes effect on the next request rather than at next sign-in.
|
||||
@@ -159,7 +159,7 @@ exceed Postgres's 63-character identifier limit: name it explicitly. See `src/da
|
||||
### Capabilities — read this before mounting a router
|
||||
|
||||
Authorization is one system, and it is not in `userMiddleware` (which only answers "is this token
|
||||
valid"). It is `originScopeMiddleware` → `capabilities/authorize.ts`, mounted globally in `hono.ts`
|
||||
valid"). It is `_middlewares/capability-gate.ts` → `capabilities/authorize.ts`, mounted globally in `hono.ts`
|
||||
ahead of everything, and it re-verifies the token itself so it covers routes that never mount
|
||||
`userMiddleware`.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user