From 0ee53e7b388c3b74aa742f82f2a806dcd2bbd055 Mon Sep 17 00:00:00 2001 From: jubnl Date: Wed, 1 Apr 2026 04:36:27 +0200 Subject: [PATCH] fix: prevent OIDC redirect URI construction from untrusted X-Forwarded-Host The OIDC login route silently fell back to building the redirect URI from X-Forwarded-Host/X-Forwarded-Proto when APP_URL was not configured. An attacker could set X-Forwarded-Host: attacker.example.com to redirect the authorization code to their own server after the user authenticates. Remove the header-derived fallback entirely. If APP_URL is not set (via env or the app_url DB setting), the OIDC login endpoint now returns a 500 error rather than trusting attacker-controlled request headers. Document APP_URL in .env.example as required for OIDC use. --- server/.env.example | 2 ++ server/src/routes/oidc.ts | 10 +++------- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/server/.env.example b/server/.env.example index 2aae179..490447c 100644 --- a/server/.env.example +++ b/server/.env.example @@ -8,6 +8,8 @@ ALLOWED_ORIGINS=https://trek.example.com # Comma-separated origins for CORS and FORCE_HTTPS=false # Redirect HTTP → HTTPS behind a TLS proxy TRUST_PROXY=1 # Number of trusted proxies for X-Forwarded-For +APP_URL=https://trek.example.com # Base URL of this instance — required when OIDC is enabled; must match the redirect URI registered with your IdP + OIDC_ISSUER=https://auth.example.com # OpenID Connect provider URL OIDC_CLIENT_ID=trek # OpenID Connect client ID OIDC_CLIENT_SECRET=supersecret # OpenID Connect client secret diff --git a/server/src/routes/oidc.ts b/server/src/routes/oidc.ts index 7f38dd8..0c3686f 100644 --- a/server/src/routes/oidc.ts +++ b/server/src/routes/oidc.ts @@ -123,14 +123,10 @@ router.get('/login', async (req: Request, res: Response) => { const doc = await discover(config.issuer); const state = crypto.randomBytes(32).toString('hex'); const appUrl = process.env.APP_URL || (db.prepare("SELECT value FROM app_settings WHERE key = 'app_url'").get() as { value: string } | undefined)?.value; - let redirectUri: string; - if (appUrl) { - redirectUri = `${appUrl.replace(/\/+$/, '')}/api/auth/oidc/callback`; - } else { - const proto = (req.headers['x-forwarded-proto'] as string) || req.protocol; - const host = (req.headers['x-forwarded-host'] as string) || req.headers.host; - redirectUri = `${proto}://${host}/api/auth/oidc/callback`; + if (!appUrl) { + return res.status(500).json({ error: 'APP_URL is not configured. OIDC cannot be used.' }); } + const redirectUri = `${appUrl.replace(/\/+$/, '')}/api/auth/oidc/callback`; const inviteToken = req.query.invite as string | undefined; pendingStates.set(state, { createdAt: Date.now(), redirectUri, inviteToken });