Secure HTTP Header Mutations in JavaScript Headers

The Fetch API’s Headers interface provides a built-in, secure mechanism for reading, modifying, and validating HTTP headers in modern JavaScript. This article explains how the Headers object safeguards network operations by employing internal header guards, blocking forbidden header names, enforcing strict syntactic validation to prevent injection attacks, and normalizing header keys during mutation operations.

Header Guards

The primary security mechanism inside a Headers instance is an internal property known as the guard. The guard determines whether headers can be modified, appended, or deleted based on the context in which the object is used.

JavaScript defines five guard states:

// Example: Attempting to mutate an immutable response header
fetch('https://api.example.com/data')
  .then(response => {
    // Throws TypeError: Failed to execute 'set' on 'Headers': Headers are immutable
    response.headers.set('X-Custom-Header', 'value');
  });

Forbidden Header Restrictions

To prevent malicious scripts from spoofing identity, bypassing security policies, or hijacking sessions, the Headers object disallows programmatic modification of specific headers.

Forbidden Request Headers

When the guard is set to request, browsers block modifications to headers controlled exclusively by the user agent: * Accept-Charset, Accept-Encoding, Access-Control-Request-Headers, Access-Control-Request-Method * Connection, Content-Length, Cookie, Cookie2, Date, DNT * Host, Keep-Alive, Origin, Referer, TE, Trailer, Transfer-Encoding, Upgrade, Via * Any header prefixed with Proxy- or Sec-

Attempting to mutate these headers does not throw an error in standard environments; instead, the modification is silently ignored, preventing unintended execution crashes while maintaining security.

Preventing HTTP Header Injection

HTTP Header Injection (or HTTP Response Splitting) occurs when untrusted input containing carriage return (\r or 0x0D) and newline (\n or 0x0A) characters is written into headers, allowing attackers to inject arbitrary headers or split HTTP messages.

The Headers interface prevents this by strictly enforcing HTTP token and byte validation rules in compliance with the HTTP/1.1 and HTTP/2 specifications (RFC 9110):

  1. Name Validation: Header names must match the token ABNF production (alphanumeric and specific standard symbols only). Whitespace, colons, control characters, or non-ASCII characters immediately throw a TypeError.
  2. Value Validation: Header values are checked for prohibited control characters (\0, \r, \n). If a newline or invalid byte sequence is passed to append() or set(), the browser throws a TypeError.
const headers = new Headers();

// Throws TypeError: Invalid character in header field name
headers.set('Bad:Name', 'value');

// Throws TypeError: String contains invalid characters (prevents CRLF injection)
headers.set('X-Custom', 'valid\r\nInjected-Header: evil');

Case-Insensitive Normalization and Merging

The Headers interface ensures consistency across operations by automatically normalizing all header names to lowercase byte-sequences before executing mutations: