{"record":{"id":"6e4161b0694158b1","repo":"siyuan-note/siyuan","slug":"websocket-control-frame-exceeds-125-bytes","errorCode":null,"errorMessage":"WebSocket control frame exceeds 125 bytes","messagePattern":"WebSocket control frame exceeds 125 bytes","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/apicontract/broadcast_protocol.go","lineNumber":88,"sourceCode":"\t\t\tif endpoint.SSE == nil || endpoint.SSE.Raw == nil {\n\t\t\t\treturn fmt.Errorf(\"endpoint does not declare raw SSE events\")\n\t\t\t}\n\t\t\treturn nil\n\t\t}\n\t}\n\treturn fmt.Errorf(\"unregistered API contract: %s %s\", method, path)\n}\n\nfunc (b *Bundle) ValidateRawWebSocketFrame(method, path string, incoming bool, frameType int, payload []byte) error {\n\tfor _, endpoint := range b.Endpoints {\n\t\tif endpoint.Method == method && endpoint.Path == path {\n\t\t\tif endpoint.WebSocket == nil || endpoint.WebSocket.Raw == nil {\n\t\t\t\treturn fmt.Errorf(\"endpoint does not declare raw WebSocket frames\")\n\t\t\t}\n\t\t\tfor _, allowed := range endpoint.WebSocket.Raw.Frames {\n\t\t\t\tif frameType == allowed {\n\t\t\t\t\tif frameType >= 8 && len(payload) > 125 {\n\t\t\t\t\t\treturn fmt.Errorf(\"WebSocket control frame exceeds 125 bytes\")\n\t\t\t\t\t}\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn fmt.Errorf(\"undeclared raw WebSocket frame: %d\", frameType)\n\t\t}\n\t}\n\treturn fmt.Errorf(\"unregistered API contract: %s %s\", method, path)\n}\n","sourceCodeStart":70,"sourceCodeEnd":98,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/apicontract/broadcast_protocol.go#L70-L98","documentation":"Within a raw WebSocket endpoint, a frame whose type matches an allowed type (from Raw.Frames) is additionally checked against the RFC 6455 rule that control frames (types >= 8, i.e. close 8, ping 9, pong 10) carry at most 125 bytes of payload. An allowed control frame larger than 125 bytes is rejected with this error.","triggerScenarios":"ValidateRawWebSocketFrame is called with frameType 8/9/10 (close/ping/pong) and len(payload) > 125 on an endpoint whose Raw.Frames includes that control type.","commonSituations":"Applications stuffing arbitrary status data into close/ping/pong payloads; ping keepalive implementations attaching large heartbeat blobs; fuzz or malformed-client tests sending oversized control frames.","solutions":["Trim the control-frame payload to 125 bytes or fewer","Move large data out of control frames into data frames (types 1/2)","Reject or split oversized close reasons on the producing side before sending","Add a payload-length check at the call site before invoking validation"],"exampleFix":"// before\npayload := append(closeReasonBytes, extraDiagnostics...) // > 125 bytes\nValidateRawWebSocketFrame(\"GET\", \"/ws\", true, 8, payload)\n// after\nif len(payload) > 125 { payload = payload[:125] }\nValidateRawWebSocketFrame(\"GET\", \"/ws\", true, 8, payload)","handlingStrategy":"validation","validationCode":"if (frameType >= 8 && payload.length > 125) throw new Error(\"control frame exceeds 125 bytes (RFC 6455)\");","typeGuard":"function isLegalControlPayload(t, p) { return t < 8 || p.length <= 125; }","tryCatchPattern":"try { bundle.ValidateRawWebSocketFrame(method, path, true, frameType, payload); } catch (e) { if (String(e).includes(\"exceeds 125 bytes\")) { payload = payload.slice(0, 125); retry; } else throw e; }","preventionTips":["Cap close/ping/pong payloads at 125 bytes at the send site","Keep large diagnostics out of control frames; use data frames","Add a shared constant MAX_CONTROL_PAYLOAD = 125","Fuzz-test frame validation with oversized control payloads"],"tags":["websocket","rfc6455","payload-too-large"],"backgroundTag":"payload-too-large","analyzedSha":"9f775e8a12daef8255556097396f9b2739078892","analyzedAt":"2026-09-19T03:17:15.984Z","contentChangedAt":"2026-09-19T03:17:15.984Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}