Anchor host-authorization regex to prevent look-alike domain bypass

The DNS-rebinding allowlist used /.*#{registrable_domain}/, which is unanchored:
for example.com it also matched evil-example.com, notexample.com,
example.computer, and example.com.attacker.com. Any of those hosts would pass
Rails' HostAuthorization middleware.

Anchor the pattern as /\A(.+\.)?DOMAIN\z/i so it matches only the registrable
domain and its subdomains (now also case-insensitively). Verified against a
real production boot.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Dan Milne
2026-06-11 19:47:35 +10:00
parent 406a79d9eb
commit 57d7d1f691

View File

@@ -118,14 +118,17 @@ Rails.application.configure do
registrable_domain = domain.domain # Gets "example.com" from "auth.example.com"
if registrable_domain.present?
# Create regex to allow any subdomain of the registrable domain
allowed_hosts << /.*#{Regexp.escape(registrable_domain)}/
# Allow the registrable domain and any subdomain of it. The pattern is
# anchored (\A...\z) with a mandatory dot before the domain so that
# look-alikes such as "evil-example.com" or "example.com.attacker.com"
# do NOT match — an unanchored /.*example\.com/ would allow both.
allowed_hosts << /\A(.+\.)?#{Regexp.escape(registrable_domain)}\z/i
end
rescue PublicSuffix::DomainInvalid
# Fallback to simple domain extraction if PublicSuffix fails
Rails.logger.warn "Could not parse domain '#{host_domain}' with PublicSuffix, using fallback"
base_domain = host_domain.split(".").last(2).join(".")
allowed_hosts << /.*#{Regexp.escape(base_domain)}/
allowed_hosts << /\A(.+\.)?#{Regexp.escape(base_domain)}\z/i
end
end