auth: survive a request that carries no origin
Signing in from a server-to-server client returned 500. originMiddleware leaves `origin` undefined when a request has neither Origin nor Referer — exactly what such a client sends — and signin.ts took it as a string, passed it into getPasskeysByUserIdAndOrigin, and Postgres rejected the undefined parameter. It would have thrown on origin.startsWith() a few lines later too. It used to be unreachable: originValidationMiddleware rejected origin-less requests before the handler ran, so the value was always a string by the time anything touched it. Turning the origin checks off removed that gate without the code behind it ever having needed to cope. This is the second thing that flag has surfaced rather than caused. The two call sites want different answers, so they get different ones: - signin normalises to ''. No passkey is registered against the empty origin, so an origin-less caller gets an empty list and falls through to password auth, which is what it is asking for. - the four passkey routes now refuse with 400 "Passkey operations require an Origin header". WebAuthn is defined in terms of an origin — a passkey is registered against one and is only verifiable against the same one — so substituting '' there would be quietly wrong. Also flips ALLOW_ANY_ORIGIN to default ON: the checks are off unless it is explicitly 'false'. A deliberate inversion of fail-closed, safe because of where this runs — the perimeter is the tailnet, devices are admitted by hand, and every protected route still requires a valid token. Verified both directions: unset lets a foreign origin through, ALLOW_ANY_ORIGIN=false rejects it. The origin test suite pins the flag off, so it still asserts the checks reject things rather than passing vacuously. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -97,7 +97,11 @@ if (PUBLIC_ORIGIN) ORIGIN_RULES[PUBLIC_ORIGIN] = { superAdminOnly: true };
|
||||
// What is lost: defence in depth, not the lock. Origin was never authentication here — `officer://<hex>`
|
||||
// is chosen by the client, forgeable outside a browser, and extractable from a shipped app binary.
|
||||
// Both flags and their call sites come out once the tailnet is the perimeter.
|
||||
const ALLOW_ANY_ORIGIN = process.env.ALLOW_ANY_ORIGIN === 'true';
|
||||
// Defaults to ON — origin checking is off unless ALLOW_ANY_ORIGIN is explicitly 'false'. That is a
|
||||
// deliberate inversion of the usual fail-closed rule, and it is safe only because of where this runs: the
|
||||
// perimeter is the tailnet, devices are admitted by hand, and a valid token is still required on every
|
||||
// protected route. Set ALLOW_ANY_ORIGIN=false to put the checks back.
|
||||
const ALLOW_ANY_ORIGIN = (process.env.ALLOW_ANY_ORIGIN ?? 'true') !== 'false';
|
||||
const ALLOW_ANY_ORIGIN_MUSIC = process.env.ALLOW_ANY_ORIGIN_MUSIC === 'true';
|
||||
|
||||
export function isOriginCheckDisabled(path?: string): boolean {
|
||||
|
||||
Reference in New Issue
Block a user