Skip to content

Trusted Host Middleware

A lightweight production-ready trusted host middleware for the sillo framework.

A lightweight, production‑ready trusted host middleware for the sillo ASGI framework.

It automatically validates the Host header of incoming requests against a configurable list of allowed hosts to prevent Host header attacks and ensure requests only come from trusted domains.

Terminal window
uv add sillo-contrib
from sillo import silloApp
import sillo_contrib.trusted as trusted
app = silloApp()
# Add the Trusted Host middleware
app.use(
trusted.TrustedHost(
allowed_hosts=[
"example.com",
"api.example.com",
"*.example.com", # Wildcard support
"127.0.0.1",
"localhost"
],
allowed_ports=[80, 443, 8000], # Optional: restrict ports
www_redirect=True # Redirect www.example.com to example.com
)
)
@app.get("/")
async def home(request, response):
return {"message": "Hello from trusted host!"}
  • allowed_hosts: List[str]
    List of allowed hostnames, IP addresses, or patterns. Supports wildcards (e.g., "*.example.com")
  • allowed_ports: Optional[List[int]] = None
    List of allowed ports. If specified, only requests to these ports will be allowed. If None, all ports are allowed.

  • www_redirect: bool = True
    If True, automatically allows requests from www.domain.com if domain.com is in the allowed hosts list.

The middleware supports several types of host patterns:

allowed_hosts = ["example.com", "api.example.com"]
allowed_hosts = ["127.0.0.1", "192.168.1.100"]
allowed_hosts = ["*.example.com", "*.api.example.com"]
allowed_hosts = ["example.com"]
allowed_ports = [80, 443] # Only HTTP and HTTPS
  1. Host Extraction: Extracts the host from request headers with proper precedence:

    • X-Forwarded-Host (for proxies/load balancers)
    • X-Host (some proxies)
    • Host header (standard)
  2. Validation: Checks the extracted host against the allowed patterns:

    • Normalizes hosts to lowercase
    • Validates against exact matches and wildcard patterns
    • Checks port restrictions if specified
  3. Security: Rejects requests with untrusted hosts using 400 Bad Request

  4. WWW Handling: Optionally allows www.domain.com if domain.com is trusted

  • Host Header Attack Prevention: Blocks requests with malicious Host headers
  • Proxy Support: Properly handles forwarded headers from reverse proxies
  • Port Security: Optional port restrictions for additional security
  • Case Insensitive: All host validation is case insensitive
  • Wildcard Support: Flexible pattern matching for subdomains
trusted.TrustedHost(allowed_hosts=["example.com", "api.example.com"])
trusted.TrustedHost(
allowed_hosts=["localhost", "127.0.0.1", "*.local"],
allowed_ports=[3000, 8000, 8080]
)
trusted.TrustedHost(
allowed_hosts=[
"example.com",
"www.example.com",
"*.example.com",
"cdn.example.com"
],
allowed_ports=[80, 443]
)
trusted.TrustedHost(
allowed_hosts=[
"example.com",
"myapp.com",
"staging.example.com",
"api.myapp.com"
]
)

When a request comes from an untrusted host, the middleware raises:

  • BadRequest exception with a descriptive message
  • HTTP 400 status code is returned to the client
# Example error response
{
"error": "Untrusted host",
"detail": "Host 'malicious.com' is not allowed"
}
from sillo import silloApp
from sillo.exceptions import BadRequest
app = silloApp()
@app.add_exception_handler(BadRequest)
async def handle_bad_request(request, response, exc):
if "Untrusted host" in str(exc):
# Custom response for untrusted hosts
return {
"error": "Access Denied",
"message": "This domain is not authorized to access this service"
}, 403
# Default handling for other BadRequest exceptions
return {"error": str(exc)}, 400
import os
from sillo_contrib.trusted import TrustedHost
# Load allowed hosts from environment
allowed_hosts = os.getenv("ALLOWED_HOSTS", "localhost").split(",")
app.use(
TrustedHost(
allowed_hosts=allowed_hosts,
allowed_ports=[80, 443] if os.getenv("PRODUCTION") else None
)
)
from sillo_contrib.trusted import TrustedHostMiddleware
class HealthCheckTrustedHost(TrustedHostMiddleware):
def __init__(self, health_check_path="/health", **kwargs):
super().__init__(**kwargs)
self.health_check_path = health_check_path
async def __call__(self, request, response, call_next):
# Skip host validation for health checks
if request.url.path == self.health_check_path:
return await call_next()
return await super().__call__(request, response, call_next)
app.use(
HealthCheckTrustedHost(
allowed_hosts=["example.com"],
health_check_path="/health"
)
)
  • Always specify allowed_hosts: Never leave this empty or use wildcards like ["*"] in production
  • Use HTTPS ports: In production, typically restrict to ports 80 and 443
  • Consider your deployment: Behind load balancers? Use X-Forwarded-Host support
  • Environment specific: Use different configurations for development vs production
  • Subdomain handling: Use wildcards for flexible subdomain support
  • IP restrictions: Include your server’s IP addresses in allowed hosts for health checks
# Nginx sets X-Forwarded-Host header
trusted.TrustedHost(
allowed_hosts=["example.com", "www.example.com"],
allowed_ports=[80, 443]
)
# Multiple ingress domains
trusted.TrustedHost(
allowed_hosts=[
"api.example.com",
"*.staging.example.com",
"internal.cluster.local"
]
)
# Allow internal service communication
trusted.TrustedHost(
allowed_hosts=[
"service-a.internal",
"service-b.internal",
"*.example.com" # External traffic
]
)

Host header not found

  • Check if your proxy is forwarding the Host header correctly
  • Verify X-Forwarded-Host is set if behind a proxy

Wildcard not matching

  • Ensure wildcard patterns use * not regex syntax
  • *.example.com matches api.example.com but not example.com

Port restrictions too strict

  • Include all ports your application runs on
  • Consider load balancer port forwarding

Built with ❤️ by the @sillohq community.