denoland/deno · error · NodeTypeError

ERR_HTTP2_INVALID_PSEUDOHEADER

ERR_HTTP2_INVALID_PSEUDOHEADER

Error message

"${key}" is an invalid pseudoheader or is used incorrectly

What it means

HTTP/2 pseudo-headers (keys starting with ':') are fixed by RFC 7540; only :status, :method, :authority, :scheme, :path and :protocol are legal (kValidPseudoHeaders, util.ts:120). When request headers are prepared, every ':'-prefixed key is checked against that set and any other pseudo-header throws ERR_HTTP2_INVALID_PSEUDOHEADER.

Source

Thrown at ext/node/polyfills/internal/http2/util.ts:613

function isIllegalConnectionSpecificHeader(name, value) {
  switch (name) {
    case HTTP2_HEADER_CONNECTION:
    case HTTP2_HEADER_UPGRADE:
    case HTTP2_HEADER_HTTP2_SETTINGS:
    case HTTP2_HEADER_KEEP_ALIVE:
    case HTTP2_HEADER_PROXY_CONNECTION:
    case HTTP2_HEADER_TRANSFER_ENCODING:
      return true;
    case HTTP2_HEADER_TE:
      return value !== "trailers";
    default:
      return false;
  }
}

const assertValidPseudoHeader = hideStackFrames((key) => {
  if (!kValidPseudoHeaders.has(key)) {
    throw new ERR_HTTP2_INVALID_PSEUDOHEADER(key);
  }
});

const assertValidPseudoHeaderResponse = hideStackFrames((key) => {
  if (key !== ":status") {
    throw new ERR_HTTP2_INVALID_PSEUDOHEADER(key);
  }
});

const assertValidPseudoHeaderTrailer = hideStackFrames((key) => {
  throw new ERR_HTTP2_INVALID_PSEUDOHEADER(key);
});

/**
 * Takes a request headers array, validates it and sets defaults, and returns
 * the resulting headers in NgHeaders string list format.
 * @returns {object}
 */

View on GitHub (pinned to 9ad36f7a2c)

Solutions

  1. Inspect the thrown message for the offending key and rename it to a normal header without the leading ':' (unless it is one of the six standard pseudo-headers).
  2. If you are proxying, strip or map unknown ':'-prefixed keys before calling request().
  3. Use the constants http2.constants.HTTP2_HEADER_METHOD / :PATH / :AUTHORITY / :SCHEME / :PROTOCOL / :STATUS instead of hand-typing pseudo-header names.

Example fix

// before
const headers = [':method', 'GET', ':ver', '2']; // ':ver' is not a pseudo-header

// after
const headers = [':method', 'GET', 'x-ver', '2'];
Defensive patterns

Strategy: validation

Validate before calling

const VALID = new Set([':status', ':method', ':authority', ':scheme', ':path', ':protocol']);
const safe = headers.filter((k, i) => i % 2 === 1 || !String(k).startsWith(':') || VALID.has(k));

Type guard

const isPseudoHeader = (k: string) => k.startsWith(':');
const isValidPseudo = (k: string) =>
  [':status', ':method', ':authority', ':scheme', ':path', ':protocol'].includes(k);

Try / catch

try { client.request(h); } catch (e) { if (e.code === 'ERR_HTTP2_INVALID_PSEUDOHEADER') { /* message contains the bad key; rename or strip it */ } throw e; }

Prevention

When it happens

Trigger: Passing a request headers array containing a ':'-prefixed key outside the valid set, e.g. [':method','GET', ':custom','x'] to http2session.request() or client.request(); assertValidPseudoHeader (util.ts:611) rejects ':custom'.

Common situations: Forwarding arbitrary header maps from proxies/gateways that already mangled ':'-prefixed names, building headers dynamically from user input where a stray ':' prefixes a normal header, or copying gRPC-style metadata (':path' ok, ':grpc-custom' not) into plain HTTP/2 requests.

Related errors


AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20). Data as JSON: /api/errors/872204b8bf6ba3a5. Report an issue: GitHub.