{"id":"d114715b4c2bde20","repo":"websockets/ws","slug":"parameter-key-must-have-only-a-single-value","errorCode":null,"errorMessage":"Parameter \"${key}\" must have only a single value","messagePattern":"Parameter \"(.+?)\" must have only a single value","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"lib/permessage-deflate.js","lineNumber":250,"sourceCode":"    }\n\n    return params;\n  }\n\n  /**\n   * Normalize parameters.\n   *\n   * @param {Array} configurations The extension negotiation offers/reponse\n   * @return {Array} The offers/response with normalized parameters\n   * @private\n   */\n  normalizeParams(configurations) {\n    configurations.forEach((params) => {\n      Object.keys(params).forEach((key) => {\n        let value = params[key];\n\n        if (value.length > 1) {\n          throw new Error(`Parameter \"${key}\" must have only a single value`);\n        }\n\n        value = value[0];\n\n        if (key === 'client_max_window_bits') {\n          if (value !== true) {\n            const num = +value;\n            if (!Number.isInteger(num) || num < 8 || num > 15) {\n              throw new TypeError(\n                `Invalid value for parameter \"${key}\": ${value}`\n              );\n            }\n            value = num;\n          } else if (!this._isServer) {\n            throw new TypeError(\n              `Invalid value for parameter \"${key}\": ${value}`\n            );\n          }","sourceCodeStart":232,"sourceCodeEnd":268,"githubUrl":"https://github.com/websockets/ws/blob/ae1de54330cef77e487548890fabfeb9aae1d83d/lib/permessage-deflate.js#L232-L268","documentation":"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.","triggerScenarios":"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].","commonSituations":"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.","solutions":["Inspect the raw Sec-WebSocket-Extensions header for any parameter that appears more than once within a single extension configuration and remove the duplicate.","If calling normalizeParams()/accept() directly, ensure each configuration object has at most one value per parameter key.","Fix the peer that generates the duplicated parameter.","Rely on the ws internal handshake handler which catches this and aborts with HTTP 400 — ensure the malformed header is not bypassing it."],"exampleFix":"// before — duplicated parameter in raw header\nconst header = 'permessage-deflate; client_max_window_bits=10; client_max_window_bits=12';\n\n// after\nconst header = 'permessage-deflate; client_max_window_bits=10';","handlingStrategy":"validation","validationCode":"// Before calling accept/normalizeParams, ensure no parameter has multiple values\nfunction hasDuplicateParams(offers) {\n  return offers.some((config) =>\n    Object.values(config).some((val) => Array.isArray(val) && val.length > 1))\n  );\n}\n\nif (!hasDuplicateParams(parsedOffers)) {\n  perMessageDeflate.accept(parsedOffers);\n}","typeGuard":null,"tryCatchPattern":"try {\n  perMessageDeflate.accept(offers);\n} catch (err) {\n  if (err.message.includes('must have only a single value')) {\n    console.warn('Duplicate parameter in offer — rejecting negotiation:', err.message);\n  } else {\n    throw err;\n  }\n}","preventionTips":["Ensure the Sec-WebSocket-Extensions header does not repeat any parameter within a single extension configuration.","When building offers programmatically, use unique keys.","Rely on the ws internal handshake handler to catch this and return HTTP 400 — do not bypass it.","Validate parsed offers for duplicate-value arrays before calling accept()."],"tags":["websocket","permessage-deflate","compression","handshake","negotiation"],"analyzedSha":"ae1de54330cef77e487548890fabfeb9aae1d83d","analyzedAt":"2026-08-03T19:11:18.437Z","schemaVersion":2}