[BUG] OIDC and logout flows unconditionally trust unvalidated X-Forwarded-Host/Proto headers in getPublicOrigin, causing Host header injection, open redirects, and 500 URL parse errors.
Maintainers usually reply within 1 day
Nobody has claimed this yet.
Assessment
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Newbie friendliness
- 58/100
- Issue type
- Bug
- Clarity
- Clearly specified
- Activity status
- Active
- Tech stack
- next.js, typescript
- Domain
- authentication, backend, security
Research direction
Start by reading src/lib/oidc.ts around getPublicOrigin, then compare how src/lib/api/request-scheme.ts and src/lib/api/origin-check.ts gate forwarded headers. Trace the logout and OIDC callback redirects in the routes named in the issue, and check existing tests for origin handling. Done means untrusted forwarded headers cannot control auth redirect origins and comma-separated headers do not cause a 500.
Written by the indexing model from the issue text.
Description
Description
In src/lib/oidc.ts, getPublicOrigin(request) derives the application's public origin by reading the x-forwarded-host and x-forwarded-proto request headers directly:
export function getPublicOrigin(request: Request): string {
const forwardedHost = request.headers.get("x-forwarded-host");
const forwardedProto = request.headers.get("x-forwarded-proto") || "https";
const host = forwardedHost || request.headers.get("host");
if (host) {
return `${forwardedProto}://${host}`;
}
return new URL(request.url).origin;
}
This implementation contains three critical vulnerabilities:
- Bypasses Proxy Trust Decision: Across the rest of the application (
src/lib/api/request-scheme.ts#L8-L19,src/lib/api/origin-check.ts#L72-L82, andsrc/lib/api/client-address.ts#L36),x-forwarded-hostandx-forwarded-protoare explicitly treated as untrusted and gated behindreadTrustProxyHeaders(). Even when an operator setsTRUST_PROXY_HEADERS=falseto indicate LibreDB Studio is exposed directly without an intermediate reverse proxy,getPublicOrigin()ignores this setting and blindly trusts client-supplied headers. - Open Redirect & Host Header Injection in Auth Flows: Because
GET /api/auth/logout,GET /api/auth/oidc/login, andGET /api/auth/oidc/callbackare public GET endpoints, they are exempt from state-changing CSRF origin checks (STATE_CHANGINGinorigin-check.tsonly gates POST/PUT/PATCH/DELETE).- When a user logs out via
src/app/api/auth/logout/route.ts#L89-L102, the route issuesNextResponse.redirect(${origin}${withBasePath("/login")}). A malicious link carryingX-Forwarded-Host: attacker.comcauses an unvalidated redirect tohttps://attacker.com/login(phishing vector). - In
src/app/api/auth/oidc/callback/route.ts#L244, after completing OIDC authentication, the application executesNextResponse.redirect(${origin}${withBasePath(landing)}), redirecting the newly authenticated user to the attacker's domain.
- When a user logs out via
- Unhandled 500 Denial-of-Service via Multi-Host Commas: When LibreDB Studio is behind standard reverse proxies (or when an attacker injects multiple headers),
X-Forwarded-Hostmay contain comma-separated entries (e.g.,X-Forwarded-Host: client-host.com, proxy-host.com). Whileorigin-check.tsandrequest-scheme.tsparse this with.split(",")[0].trim(),getPublicOrigin()takes the whole string raw. This produces an invalid URLhttps://client-host.com, proxy-host.com/login, causingnew URL(callbackUrl, origin)orNextResponse.redirect()to throw an uncaughtTypeError: Invalid URL(HTTP 500), breaking login and logout for all users.
Steps to Reproduce
- Configure LibreDB Studio with OIDC enabled (
NEXT_PUBLIC_AUTH_PROVIDER=oidcor enable SSO). - Set
TRUST_PROXY_HEADERS=falsein environment variables. - Send a logout request supplying an external
X-Forwarded-Hostheader:curl -i "http://localhost:3000/api/auth/logout" \ -H "X-Forwarded-Host: attacker-controlled-domain.com" \ -H "X-Forwarded-Proto: https" - Observe the
Locationresponse header:HTTP/1.1 307 Temporary Redirect Location: https://attacker-controlled-domain.com/login - Next, send a request with standard comma-separated forwarded headers:
curl -i "http://localhost:3000/api/auth/logout" \ -H "X-Forwarded-Host: internal-proxy.com, edge-proxy.com" - The request crashes with an unhandled 500 error due to
TypeError: Invalid URL.
Expected Behavior
- When
TRUST_PROXY_HEADERS=false,X-Forwarded-HostandX-Forwarded-Protomust be completely ignored; the origin must be constructed only from the validatedHostheader orrequest.url. X-Forwarded-Hostmust be split on,to read only the first proxy hop and validated againstALLOWED_ORIGINSor host formats.- Auth redirects (
/api/auth/logout,/api/auth/oidc/callback) must never redirect to unvalidated external domains derived from client headers.
Actual Behavior
getPublicOrigin()unconditionally trustsX-Forwarded-Hostregardless ofTRUST_PROXY_HEADERS.- The user is redirected to the attacker-controlled origin.
- Multi-value
X-Forwarded-Hostheaders throw aTypeError: Invalid URLand crash the route with HTTP 500.
Environment
- LibreDB Studio Version: Current
main/master(commita6cdee1+) - Browser: All
- OS: macOS / Linux (Docker, Helm, Bare Metal)
- Node.js/Bun Version: Bun 1.4.x / Node.js 22+
Additional Context
Affected files:
src/lib/oidc.tssrc/app/api/auth/logout/route.tssrc/app/api/auth/oidc/login/route.tssrc/app/api/auth/oidc/callback/route.ts
Compare with src/lib/api/request-scheme.ts#L10-L27, where the author correctly noted:
"Never base a security decision on this. x-forwarded-proto and x-forwarded-host are attacker-supplied unless a reverse proxy overwrites them... They are read here only behind readTrustProxyHeaders()."
Possible Solution
- In
src/lib/oidc.ts, updategetPublicOrigin(request: Request)to respectreadTrustProxyHeaders():import { readTrustProxyHeaders } from "@/lib/security/config"; export function getPublicOrigin(request: Request): string { let host: string | null = null; let proto = "https"; if (readTrustProxyHeaders()) { const forwardedHost = request.headers.get("x-forwarded-host")?.split(",")[0]?.trim(); const forwardedProto = request.headers.get("x-forwarded-proto")?.split(",")[0]?.trim().toLowerCase(); if (forwardedHost) host = forwardedHost; if (forwardedProto === "http" || forwardedProto === "https") proto = forwardedProto; } if (!host) { host = request.headers.get("host")?.trim() || null; } if (host) { return `${proto}://${host}`; } return new URL(request.url).origin; } - Validate that the computed origin or host matches
ALLOWED_ORIGINSorrequest.urlbefore using it in redirect targets.
- Dominant language
- TypeScript
- Stars
- 810
- Forks
- 158
- Avg merge
- 9h 45m
- Merged PRs (30d)
- 521
Getting set up
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from libredb/libredb-studio
-
[BUG] Column profiling fails with SQL syntax errors on columns named after reserved keywords due to unforced identifier quoting in /api/db/profilePossibly taken @saad-works claimed this 2 days ago. Openbug
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
libredb/libredb-studio#2245 · 2 comments · 1 assignee ·
Maintainers usually reply within 1 day
-
[BUG] MCP isLoopbackBind() defaults to false when HOSTNAME is unset or bracketed, disabling Host validation and DNS rebinding protectionPossibly taken @saad-works claimed this 3 days ago. Openbug
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
libredb/libredb-studio#2244 · 1 comment ·
Maintainers usually reply within 1 day
-
[BUG] inspect_schema matches named container case-insensitively without preferring exact matchPossibly taken @Maaz2212 claimed this 7 days ago. Openbug
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
libredb/libredb-studio#2128 · 1 comment · 1 assignee ·
Maintainers usually reply within 1 day
-
bug
Difficulty 3/5 1-2 days Newbie friendliness 58/100
libredb/libredb-studio#2271 ·
Maintainers usually reply within 1 day
-
Difficulty 4/5 3-5 days Newbie friendliness 42/100
libredb/libredb-studio#2267 · 3 comments ·
Maintainers usually reply within 1 day
All issues in libredb/libredb-studio
Similar issues
-
Add: Shop On TV SDOpencheck:passed streams:add
Difficulty 2/5 1-3 hours Newbie friendliness 64/100
Maintainers usually reply within 1 day
-
bug implicit-state
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
-
book-layout.spec fails when the similar-books rail holds other projects' booksPossibly taken @praetorianer777 claimed this today. Open
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
praetorianer777/GoTome#373 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 75/100
Tencent/tdesign-react#4465 · 1 comment ·
Maintainers usually reply within 1 day
-
area:cards enhancement ready-for-agent
Difficulty 2/5 1-3 hours Newbie friendliness 66/100
fil-donadoni/tolaria#8883 ·
Maintainers usually reply within 1 day