{"record":{"id":"7e3a6007e63da937","repo":"siyuan-note/siyuan","slug":"invalid-plugin-jsonp-response","errorCode":null,"errorMessage":"invalid plugin JSONP response","messagePattern":"invalid plugin JSONP response","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/apicontract/plugin_service_protocol.go","lineNumber":222,"sourceCode":"\t\tif len(payload) != 0 {\n\t\t\treturn fmt.Errorf(\"empty plugin response contains a body\")\n\t\t}\n\tcase PluginServiceJSON, PluginServiceASCIIJSON, PluginServiceIndentedJSON, PluginServicePureJSON:\n\t\tif !json.Valid(payload) {\n\t\t\treturn fmt.Errorf(\"invalid plugin JSON response\")\n\t\t}\n\tcase PluginServiceJSONP:\n\t\tvalid := json.Valid(payload)\n\t\tif tail, ok := strings.CutSuffix(string(payload), \");\"); ok {\n\t\t\tfor index, char := range tail {\n\t\t\t\tif char == '(' && json.Valid([]byte(tail[index+1:])) {\n\t\t\t\t\tvalid = true\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif !valid {\n\t\t\treturn fmt.Errorf(\"invalid plugin JSONP response\")\n\t\t}\n\tcase PluginServiceSecureJSON:\n\t\tif !json.Valid(payload) && !json.Valid([]byte(strings.TrimPrefix(string(payload), \"while(1);\"))) {\n\t\t\treturn fmt.Errorf(\"invalid plugin secure JSON response\")\n\t\t}\n\tcase PluginServiceWebSocket:\n\t\tif status == 101 && len(payload) != 0 {\n\t\t\treturn fmt.Errorf(\"WebSocket handshake contains a body\")\n\t\t}\n\tcase PluginServiceXML:\n\t\tdecoder := xml.NewDecoder(strings.NewReader(string(payload)))\n\t\tfor {\n\t\t\tif _, err := decoder.Token(); err != nil {\n\t\t\t\tif err == io.EOF {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\treturn err\n\t\t\t}","sourceCodeStart":204,"sourceCodeEnd":240,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/apicontract/plugin_service_protocol.go#L204-L240","documentation":"For endpoints declared with PluginServiceJSONP, ValidatePluginServiceResponse requires the payload to be a JSONP envelope: either a bare JSON value or a JavaScript-style call like callback({...}); (a trailing ');' is tolerated). The validator scans for a '(' and checks that everything after it parses as JSON; if neither form is valid it returns \"invalid plugin JSONP response\". This enforces that the JSONP contract is met so browsers can execute the wrapper as a function call.","triggerScenarios":"Calling Bundle.ValidatePluginServiceResponse with mode PluginServiceJSONP and a payload that is neither valid JSON nor of the form prefix(json)[;] — e.g. an empty string, plain text, or a callback wrapper whose inner payload is malformed JSON.","commonSituations":"The plugin handler wraps a non-JSON body in the callback name; the inner JSON was truncated or produced by string concatenation; the response is plain HTML from an error page; the callback name itself contains characters that break the parenthesis scan.","solutions":["Marshal the inner payload to valid JSON first, then wrap it as callbackName(<json>) (optionally with a trailing semicolon)","Validate the inner JSON with json.Valid before wrapping it in the callback","Check that the response is not an error page or empty body being validated as JSONP","Return the endpoint in a JSON mode instead of JSONP if no client-side callback wrapper is actually needed"],"exampleFix":"// before\nfmt.Fprintf(w, \"%s(%s)\", callback, rawBody)\n// after\ninner, _ := json.Marshal(data)\nfmt.Fprintf(w, \"%s(%s);\", callback, inner)","handlingStrategy":"validation","validationCode":"func isValidJSONP(payload []byte) bool {\n  s := string(payload)\n  if json.Valid(payload) { return true }\n  if tail, ok := strings.CutSuffix(s, \");\"); ok {\n    for i, c := range tail { if c == '(' && json.Valid([]byte(tail[i+1:])) { return true } }\n  }\n  return false\n}","typeGuard":null,"tryCatchPattern":"if err := bundle.ValidatePluginServiceResponse(method, path, PluginServiceJSONP, status, ct, payload); err != nil { if strings.Contains(err.Error(), \"invalid plugin JSONP response\") { rewrapAsJSONPAndRetry(); return }; return err }","preventionTips":["Build JSONP bodies by marshaling the inner value first, then wrapping in callback(...)","Validate the inner JSON with json.Valid before wrapping","Do not emit JSONP for error pages; return an HTTP error status instead","Test the exact bytes your handler emits for JSONP endpoints"],"tags":["jsonp","json","validation","plugin-api"],"backgroundTag":"invalid-json-response","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"}