{"record":{"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":252,"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":234,"sourceCodeEnd":270,"githubUrl":"https://github.com/websockets/ws/blob/c791e707eab3c13dd9a261d2479c3cc4a49a6fed/lib/permessage-deflate.js#L234-L270","documentation":"Thrown by `PerMessageDeflate.normalizeParams()` when a parameter appears more than once in a single extension configuration, i.e. its value array (produced by `extension.parse()` which collects repeats) has length > 1. RFC 7692 requires each parameter to occur at most once per offer, so duplicates are rejected.","triggerScenarios":"A peer sends `Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits=10; client_max_window_bits=12`; after parsing this becomes `{ client_max_window_bits: ['10','12'] }` and `normalizeParams` throws. Triggered internally during `accept()`.","commonSituations":"A buggy client or proxy duplicates a parameter; a hand-crafted header with repeated keys; fuzzer input that stresses the handshake.","solutions":["Send each parameter at most once per extension offer.","Generate the header with `extension.format()`, which never emits duplicates.","Catch the error during the handshake and close with code 1002 (protocol error)."],"exampleFix":"// before — duplicated parameter\nextension.parse('permessage-deflate; client_max_window_bits=10; client_max_window_bits=12');\npmd.normalizeParams(/* its output */);\n\n// after — single value\nextension.parse('permessage-deflate; client_max_window_bits=12');","handlingStrategy":"validation","validationCode":"// Reject duplicate parameters before calling normalizeParams/accept.\nfunction hasDuplicateParams(offer) {\n  return Object.values(offer).some((v) => Array.isArray(v) && v.length > 1);\n}\nif (parsedOffers.some(hasDuplicateParams)) {\n  abortHandshake(socket, 1002);\n}","typeGuard":null,"tryCatchPattern":"try {\n  perMessageDeflate.accept(parsedOffers);\n} catch (err) {\n  if (/must have only a single value/.test(err.message)) {\n    abortHandshake(socket, 1002);\n    return;\n  }\n  throw err;\n}","preventionTips":["Send each permessage-deflate parameter at most once per offer.","Use `extension.format()` to emit offers/responses (it never duplicates).","Validate parsed offers for duplicate keys before negotiation."],"tags":["websocket","compression","permessage-deflate","negotiation","header","rfc7692"],"backgroundTag":null,"analyzedSha":"c791e707eab3c13dd9a261d2479c3cc4a49a6fed","analyzedAt":"2026-08-06T19:07:51.047Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}