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
- Inspect the raw Sec-WebSocket-Extensions header value and fix the malformed separator placement (remove leading/doubled/stray ';' and ',').
- If you are calling parse() directly, sanitize or reject the header string before parsing.
- 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.
- 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
- Never manually construct Sec-WebSocket-Extensions header strings — use the extension.format() helper.
- If calling extension.parse() directly, wrap it in try-catch and treat SyntaxError as a malformed-input signal.
- In server mode, rely on the internal handshake handler (websocket-server.js) which catches parse errors and returns HTTP 400 automatically.
- Sanitize peer-supplied headers before debugging with parse().
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
- Unexpected end of input
- Unexpected character at index ${i}
- The "${protocol}" subprotocol is duplicated
- Unexpected end of input
- None of the extension offers can be accepted
AI-assisted analysis of websockets/ws@ae1de54330 (2026-08-03).
Data as JSON: /data/errors/1894de79dc16fe79.json.
Report an issue: GitHub.