Skip to content

Harden against host header injection and open redirect (CVE-2025-50578)#1568

Merged
KodeStar merged 3 commits into
2.xfrom
fix/host-header-injection
Jul 8, 2026
Merged

Harden against host header injection and open redirect (CVE-2025-50578)#1568
KodeStar merged 3 commits into
2.xfrom
fix/host-header-injection

Conversation

@KodeStar

@KodeStar KodeStar commented Jul 8, 2026

Copy link
Copy Markdown
Member

Problem

Resolves #1451 (CVE-2025-50578).

Heimdall trusted attacker-controlled host headers for URL generation. layouts/app.blade.php renders <base href="{{ url('') }}"> (request-host-based) when APP_URL is left at its default, and views build absolute URLs with asset(). Because TrustProxies trusted X-Forwarded-Host over broad private proxy ranges, a request like:

GET / HTTP/1.1
Host: localhost
X-Forwarded-Host: evil.com

made every relative asset load from evil.com, and redirect(route('dash')) emitted a Location pointing at the spoofed host (the "open redirect").

Change (backward-compatible by default)

  • Stop trusting X-Forwarded-Host in TrustProxies — a forged value can no longer influence getHost(), url(), asset() or redirects. X-Forwarded-For / -Port / -Proto handling is unchanged.
  • TRUSTED_PROXIES env var — reverse-proxy IPs/CIDRs are now configurable (comma-separated, * to trust all), defaulting to the previous private ranges when unset.
  • TRUSTED_HOSTS env var (opt-in) — a new App\Http\Middleware\TrustHosts restricts the Host header to a configured allow-list. Unset ⇒ no restriction (Heimdall keeps serving arbitrary hosts, so existing installs are unaffected). Set to your domain ⇒ any other Host is rejected (HTTP 400), fully closing the direct-Host-injection vector.

Installs that already set APP_URL were partially protected (Heimdall calls URL::forceRootUrl(config('app.url'))); this change closes the remaining header-trust gap and gives operators an explicit lockdown knob.

Tests

tests/Feature/TrustProxiesTest.php + tests/Feature/TrustHostsTest.php (12 tests): X-Forwarded-Host is ignored while X-Forwarded-Proto still works, TRUSTED_PROXIES parsing incl. *, TRUSTED_HOSTS empty ⇒ arbitrary host accepted, TRUSTED_HOSTS=example.comevil.com rejected / example.com accepted, and the subclass is registered in the global middleware stack. Full suite green (46 passing, 1 pre-existing skip); phpcs clean.

Notes

  • The TRUSTED_HOSTS allow-list follows Laravel's standard TrustHosts behaviour and only enforces when APP_ENV is not local. The X-Forwarded-Host change (the primary mitigation) applies in all environments.
  • TRUSTED_PROXIES / TRUSTED_HOSTS are read via env(); if you run php artisan config:cache the defaults apply. The security-critical default (not trusting X-Forwarded-Host) is hard-coded and unaffected.

Heimdall trusted the incoming X-Forwarded-Host header for URL generation, so
a spoofed value poisoned the page base href, asset() URLs and redirect
targets - loading assets from and redirecting to an attacker-controlled host
(CVE-2025-50578).

- TrustProxies no longer trusts X-Forwarded-Host; a forged value can no longer
  influence getHost(), url(), asset() or redirects. X-Forwarded-For/Port/Proto
  handling is unchanged.
- Trusted proxies are now configurable via the TRUSTED_PROXIES env var
  (comma-separated CIDRs/IPs, "*" to trust all), defaulting to the previous
  private ranges.
- Added an opt-in TRUSTED_HOSTS allow-list: when set, only the listed hosts are
  served and any other Host header is rejected. Unset keeps the historic
  behaviour of serving arbitrary hosts, so existing installs are unaffected.

Resolves #1451
@KodeStar KodeStar mentioned this pull request Jul 8, 2026
The custom TrustHosts middleware only overrode hosts(), so it inherited the
parent's shouldSpecifyTrustedHosts() gate, which skips enforcement whenever the
app runs in the local environment or under the test runner. Heimdall ships
APP_ENV=local by default (.env.example, copied to .env on install), so the
TRUSTED_HOSTS allow-list a user configures per the .env.example guidance was
never actually applied.

Override shouldSpecifyTrustedHosts() to tie enforcement to configuration
instead of environment: apply the allow-list whenever TRUSTED_HOSTS is set, in
any environment; when it is unset hosts() is empty and enforcement stays off,
preserving the historic no-restriction behaviour. Add handle()-driven tests
covering both the configured and unconfigured cases.
@KodeStar
KodeStar merged commit a1f0d8f into 2.x Jul 8, 2026
1 check passed
@LinuxServer-CI LinuxServer-CI moved this from PRs to Done in Issue & PR Tracker Jul 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Development

Successfully merging this pull request may close these issues.

CVE-2025-50578: Host Header Injection & Open Redirect in linuxserver/heimdall:latest

2 participants