AL+

Language: RO (română) EN (English)

Email us: paul@alplustech.com

Django ASGI headers need one trust boundary

Django’s ASGI header fix shows why proxy filters and application normalization must agree, with deployment-specific limits and low official severity.

Citiți articolul în română

A reverse proxy can remove a sensitive header and still leave the application exposed to an equivalent spelling. The relevant security boundary includes every transformation between the public request and the value the application trusts. Django’s ASGI header advisory makes that boundary unusually clear: a filter’s spelling rules and an application’s normalization rules have to agree.

On April 7, 2026, Django disclosed CVE-2026-3902 and issued fixes in 6.0.4, 5.2.13 and 4.2.30. The maintainers assigned the issue low severity. Those are historical fixed versions, not a list of the latest releases or a deployment-specific assessment. The official announcement is the starting point for the mechanism and its limits.

A trusted name can have two spellings

The advisory describes ASGIRequest converting hyphens in header names to underscores, following WSGI conventions. A proxy might strip a security-sensitive header whose name contains hyphens while accepting a name containing underscores. If the application subsequently maps both spellings to the same internal name, the proxy’s removal rule has not established the intended boundary. This is a disagreement about identity across layers.

That distinction matters when reviewing a system. Seeing a removal rule proves that one spelling is handled at that layer. It does not prove that every input which becomes the same application value is handled. The useful question is which public inputs can reach the trusted internal name after all transformations have happened. That question remains useful even when the individual components are behaving according to their own documented conventions.

The Django 6.0.4 release notes distinguish the WSGI expectation from the ASGI situation. Under WSGI, avoiding ambiguous mappings is the server or proxy’s responsibility. ASGI did not carry the same uniform expectation. The fix makes ASGIRequest ignore headers containing underscores, matching Daphne’s behavior. Moving between serving interfaces therefore deserves a fresh look at inherited assumptions.

Follow the value through the deployment

A useful design review starts with the application value that receives authority. Identify which component is allowed to supply it and which public request paths can reach that component. Then trace the name through the proxy and application adapter. Keeping the value in view prevents a discussion about header punctuation from losing the reason the header is sensitive.

This is a recommendation for examining the deployed contract, rather than evidence that a particular installation is vulnerable. The relevant facts include the actual serving interface and the behavior of the actual proxy path. A diagram that labels a box “proxy” cannot establish how that box treats underscores. Nor can a framework version alone explain whether an attacker-controlled spelling reaches an authority-bearing value.

Ownership belongs in that review. Someone should be able to explain which layer rejects ambiguous client input and which layer introduces trusted information. If those responsibilities are split between infrastructure and application teams, the handoff should describe the accepted names. Otherwise each team can point to a locally reasonable rule while nobody has checked the complete interpretation.

The same reasoning applies when a deployment changes. Replacing an adapter or changing a proxy path can invalidate a previously accurate explanation. Treat that explanation as a claim about a specific request path. It should remain possible to identify the assumptions that would require reassessment, without turning every infrastructure change into a claim that a security incident has occurred.

Default protection is a real counterargument

The strongest objection to broad warnings is already in Django’s disclosure: many proxies protect against this under their default configuration. The announcement explicitly names nginx’s default rejection of underscore headers. A deployment that discards the alternative spelling before Django receives it may already prevent this route. It would be misleading to describe every ASGI installation as universally exploitable.

That objection narrows the exposure claim. It also explains why a security review should use deployment evidence. The presence of a protective default is useful information, but its relevance depends on whether that behavior actually applies along the request path being discussed. A team should distinguish documented defaults from the configuration it relies on, and distinguish both from an observed outcome.

Low severity also belongs in the explanation. It is the official classification for this issue, not permission to invent a higher rating because header spoofing sounds serious. Local prioritization can consider the role of the affected header and the deployment conditions. Any stronger assessment should state those conditions and its own evidence. The source alone does not establish them for an unfamiliar application.

Keep the review decision narrow

The practical outcome should be a specific account of the trust boundary: which input names are accepted, how they become application names, and where untrusted alternatives stop. Updating supported software remains part of normal maintenance, but the April versions should not be copied into a November upgrade recommendation as though time had stopped. Current support and the application’s environment require their own check.

For teams commissioning custom software development, this is a useful acceptance topic. Ask for an explanation of how the application receives trusted proxy information and who owns that contract after handover. A framework patch addresses the disclosed mechanism. A clear deployment contract helps the next maintainer recognize when a later change touches the same boundary.