{"record":{"id":"d69d6c11156c7ee5","repo":"siyuan-note/siyuan","slug":"undeclared-plugin-websocket-frame-d","errorCode":null,"errorMessage":"undeclared plugin WebSocket frame: %d","messagePattern":"undeclared plugin WebSocket frame: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/apicontract/plugin_service_protocol.go","lineNumber":268,"sourceCode":"\t\t\treturn err\n\t\t}\n\tcase PluginServiceProtoBuf:\n\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, \"$\")","sourceCodeStart":250,"sourceCodeEnd":286,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/apicontract/plugin_service_protocol.go#L250-L286","documentation":"Bundle.ValidatePluginServiceFrame validates WebSocket frames sent on a plugin service endpoint. Only the data frames 1 (text) and 2 (binary) and control frames 8 (close), 9 (ping), and 10 (pong) are declared; any other opcode — including reserved ones (3-7, 11-15) and continuation frame 0 — is rejected with \"undeclared plugin WebSocket frame: %d\". The message includes the offending opcode so you can identify which frame type slipped through.","triggerScenarios":"Calling Bundle.ValidatePluginServiceFrame with frameType values outside {1,2,8,9,10}: opcode 0 (continuation frames of fragmented messages), opcodes 3-7 or 11-15 (reserved), or a raw integer from an untrusted source passed straight into the validator.","commonSituations":"A plugin relays raw frames from a client or upstream and forwards continuation frames (opcode 0) of fragmented messages individually; a custom protocol layer invents an opcode; tests feed arbitrary integers to check validator behavior; a non-WebSocket value like 80/100 is passed by mistake.","solutions":["Restrict emitted frames to text (1), binary (2), close (8), ping (9), and pong (10); reassemble fragmented messages before forwarding","Treat opcode 0 continuation frames as part of their initial data/binary frame rather than validating them separately","Sanitize any frame type read from external input before passing it to the validator","Log the offending opcode from the error message and map it to the sending code path"],"exampleFix":"// before\nvalidateFrame(msg.Opcode, msg.Payload) // forwards opcode 0 continuations\n// after\nif msg.Opcode == 0 { bufferForAssembly(msg); return }\nvalidateFrame(msg.Opcode, msg.Payload)","handlingStrategy":"validation","validationCode":"func isDeclaredFrameType(t int) bool { return t == 1 || t == 2 || t == 8 || t == 9 || t == 10 }","typeGuard":null,"tryCatchPattern":"if err := bundle.ValidatePluginServiceFrame(method, path, frameType, payload); err != nil { if strings.Contains(err.Error(), \"undeclared plugin WebSocket frame\") { reassembleFragmentedMessage(); return }; return err }","preventionTips":["Reassemble fragmented messages (opcode 0) before validating or forwarding frames","Map reserved opcodes to errors at the read boundary instead of passing them on","Constrain frame types to an enum in plugin code","Fuzz or table-test the frame validator with opcodes 0-15"],"tags":["websocket","protocol-violation","validation","plugin-api"],"backgroundTag":"unsupported-enum-value","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"}