ASVS 5.0 V4.2.3 (level 3) requires that your application neither sends nor accepts HTTP/2 or HTTP/3 messages that carry connection specific header fields, with Transfer-Encoding as the standard example. The same applies to Connection, Keep-Alive, Proxy-Connection and Upgrade.
Those headers belong to HTTP/1.1, where one connection carries a stream of messages. HTTP/2 and HTTP/3 handle connection state in the protocol itself, so these fields have no legitimate use there.
Why it matters
When an edge terminates HTTP/2 and speaks HTTP/1.1 to the backend, a forbidden header can ride through the translation. A request that carries Transfer-Encoding: chunked over HTTP/2 can turn into a smuggling payload after downgrade, because the backend now honors a header the edge never should have forwarded. The same translation can be abused for response splitting and header injection, which is why the ASVS item names both. See CWE-113.
The risk grows with every gateway, service mesh sidecar and custom proxy you add, since each is another translator.
How to implement it
Most mature servers already reject these headers on HTTP/2. Your job is to confirm that and to avoid emitting them from your own code.
- Keep servers and proxies on current versions, and leave strict header validation on.
- In handlers and middleware, never set
Connection,Keep-Alive,Transfer-EncodingorUpgradeyourself on responses that may be served over HTTP/2. - In a custom proxy, strip hop by hop headers before forwarding.
A Node proxy helper:
const HOP = ['connection','keep-alive','proxy-connection','transfer-encoding','upgrade','te','trailer'];
function cleanHeaders(headers) {
const out = { ...headers };
for (const h of HOP) delete out[h];
return out;
}
In nginx, the server terminates HTTP/2 itself, so check that the version in use rejects bad pseudo and connection headers, and keep ignore_invalid_headers on (the default).
How to test it
Use a client that can send raw HTTP/2 frames, such as nghttp or a small script with a low level library, and add a forbidden header.
nghttp -v -H "transfer-encoding: chunked" https://example.com/
A compliant server answers with a stream error or a 400 and does not pass the header to the backend. Check the backend logs too, since the point is that nothing forbidden reaches it. Then read your own responses over HTTP/2 with curl --http2 -i and make sure none include Connection or Transfer-Encoding.
Common mistakes
- Setting
Connection: keep-alivein application code out of habit. - A proxy that copies every header from the inbound request to the outbound one.
- Testing only HTTP/1.1, where these headers are normal.
- Assuming the CDN removes them, without checking the origin path it uses.
Quick check
Why is Transfer-Encoding a problem in an HTTP/2 request?
HTTP/2 has its own framing, so the header is not allowed. If it survives a downgrade to HTTP/1.1, the backend may honor it and misread message boundaries.
Sources
- OWASP Application Security Verification Standard, requirement V4.2.3 (level 3)
- RFC 9113, HTTP/2, connection specific header fields