{"record":{"id":"0c11d2a72342b7aa","repo":"siyuan-note/siyuan","slug":"plugin-websocket-control-frame-exceeds-125-bytes","errorCode":null,"errorMessage":"plugin WebSocket control frame exceeds 125 bytes","messagePattern":"plugin WebSocket control frame exceeds 125 bytes","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/apicontract/plugin_service_protocol.go","lineNumber":271,"sourceCode":"\t\tfor len(payload) > 0 {\n\t\t\t_, _, size := protowire.ConsumeField(payload)\n\t\t\tif size < 0 {\n\t\t\t\treturn protowire.ParseError(size)\n\t\t\t}\n\t\t\tpayload = payload[size:]\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (b *Bundle) ValidatePluginServiceFrame(method, path string, frameType int, payload []byte) error {\n\tfor _, endpoint := range b.Endpoints {\n\t\tif endpoint.Method == method && endpoint.Path == path && endpoint.PluginService != nil {\n\t\t\tif frameType != 1 && frameType != 2 && frameType != 8 && frameType != 9 && frameType != 10 {\n\t\t\t\treturn fmt.Errorf(\"undeclared plugin WebSocket frame: %d\", frameType)\n\t\t\t}\n\t\t\tif frameType >= 8 && len(payload) > 125 {\n\t\t\t\treturn fmt.Errorf(\"plugin WebSocket control frame exceeds 125 bytes\")\n\t\t\t}\n\t\t\treturn nil\n\t\t}\n\t}\n\treturn fmt.Errorf(\"unregistered plugin service: %s %s\", method, path)\n}\n\nfunc (b *Bundle) ValidatePluginServiceEvent(method, path string, payload []byte) error {\n\tfor _, endpoint := range b.Endpoints {\n\t\tif endpoint.Method == method && endpoint.Path == path && endpoint.PluginService != nil {\n\t\t\tvar event any\n\t\t\tif err := json.Unmarshal(payload, &event); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\treturn b.validate(endpoint.PluginService.SSEEvent, event, \"$\")\n\t\t}\n\t}\n\treturn fmt.Errorf(\"unregistered plugin service: %s %s\", method, path)","sourceCodeStart":253,"sourceCodeEnd":289,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/apicontract/plugin_service_protocol.go#L253-L289","documentation":"WebSocket control frames (opcodes 8 close, 9 ping, 10 pong) are limited by RFC 6455 to a payload of 125 bytes because they must fit in a single unfragmented frame. Bundle.ValidatePluginServiceFrame enforces this: when frameType >= 8 and len(payload) > 125 it returns \"plugin WebSocket control frame exceeds 125 bytes\". Oversized control frames break interoperability and are commonly rejected by other WebSocket implementations.","triggerScenarios":"Calling Bundle.ValidatePluginServiceFrame with frameType 8, 9, or 10 and a payload longer than 125 bytes — e.g. a close frame carrying a long reason string, or a ping used to transport application data.","commonSituations":"A close status/reason string longer than 125 bytes is packed into a close frame; an application misuses ping/pong as a data channel; a plugin echoes back an upstream payload as a pong without truncating it; automated tests generate large control payloads.","solutions":["Truncate or drop the close-frame reason text so the control payload is at most 125 bytes (2 status bytes + reason)","Send the data through text/binary frames (opcodes 1/2) instead of piggybacking on ping/pong","Trim the payload to 125 bytes before emitting any control frame","Add a size assertion in plugin tests for all close/ping/pong payloads"],"exampleFix":"// before\nconn.WriteControl(8, append(statusBytes, longReason...), deadline)\n// after\nreason := longReason\nif len(reason) > 123 { reason = reason[:123] }\nconn.WriteControl(8, append(statusBytes, reason...), deadline)","handlingStrategy":"validation","validationCode":"func controlFrameFits(t int, payload []byte) bool { return t < 8 || len(payload) <= 125 }","typeGuard":null,"tryCatchPattern":"if err := bundle.ValidatePluginServiceFrame(method, path, frameType, payload); err != nil { if strings.Contains(err.Error(), \"control frame exceeds 125 bytes\") { truncateControlPayload(); return }; return err }","preventionTips":["Keep close-frame reasons within 123 bytes after the 2-byte status code","Never transport application data in ping/pong frames","Truncate control payloads before WriteControl","Assert control-frame sizes in plugin tests"],"tags":["websocket","payload-too-large","protocol-violation","plugin-api"],"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"}