Security

Built-in security features for production deployments

4 min read 801 words

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 from X-Forwarded-For
  • scope["scheme"] — Set from X-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 rulesTransfer-Encoding takes precedence over Content-Length
  • Rejects duplicateContent-Lengthheaders with different values
  • Rejects obfuscatedTransfer-Encoding variants (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