Safeguard
Application Security•

ASVS 5.0 V4.2.2: Generate Correct Content-Length Headers

When your code builds HTTP messages, Content-Length must never disagree with the real body length. Learn how to avoid it and test for mismatches.

Safeguard Security Team
3 min read

ASVS 5.0 V4.2.2 (level 3) says that when your application generates HTTP messages, the Content-Length header must not conflict with the length of the content as framed by the protocol. If the body is 120 bytes, the header says 120, or you leave the header off and let the library choose chunked or frame based framing.

This is the sending side of V4.2.1. If you build requests or responses, you must not be the source of a smuggling mismatch.

Why it matters

Gateways, API clients, mock servers and custom proxies often build HTTP by hand. A wrong Content-Length is easy to write, especially after a body is compressed, re encoded or templated. Downstream parsers then disagree about where the message ends, which is the exact condition smuggling attacks need.

Character count versus byte count is the classic source. A string with accented letters is longer in UTF-8 bytes than in characters, and the header ends up short.

How to implement it

Do not set Content-Length by hand if the library can do it. Let the HTTP client or server compute it from the final body bytes.

When you must set it, compute it from the encoded buffer, after all transformations:

const body = Buffer.from(JSON.stringify(payload), 'utf8');
res.writeHead(200, {
  'Content-Type': 'application/json',
  'Content-Length': body.length, // bytes, not string length
});
res.end(body);

For proxies, drop the original Content-Length when you change the body, and never copy both Content-Length and Transfer-Encoding forward. In HTTP/2, remove any Content-Length that does not equal the DATA total.

How to test it

Write a test that captures what your code puts on the wire, and compares header to body.

raw = capture_response(app, "/report")
head, body = raw.split(b"\r\n\r\n", 1)
declared = int(re.search(rb"Content-Length: (\d+)", head).group(1))
assert declared == len(body)

Include cases with non ASCII text, compressed bodies, empty bodies, and responses to HEAD requests. Also run a proxy in front and check that it never forwards both framing headers.

Common mistakes

  • Using string length instead of byte length.
  • Computing the header before gzip or template rendering changes the body.
  • Copying hop by hop and framing headers when forwarding in a proxy.
  • Hand building HTTP over raw sockets in tests and then reusing that code in production.

Quick check

Why is string length risky as a Content-Length value?

Multi byte characters make the encoded body longer than the character count, so the header is too small and the message framing is wrong.

Sources

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.