Host Filtering

Restrict which Host headers are allowed to reach your app. This mitigates HTTP Host header attacks and ensures requests are routed only for expected hostnames.

Full source

File: pwsh/tutorial/examples/10.6-HostFiltering.ps1

<#
    Sample Kestrun Server – Host Filtering
    Demonstrates enabling Host Filtering middleware and a simple route.
    FileName: 10.6-HostFiltering.ps1
#>

param(
    [int]$Port = 5000,
    [IPAddress]$IPAddress = [IPAddress]::Loopback
)

# Optional: console logger so we can see events
New-KrLogger | Set-KrLoggerLevel -Value Debug |
    Add-KrSinkConsole |
    Register-KrLogger -Name 'console' -SetAsDefault | Out-Null

# 1) Create server
New-KrServer -Name 'Endpoints HostFiltering'

# 2) Listener
Add-KrEndpoint -Port $Port -IPAddress $IPAddress

# 3)  Configure Host Filtering via parameters
#    - Only allow requests with Host header "example.com" or "www.example.com"
#    - Reject empty Host headers
#    - Exclude failure message body for blocked hosts
Add-KrHostFiltering `
    -AllowedHosts @('example.com', 'www.example.com') `
    -NotAllowEmptyHosts

# 4) Enable Kestrun configuration
Enable-KrConfiguration

# 5) Map a simple route
Add-KrMapRoute -Verbs Get -Pattern '/hello' -ScriptBlock {
    Write-KrLog -Level Information -Message 'Processing /hello request'
    Write-KrTextResponse -InputObject 'Hello from host-filtered server' -StatusCode 200
}

# 6) Run
Start-KrServer -CloseLogsOnExit

Step-by-step

  1. Add the Host Filtering middleware with your allowlist.
  2. Commit configuration with Enable-KrConfiguration.
  3. Map routes as usual; unauthorized hosts never reach them.
  4. Start the server and probe with different Host headers.

Try it

Run the sample:

pwsh ./docs/_includes/examples/pwsh/10.6-HostFiltering.ps1

Then test allowed vs. blocked hosts:

# Allowed
curl -i -H "Host: example.com" http://127.0.0.1:5000/hello
curl -i -H "Host: www.example.com" http://127.0.0.1:5000/hello

# Blocked
curl -i -H "Host: blocked.example" http://127.0.0.1:5000/hello

# Empty Host (rejected when -NotAllowEmptyHosts used)
curl -i -H "Host: " http://127.0.0.1:5000/hello
# Allowed
Invoke-WebRequest http://127.0.0.1:5000/hello -Headers @{ Host = 'example.com' }
Invoke-WebRequest http://127.0.0.1:5000/hello -Headers @{ Host = 'www.example.com' }

# Blocked
Invoke-WebRequest http://127.0.0.1:5000/hello -Headers @{ Host = 'blocked.example' } -SkipHttpErrorCheck

# Empty Host (rejected when -NotAllowEmptyHosts used)
Invoke-WebRequest http://127.0.0.1:5000/hello -Headers @{ Host = '' } -SkipHttpErrorCheck

Expected outcomes:

  • Allowed hosts → 200 OK with response body.
  • Disallowed or empty host → 400 Bad Request. If -ExcludeFailureMessage is used, the response body won’t include details.

Key Points

The middleware validates the request Host header against an allowlist and returns 400 Bad Request for non-matching hosts. You can optionally allow empty Host headers and include a failure message.

Option Meaning
AllowedHosts List of hostnames to allow (no ports). Supports wildcard * for any non-empty, and subdomain wildcards like *.example.com.
NotAllowEmptyHosts If set, requests with an empty Host header are rejected.
ExcludeFailureMessage If set, the 400 response omits the failure message body.

IPv6 literals must be bracketed and normalized; Unicode hosts are matched using their punycode representation.

Troubleshooting

Symptom Cause Fix
Still seeing 200 for blocked host Middleware added after configuration or not applied Ensure Add-KrHostFiltering is called before Enable-KrConfiguration.
Allowed host with port is blocked Ports are not permitted in AllowedHosts Remove the port; use just the hostname.
Wildcard not matching parent domain *.example.com matches subdomains, not the bare example.com Add both example.com and *.example.com if needed.

References


Previous / Next

Previous: SignalR Next: None