Files
zpan/server/lib/local-origin.ts
T
saltboandClaude Fable 5 e56d3dc61a feat(server): auto-trust loopback and LAN origins without TRUSTED_ORIGINS
Sign-in via 127.0.0.1 or a LAN IP failed with "Invalid origin" unless the
user manually configured TRUSTED_ORIGINS. better-auth's trustedOrigins now
uses the function form: origins on localhost, 127.0.0.0/8, ::1, or RFC 1918
private ranges are trusted per request. Browsers set the Origin header
themselves, so a private address in it proves the page was served from the
user's own machine or LAN — safe to trust for CSRF purposes.

Also set advanced.disableOriginCheck: false explicitly: better-auth
silently disables the origin check under NODE_ENV=test, so no test ever
exercised real CSRF behavior. Test helpers now send an Origin header on
cookie-bearing requests, like real browsers do.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 01:18:35 -04:00

33 lines
1.2 KiB
TypeScript

/**
* Returns true when the origin points at localhost, the loopback interface,
* or an RFC 1918 private network address.
*
* Why trusting these is safe for CSRF purposes: browsers set the Origin
* header themselves and a public website cannot forge it. An Origin on a
* loopback or private address therefore proves the page was served from the
* user's own machine or LAN — the self-hosted scenario where requiring a
* manual TRUSTED_ORIGINS entry is pure friction.
*/
export function isLocalNetworkOrigin(origin: string): boolean {
let url: URL
try {
url = new URL(origin)
} catch {
return false
}
if (url.protocol !== 'http:' && url.protocol !== 'https:') return false
const host = url.hostname
if (host === 'localhost' || host.endsWith('.localhost')) return true
if (host === '[::1]') return true
return isPrivateIpv4(host)
}
function isPrivateIpv4(host: string): boolean {
if (!/^\d{1,3}(\.\d{1,3}){3}$/.test(host)) return false
const [a, b] = host.split('.').map(Number)
if (a > 255 || b > 255) return false
if (a === 127 || a === 10) return true
if (a === 192 && b === 168) return true
return a === 172 && b >= 16 && b <= 31
}