Require CLINCH_HOST in deployed environments; drop request-host fallback

determine_base_url fell back to request.host when CLINCH_HOST was unset. Rails
resolves request.host from X-Forwarded-Host behind a trusted proxy, so a spoofed
header could make the forward-auth login redirect point at an attacker origin
(host-header phishing).

- Add config/initializers/clinch_host.rb: fail fast at boot in any non-local
  environment when CLINCH_HOST is blank. It anchors the OIDC issuer, WebAuthn
  RP ID, and login redirect, so it must be explicit, never inferred.
- determine_base_url now uses CLINCH_HOST (guaranteed in production) with a safe
  localhost default for dev/test, and never reads the request host.
- Simplify the spoofed-host regression test now that the fallback is safe.

Verified: production boot aborts with a clear message when CLINCH_HOST is blank,
and boots normally when set.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Dan Milne
2026-06-11 08:04:42 +10:00
parent 96a657e349
commit 84ed462f40
3 changed files with 23 additions and 20 deletions

View File

@@ -243,18 +243,13 @@ module Api
def determine_base_url(redirect_url)
return redirect_url if redirect_url.present?
if ENV["CLINCH_HOST"].present?
host = ENV["CLINCH_HOST"]
host.match?(/^https?:\/\//) ? host : "https://#{host}"
else
request_host = request.host || request.headers["X-Forwarded-Host"]
if request_host.present?
Rails.logger.warn "ForwardAuth: CLINCH_HOST not set, using request host: #{request_host}"
"https://#{request_host}"
else
raise StandardError, "ForwardAuth: CLINCH_HOST environment variable not set and no request host available."
end
end
# CLINCH_HOST is the IdP's canonical origin and is mandatory in deployed
# environments (enforced at boot in config/initializers/clinch_host.rb).
# We never fall back to the request host: a spoofed X-Forwarded-Host would
# otherwise redirect the login flow to an attacker-controlled origin. The
# localhost default only applies to local dev/test.
host = ENV["CLINCH_HOST"].presence || "http://localhost:3000"
host.match?(%r{\Ahttps?://}) ? host : "https://#{host}"
end
end
end