{"record":{"id":"bd09d47da219a05f","repo":"aio-libs/aiohttp","slug":"handshake-error-key-r","errorCode":null,"errorMessage":"Handshake error: {key!r}","messagePattern":"Handshake error: (.+?)","errorType":"http","errorClass":"HTTPBadRequest","httpStatus":400,"severity":"error","filePath":"aiohttp/web_ws.py","lineNumber":317,"sourceCode":"            else:\n                # No overlap found: Return no protocol as per spec\n                ws_logger.warning(\n                    \"%s: Client protocols %r don’t overlap server-known ones %r\",\n                    request.remote,\n                    req_protocols,\n                    self._protocols,\n                )\n\n        # check supported version\n        version = headers.get(hdrs.SEC_WEBSOCKET_VERSION, \"\")\n        if version not in (\"13\", \"8\", \"7\"):\n            raise HTTPBadRequest(text=f\"Unsupported version: {version}\")\n\n        # check client handshake for validity\n        key = headers.get(hdrs.SEC_WEBSOCKET_KEY)\n        try:\n            if not key or len(base64.b64decode(key)) != 16:\n                raise HTTPBadRequest(text=f\"Handshake error: {key!r}\")\n        except binascii.Error:\n            raise HTTPBadRequest(text=f\"Handshake error: {key!r}\") from None\n\n        accept_val = base64.b64encode(\n            hashlib.sha1(key.encode() + WS_KEY).digest()\n        ).decode()\n        response_headers = CIMultiDict(\n            {\n                hdrs.UPGRADE: \"websocket\",\n                hdrs.CONNECTION: \"upgrade\",\n                hdrs.SEC_WEBSOCKET_ACCEPT: accept_val,\n            }\n        )\n\n        notakeover = False\n        compress = 0\n        if self._compress:\n            extensions = headers.get(hdrs.SEC_WEBSOCKET_EXTENSIONS)","sourceCodeStart":299,"sourceCodeEnd":335,"githubUrl":"https://github.com/aio-libs/aiohttp/blob/d041d4d0fd48c3f0832084d33be16cf1c4835f85/aiohttp/web_ws.py#L299-L335","documentation":"Branch 1 of the handshake-key check (web_ws.py:317): if the Sec-WebSocket-Key header is missing/falsy OR its base64-decoded form is not exactly 16 bytes, aiohttp returns HTTP 400. Per RFC 6455 §4.1, the client must send a 16-byte random value base64-encoded in this header. A missing or wrong-length key means the handshake is invalid.","triggerScenarios":"Client omits Sec-WebSocket-Key; client sends a key that base64-decodes to fewer/more than 16 bytes (e.g. a fixed test string); a proxy that strips or rewrites the header; a malformed probe request.","commonSituations":"Hand-crafted test requests that reuse a non-16-byte key; bots/scanners probing WS endpoints; intermediaries that rewrite headers; clients built on broken libraries.","solutions":["On the client, generate a correct key: import os, base64; key = base64.b64encode(os.urandom(16)).decode(). The aiohttp client and browsers do this automatically.","Guard with ws.can_prepare(request) and return a friendly 400 instead of letting the framework's default error render.","If you control the client, prefer aiohttp.ClientSession().ws_connect(url) over a hand-built handshake."],"exampleFix":"// before — hand-built client key\n// headers['Sec-WebSocket-Key'] = 'fixed-test-key'  # wrong length\n// after\nimport os, base64\nheaders['Sec-WebSocket-Key'] = base64.b64encode(os.urandom(16)).decode()","handlingStrategy":"validation","validationCode":"import base64, binascii\n\ndef valid_ws_key(request) -> bool:\n    key = request.headers.get('Sec-WebSocket-Key')\n    if not key:\n        return False\n    try:\n        return len(base64.b64decode(key)) == 16\n    except binascii.Error:\n        return False\n\nif not valid_ws_key(request):\n    return web.Response(status=400, text='invalid Sec-WebSocket-Key')","typeGuard":"import base64, binascii\n\ndef is_well_formed_ws_key(key: str | None) -> bool:\n    if not isinstance(key, str) or not key:\n        return False\n    try:\n        return len(base64.b64decode(key, validate=True)) == 16\n    except (binascii.Error, ValueError):\n        return False","tryCatchPattern":"ws = web.WebSocketResponse()\ntry:\n    await ws.prepare(request)\nexcept web.HTTPBadRequest as e:\n    if 'Handshake error' in (e.text or ''):\n        log.warning('client sent malformed Sec-WebSocket-Key')\n    return","preventionTips":["Generate keys on the client with base64.b64encode(os.urandom(16)).","Prefer aiohttp.ClientSession.ws_connect over hand-built handshakes.","Treat handshake 400s as expected for malformed/probe traffic."],"tags":["websocket","handshake","sec-websocket-key","rfc6455"],"backgroundTag":null,"analyzedSha":"d041d4d0fd48c3f0832084d33be16cf1c4835f85","analyzedAt":"2026-08-11T20:44:15.550Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}