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:
@@ -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
|
||||
|
||||
13
config/initializers/clinch_host.rb
Normal file
13
config/initializers/clinch_host.rb
Normal file
@@ -0,0 +1,13 @@
|
||||
# CLINCH_HOST is this IdP's canonical external origin, e.g. https://auth.example.com.
|
||||
# It anchors the OIDC issuer, the WebAuthn RP ID, and the forward-auth login
|
||||
# redirect. In deployed (non-local) environments it MUST be set explicitly and
|
||||
# never inferred from request headers — X-Forwarded-Host is attacker-influenceable,
|
||||
# so inferring the origin from it would allow host-header phishing and open
|
||||
# redirects. Fail fast at boot rather than start in an unsafe configuration.
|
||||
unless Rails.env.local?
|
||||
if ENV["CLINCH_HOST"].blank?
|
||||
raise "CLINCH_HOST must be set (e.g. https://auth.example.com). It is the " \
|
||||
"canonical origin of this Clinch instance and must not be inferred " \
|
||||
"from request headers."
|
||||
end
|
||||
end
|
||||
@@ -177,13 +177,10 @@ class ForwardAuthIntegrationTest < ActionDispatch::IntegrationTest
|
||||
end
|
||||
|
||||
test "spoofed X-Forwarded-Host is not reflected as a redirect target" do
|
||||
# CLINCH_HOST pins the IdP origin (as in production) so base_url cannot be
|
||||
# influenced by the request; this isolates the return_to/redirect behaviour.
|
||||
original_clinch_host = ENV["CLINCH_HOST"]
|
||||
ENV["CLINCH_HOST"] = "https://auth.example.com"
|
||||
|
||||
# No forward-auth app exists for evil.com, and no valid rd is supplied. The
|
||||
# attacker-controlled host must NOT be stored or reflected into the signin URL.
|
||||
# attacker-controlled host must NOT be stored or reflected into the signin URL,
|
||||
# and base_url must come from CLINCH_HOST (or the safe localhost default in
|
||||
# test) rather than the request host.
|
||||
get "/api/verify", headers: {
|
||||
"X-Forwarded-Host" => "evil.com",
|
||||
"X-Forwarded-Uri" => "/steal"
|
||||
@@ -193,8 +190,6 @@ class ForwardAuthIntegrationTest < ActionDispatch::IntegrationTest
|
||||
assert_match %r{/signin}, response.location
|
||||
refute_includes response.location, "evil.com"
|
||||
refute_match(/evil\.com/, session[:return_to_after_authenticating].to_s)
|
||||
ensure
|
||||
ENV["CLINCH_HOST"] = original_clinch_host
|
||||
end
|
||||
|
||||
test "return URL functionality after authentication" do
|
||||
|
||||
Reference in New Issue
Block a user