From f079714cafb4f2307a1fac8d9c6105a005cea58f Mon Sep 17 00:00:00 2001 From: Owen Date: Tue, 4 Aug 2026 10:07:52 -0400 Subject: [PATCH] Dont redirect when the browser agent is not real --- server/routers/badger/verifySession.ts | 54 ++++++++++++++++++++++++-- 1 file changed, 51 insertions(+), 3 deletions(-) diff --git a/server/routers/badger/verifySession.ts b/server/routers/badger/verifySession.ts index 33c75b101..99e0d9812 100644 --- a/server/routers/badger/verifySession.ts +++ b/server/routers/badger/verifySession.ts @@ -127,6 +127,9 @@ export async function verifyResourceSession( // Extract HTTP Basic Auth credentials if present const clientHeaderAuth = extractBasicAuth(headers); + const clientUserAgent = headers?.["user-agent"] || headers?.["User-Agent"]; + const clientIsBrowser = isBrowserUserAgent(clientUserAgent); + const clientIp = requestIp ? stripPortFromHost(requestIp, badgerVersion) : undefined; @@ -313,9 +316,14 @@ export async function verifyResourceSession( return allowed(res, undefined, dontStripSession); } - const redirectPath = `/auth/resource/${encodeURIComponent( - resource.resourceGuid - )}?redirect=${encodeURIComponent(originalRequestURL)}`; + // Only offer a browser redirect to clients that can actually follow one and log in + // (an interactive browser). Non-browser clients (curl, scripts, bots, etc.) just get + // an unauthorized response from Badger instead of a login redirect URL. + const redirectPath = clientIsBrowser + ? `/auth/resource/${encodeURIComponent( + resource.resourceGuid + )}?redirect=${encodeURIComponent(originalRequestURL)}` + : undefined; // check for access token in headers if ( @@ -1476,6 +1484,46 @@ async function getCountryCodeFromIp(ip: string): Promise { return cachedCountryCode; } +// Permissive by default: only reject known non-browser clients or a missing +// User-Agent (real browsers always send one). This avoids blocking real +// browsers whose UA string doesn't match a hardcoded allow-list. +const NON_BROWSER_USER_AGENT_PATTERNS = [ + /curl/, + /wget/, + /python-requests/, + /python-urllib/, + /go-http-client/, + /okhttp/, + /axios/, + /node-fetch/, + /postmanruntime/, + /insomnia/, + /libwww-perl/, + /java\//, + /ruby/, + /php/, + /bot/, + /spider/, + /crawler/, + /headlesschrome/, + /phantomjs/, + /httpclient/, + /prometheus/, + /go-resty/, + /apache-httpclient/, + /scrapy/ +]; + +function isBrowserUserAgent(userAgent: string | undefined): boolean { + if (!userAgent) { + return false; + } + + const ua = userAgent.toLowerCase(); + + return !NON_BROWSER_USER_AGENT_PATTERNS.some((pattern) => pattern.test(ua)); +} + function extractBasicAuth( headers: Record | undefined ): string | undefined {