{"id":"3ded9ed5c39a423c","repo":"websockets/ws","slug":"an-invalid-or-duplicated-subprotocol-was-specified","errorCode":null,"errorMessage":"An invalid or duplicated subprotocol was specified","messagePattern":"An invalid or duplicated subprotocol was specified","errorType":"exception","errorClass":"SyntaxError","httpStatus":null,"severity":"error","filePath":"lib/websocket.js","lineNumber":786,"sourceCode":"\n  if (opts.perMessageDeflate) {\n    perMessageDeflate = new PerMessageDeflate({\n      ...opts.perMessageDeflate,\n      isServer: false,\n      maxPayload: opts.maxPayload\n    });\n    opts.headers['Sec-WebSocket-Extensions'] = format({\n      [PerMessageDeflate.extensionName]: perMessageDeflate.offer()\n    });\n  }\n  if (protocols.length) {\n    for (const protocol of protocols) {\n      if (\n        typeof protocol !== 'string' ||\n        !subprotocolRegex.test(protocol) ||\n        protocolSet.has(protocol)\n      ) {\n        throw new SyntaxError(\n          'An invalid or duplicated subprotocol was specified'\n        );\n      }\n\n      protocolSet.add(protocol);\n    }\n\n    opts.headers['Sec-WebSocket-Protocol'] = protocols.join(',');\n  }\n  if (opts.origin) {\n    if (opts.protocolVersion < 13) {\n      opts.headers['Sec-WebSocket-Origin'] = opts.origin;\n    } else {\n      opts.headers.Origin = opts.origin;\n    }\n  }\n  if (parsedUrl.username || parsedUrl.password) {\n    opts.auth = `${parsedUrl.username}:${parsedUrl.password}`;","sourceCodeStart":768,"sourceCodeEnd":804,"githubUrl":"https://github.com/websockets/ws/blob/ae1de54330cef77e487548890fabfeb9aae1d83d/lib/websocket.js#L768-L804","documentation":"Thrown by initAsClient() (lib/websocket.js:779-789) when iterating the requested subprotocols list: each entry must be a string, must match the subprotocolRegex (/^[!#$%&'*+\\-.0-9A-Z^_`|a-z~]+$/), and must not already have been seen (tracked via a Set). Any failure of those three checks throws SyntaxError. This validates the protocols array the caller passes to the WebSocket constructor.","triggerScenarios":"Constructing new WebSocket(url, protocols) where protocols is an array containing a non-string (e.g. [123]), a string with invalid characters (e.g. ['my protocol'] with a space), or a duplicate (e.g. ['chat', 'chat']). Also triggered by the single-string form new WebSocket(url, 'bad protocol').","commonSituations":"Passing numeric or object protocol identifiers; allowing spaces or slashes in protocol names; concatenating user protocols with library defaults without dedup; reading protocols from config that contains whitespace.","solutions":["Ensure each protocol is a string matching /^[!#$%&'*+\\-.0-9A-Z^_`|a-z~]+$/ (alphanumerics and a few symbols, no spaces).","Deduplicate the array: [...new Set(protocols)].","Sanitize/validate the protocols array before constructing the client."],"exampleFix":"// before\nnew WebSocket(url, ['chat', 'chat', 'my protocol']);\n\n// after\nnew WebSocket(url, [...new Set(['chat', 'my-protocol'])]);","handlingStrategy":"validation","validationCode":"const TOKEN_RE = /^[!#$%&'*+\\-.0-9A-Z^_`|a-z~]+$/;\nfunction normalizeProtocols(protocols) {\n  const arr = Array.isArray(protocols) ? protocols : protocols != null ? [protocols] : [];\n  const seen = new Set();\n  const out = [];\n  for (const p of arr) {\n    if (typeof p !== 'string' || !TOKEN_RE.test(p) || seen.has(p)) continue;\n    seen.add(p);\n    out.push(p);\n  }\n  return out;\n}\n// usage: new WebSocket(url, normalizeProtocols(input))","typeGuard":"const TOKEN_RE = /^[!#$%&'*+\\-.0-9A-Z^_`|a-z~]+$/;\nfunction isValidProtocolList(protocols) {\n  const seen = new Set();\n  for (const p of protocols) {\n    if (typeof p !== 'string' || !TOKEN_RE.test(p) || seen.has(p)) return false;\n    seen.add(p);\n  }\n  return true;\n}","tryCatchPattern":"try {\n  new WebSocket(url, protocols);\n} catch (err) {\n  if (/invalid or duplicated subprotocol/.test(err.message)) {\n    new WebSocket(url, normalizeProtocols(protocols));\n  } else {\n    throw err;\n  }\n}","preventionTips":["Validate each protocol is a string matching the token regex before constructing.","Deduplicate the list to avoid the duplicate-subprotocol branch.","Sanitize protocol identifiers from config/user input at the boundary."],"tags":["websocket","client","subprotocol","input-validation"],"analyzedSha":"ae1de54330cef77e487548890fabfeb9aae1d83d","analyzedAt":"2026-08-03T19:11:18.437Z","schemaVersion":2}