denoland/deno · error · NodeTypeError
ERR_INVALID_HTTP_TOKEN
ERR_INVALID_HTTP_TOKEN
Error message
Header name must be a valid HTTP token ["${key}"] What it means
Non-pseudo header names must be valid HTTP tokens (RFC 7230 token grammar: visible ASCII, no separators/space/CTL). Before a header name is written into the HPACK string, checkIsHttpToken(key) runs and failure throws ERR_INVALID_HTTP_TOKEN with 'Header name' (util.ts:869). This also blocks header injection via CR/LF.
Source
Thrown at ext/node/polyfills/internal/http2/util.ts:869
throw new ERR_HTTP2_HEADER_SINGLE_VALUE(key);
}
singles.add(key);
}
const flags = ArrayPrototypeIncludes(neverIndex, key)
? kNeverIndexFlag
: kNoHeaderFlags;
if (key[0] === ":") {
const err = assertValuePseudoHeader(key);
if (err !== undefined) {
throw err;
}
value = escapeNgHeaderValueZeroBytes(value);
pseudoHeaders += `${key}\0${value}\0${flags}`;
count++;
return;
}
if (!checkIsHttpToken(key)) {
throw new ERR_INVALID_HTTP_TOKEN("Header name", key);
}
if (isIllegalConnectionSpecificHeader(key, value)) {
throw new ERR_HTTP2_INVALID_CONNECTION_HEADERS(key);
}
if (isArray) {
for (let j = 0; j < value.length; ++j) {
const val = escapeNgHeaderValueZeroBytes(String(value[j]));
headers += `${key}\0${val}\0${flags}`;
}
count += value.length;
return;
}
value = escapeNgHeaderValueZeroBytes(value);
headers += `${key}\0${value}\0${flags}`;
count++;
}
if (ArrayIsArray(arrayOrMap)) {View on GitHub (pinned to 9ad36f7a2c)
Solutions
- Validate/normalize header names with an HTTP-token regex (e.g. /^[!#$%&'*+.^_|~A-Za-z0-9-]+$/) before building headers.
- Reject or sanitize user-supplied names: strip non-token characters or reject outright — do not silently forward.
- Keep header names in a constants module instead of interpolating dynamic strings.
Example fix
// before
stream.respond({ ':status': 200, [`x-tenant-${tenantName}`]: '1' }); // tenantName = "acme corp"
// after
const TOKEN = /^[!#$%&'*+.^_|~A-Za-z0-9-]+$/;
const name = `x-tenant-${tenantName}`;
if (!TOKEN.test(name)) throw new Error(`bad header name: ${name}`);
stream.respond({ ':status': 200, [name]: '1' }); Defensive patterns
Strategy: type-guard
Validate before calling
const TOKEN_RE = /^[!#$%&'*+.^_|~A-Za-z0-9-]+$/; const isValidHeaderName = (name) => typeof name === 'string' && name.length > 0 && TOKEN_RE.test(name);
Type guard
function isHttpToken(name: string): boolean {
return /^[!#$%&'*+.^_|~A-Za-z0-9-]+$/.test(name);
} Try / catch
try { stream.respond(h); } catch (e) { if (e.code === 'ERR_INVALID_HTTP_TOKEN') { /* find the name in e.message, sanitize or drop, rebuild h */ } throw e; } Prevention
- Never build header names from unfiltered user input
- Keep header names in a constants module
When it happens
Trigger: Passing a header name containing a space, comma, colon-adjacent junk, non-ASCII, or control characters, e.g. { 'x custom': 'v' }, { 'café': 'v' }, or user input used directly as a header name in http2session.request()/stream.respond(). Note ':'-prefixed keys take the pseudo-header path, not this one.
Common situations: Using raw user/DB values as header names (x-user-${username}); fat-fingered names with spaces or underscores vs dashes confusion is fine but spaces are not; truncated mojibake strings; security scans probing CRLF injection ("value\r\nX-Evil: 1" used as a name).
Related errors
- ERR_INVALID_HTTP_TOKEN
- Invalid header value: "${value}"
- ERR_INVALID_HTTP_TOKEN
- ERR_HTTP_INVALID_HEADER_VALUE
- ERR_INVALID_ARG_VALUE
AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20).
Data as JSON: /api/errors/ccbf41acff2ba0e3.
Report an issue: GitHub.