websockets/ws · error · Error

Parameter "${key}" must have only a single value

Error message

Parameter "${key}" must have only a single value

What it means

Thrown by PerMessageDeflate.normalizeParams() (permessage-deflate.js:249-250) when a parameter key has more than one value in a single configuration object. The parser represents each parameter's value(s) as an array; if that array's length exceeds 1, the negotiation is rejected because permessage-deflate parameters are defined to have at most one value per RFC 7692.

Source

Thrown at lib/permessage-deflate.js:250

    }

    return params;
  }

  /**
   * Normalize parameters.
   *
   * @param {Array} configurations The extension negotiation offers/reponse
   * @return {Array} The offers/response with normalized parameters
   * @private
   */
  normalizeParams(configurations) {
    configurations.forEach((params) => {
      Object.keys(params).forEach((key) => {
        let value = params[key];

        if (value.length > 1) {
          throw new Error(`Parameter "${key}" must have only a single value`);
        }

        value = value[0];

        if (key === 'client_max_window_bits') {
          if (value !== true) {
            const num = +value;
            if (!Number.isInteger(num) || num < 8 || num > 15) {
              throw new TypeError(
                `Invalid value for parameter "${key}": ${value}`
              );
            }
            value = num;
          } else if (!this._isServer) {
            throw new TypeError(
              `Invalid value for parameter "${key}": ${value}`
            );
          }

View on GitHub (pinned to ae1de54330)

Solutions

  1. Inspect the raw Sec-WebSocket-Extensions header for any parameter that appears more than once within a single extension configuration and remove the duplicate.
  2. If calling normalizeParams()/accept() directly, ensure each configuration object has at most one value per parameter key.
  3. Fix the peer that generates the duplicated parameter.
  4. Rely on the ws internal handshake handler which catches this and aborts with HTTP 400 — ensure the malformed header is not bypassing it.

Example fix

// before — duplicated parameter in raw header
const header = 'permessage-deflate; client_max_window_bits=10; client_max_window_bits=12';

// after
const header = 'permessage-deflate; client_max_window_bits=10';
Defensive patterns

Strategy: validation

Validate before calling

// Before calling accept/normalizeParams, ensure no parameter has multiple values
function hasDuplicateParams(offers) {
  return offers.some((config) =>
    Object.values(config).some((val) => Array.isArray(val) && val.length > 1))
  );
}

if (!hasDuplicateParams(parsedOffers)) {
  perMessageDeflate.accept(parsedOffers);
}

Try / catch

try {
  perMessageDeflate.accept(offers);
} catch (err) {
  if (err.message.includes('must have only a single value')) {
    console.warn('Duplicate parameter in offer — rejecting negotiation:', err.message);
  } else {
    throw err;
  }
}

Prevention

When it happens

Trigger: normalizeParams() is called (internally via accept()) with a configuration where one parameter maps to an array of length > 1. This happens when extension.parse() produces a parameter that appears multiple times in the same configuration segment, e.g. the raw header 'permessage-deflate; client_max_window_bits=10; client_max_window_bits=12' results in client_max_window_bits having two values [10, 12].

Common situations: A non-compliant client or server sends a Sec-WebSocket-Extensions header with a duplicated parameter in the same offer. A test fixture or hand-crafted offer object contains repeated keys. A proxy duplicates a parameter during header manipulation.

Related errors


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