Overview
Pounce includes several defense-in-depth security measures that operate at the server level — before your ASGI application is invoked. These protections are active by default and require no configuration.
Proxy Header Validation
When running behind a reverse proxy (nginx, Caddy, etc.), the proxy adds headers likeX-Forwarded-For, X-Forwarded-Proto, and X-Forwarded-Hostto communicate the original client's information.
The problem: A malicious client can send these headers directly to spoof their IP or protocol. Pounce prevents this by only trusting these headers from known proxy IPs.
How It Works
trusted_hosts |
Behavior |
|---|---|
()(empty, default) |
Strip allX-Forwarded-*headers — assume direct connection |
("10.0.0.1",) |
Trust proxy at10.0.0.1— apply forwarded headers to ASGI scope |
("10.0.0.1", "10.0.0.2") |
Trust multiple proxies |
("*",) |
Trust all peers (only safe for private networks) |
When a trusted proxy sendsX-Forwarded-For, Pounce updates:
scope["client"]— Set to the real client IP fromX-Forwarded-Forscope["scheme"]— Set fromX-Forwarded-Proto(e.g."https")scope["headers"]— Proxy headers stripped to prevent downstream leakage
When an untrusted peer sends these headers, they are silently stripped.
import pounce
pounce.run(
"myapp:app",
trusted_hosts=("10.0.0.1",), # Only trust your proxy
)
Warning
Never usetrusted_hosts=("*",)on internet-facing deployments. Any client could spoof their IP address.
CRLF Header Injection Protection
Pounce sanitises all response header names and values by stripping carriage return (\r) and line feed (\n) characters before they reach the HTTP serializer.
This prevents a class of attacks where a malicious ASGI application could inject extra HTTP headers into the response by embedding\r\nsequences in header values:
X-Safe: clean\r\nX-Injected: evil
Without sanitisation, this would appear as two separate headers in the response. Pounce's bridge strips the control characters, and h11 provides an additional validation layer.
This protection applies to both HTTP/1.1 and HTTP/2 responses.
Request Smuggling Prevention
HTTP request smuggling exploits ambiguities whenContent-Length and Transfer-Encodingheaders conflict. Pounce relies on h11's strict HTTP parser, which:
- Enforces RFC 9112 framing rules —
Transfer-Encodingtakes precedence overContent-Length - Rejects duplicate
Content-Lengthheaders with different values - Rejects obfuscated
Transfer-Encodingvariants (e.g.Transfer-Encoding: chunked, identity) - Rejects null bytes in header values
Because Pounce is not an intermediary (it's the terminal server), desynchronisation attacks are not applicable. The h11 parser's strict mode prevents all known smuggling vectors.
Slowloris Protection
A slowloris attack holds connections open by sending HTTP headers very slowly — one byte at a time — exhausting the server's connection pool without ever completing a request.
Pounce protects against this withheader_timeout:
pounce myapp:app --header-timeout 10
| Timeout | Purpose | Default |
|---|---|---|
header_timeout |
Max seconds to receive complete request headers | 10s |
keep_alive_timeout |
Max seconds to wait between keep-alive requests | 5s |
Theheader_timeout applies to the initial header read of each new request. If headers are not received within the limit, the connection is closed. Between keep-alive requests, the keep_alive_timeoutapplies instead.
Connection Backpressure
When the server reachesmax_connections, new connections receive an HTTP 503 Service Unavailable response with a Retry-After: 5header, rather than being silently dropped. This gives clients actionable feedback.
Streaming Body Limits
max_request_sizeis enforced for both buffered and streaming request bodies. If a chunked or streaming body exceeds the limit, the stream is terminated with an empty final chunk — the ASGI app sees EOF and the connection is not abruptly closed.
HEAD Request Handling
ForHEAD requests, the ASGI app may produce response body bytes (to calculate Content-Length), but they must not be sent on the wire. Pounce automatically disables compression for HEAD responses to ensure the Content-Length header from the app matches what a subsequent GETwould produce.
Bodyless Response Protection
HTTP status codes 204 (No Content) and 304 (Not Modified) must not carry a message body per RFC 9110 section 6.4.1. Pounce disables compression for these status codes to prevent compressor flush bytes from producing a body where none is allowed.
Summary
| Protection | Layer | Config Required |
|---|---|---|
| Proxy header validation | Bridge | trusted_hosts |
| CRLF header injection | Bridge | None (always on) |
| Request smuggling prevention | Protocol (h11) | None (always on) |
| Slowloris protection | Worker | header_timeout(default: 10s) |
| Connection backpressure | Worker | max_connections(default: 10,000) |
| Streaming body limits | Worker | max_request_size(default: 1 MB) |
| HEAD compression guard | Bridge | None (always on) |
| Bodyless response guard | Bridge | None (always on) |
See Also
- Production — Full production deployment guide
- Observability — Health checks and request tracing
- ServerConfig — All configuration options