websockets/ws · error · SyntaxError

Unexpected character at index ${i}

Error message

Unexpected character at index ${i}

What it means

Thrown by extension.parse() while reading an extension NAME in the Sec-WebSocket-Extensions header. The parser hit a ';' or ',' separator but start was -1, meaning no token characters had been accumulated where an extension name was expected (extension.js:51-53). This violates the RFC 6455 §9.1 ABNF which requires at least one token char before any separator.

Source

Thrown at lib/extension.js:53

  let start = -1;
  let code = -1;
  let end = -1;
  let i = 0;

  for (; i < header.length; i++) {
    code = header.charCodeAt(i);

    if (extensionName === undefined) {
      if (end === -1 && tokenChars[code] === 1) {
        if (start === -1) start = i;
      } else if (
        i !== 0 &&
        (code === 0x20 /* ' ' */ || code === 0x09) /* '\t' */
      ) {
        if (end === -1 && start !== -1) end = i;
      } else if (code === 0x3b /* ';' */ || code === 0x2c /* ',' */) {
        if (start === -1) {
          throw new SyntaxError(`Unexpected character at index ${i}`);
        }

        if (end === -1) end = i;
        const name = header.slice(start, end);
        if (code === 0x2c) {
          push(offers, name, params);
          params = Object.create(null);
        } else {
          extensionName = name;
        }

        start = end = -1;
      } else {
        throw new SyntaxError(`Unexpected character at index ${i}`);
      }
    } else if (paramName === undefined) {
      if (end === -1 && tokenChars[code] === 1) {
        if (start === -1) start = i;

View on GitHub (pinned to ae1de54330)

Solutions

  1. Inspect the raw Sec-WebSocket-Extensions header value and fix the malformed separator placement (remove leading/doubled/stray ';' and ',').
  2. If you are calling parse() directly, sanitize or reject the header string before parsing.
  3. If received from a peer, the ws server/client internally catches this and aborts the handshake with HTTP 400 — ensure you are not bypassing that internal handler.
  4. Upgrade or patch the peer that is generating the non-compliant header.

Example fix

// before
const offers = WebSocket.extension.parse(';permessage-deflate');

// after
const offers = WebSocket.extension.parse('permessage-deflate');
Defensive patterns

Strategy: try-catch

Validate before calling

// Validate that the header does not start with or double-up separators
function isValidExtensionsHeader(header) {
  if (typeof header !== 'string') return false;
  if (/^[;\s,]|[;,][;,]|[;,]$/.test(header.trim())) return false;
  return true;
}

if (isValidExtensionsHeader(header)) {
  const offers = WebSocket.extension.parse(header);
}

Type guard

function isNonEmptyHeader(header) {
  return typeof header === 'string' && header.trim().length > 0;
}

Try / catch

try {
  const offers = WebSocket.extension.parse(header);
} catch (err) {
  if (err instanceof SyntaxError) {
    // Malformed Sec-WebSocket-Extensions header — reject/log
    console.error('Bad extensions header:', err.message);
  } else {
    throw err;
  }
}

Prevention

When it happens

Trigger: Calling WebSocket.extension.parse(header) (exported as WebSocket.extension.parse) with a header string that begins with or contains a stray separator in the extension-name position, e.g. ';permessage-deflate', 'permessage-deflate,,x', or 'permessage-deflate,;x'. A malformed Sec-WebSocket-Extensions header received from a peer would also trigger this through the internal handshake path.

Common situations: A hand-crafted or buggy client sends a Sec-WebSocket-Extensions header with a leading/trailing/doubled separator. A proxy or middleware injects a malformed extensions header. A developer testing extension negotiation feeds a syntactically broken string to parse().

Related errors


AI-assisted analysis of websockets/ws@ae1de54330 (2026-08-03). Data as JSON: /data/errors/1894de79dc16fe79.json. Report an issue: GitHub.