c4a856dc10
* fix(backend): reject disallowed cross-origin state changes in CORS The CORS middleware only aborted disallowed origins on the OPTIONS preflight, then called c.Next() for every other method. A CORS-safelisted request (e.g. a multipart/form-data POST, which skips preflight) from a malicious page therefore still executed the handler's side effect even though the browser could not read the response. Against a loopback-bound backend this is a drive-by CSRF primitive: any web page the victim visits could POST to a mutating endpoint such as /api/plugins/install, which unpacks and spawns the uploaded plugin binary (RCE), with no authentication layer in front of it. Now a disallowed cross-origin request is rejected with 403 for the preflight and every state-changing method; only CORS-safe methods (GET/HEAD) fall through without CORS headers, so the browser still cannot read the response and no state is changed. No-Origin (CLI/curl) and allowed same/loopback-origin requests are unaffected. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(backend): block all disallowed cross-origin requests in CORS Addresses review: GET is not universally side-effect-free here. Plugin webhooks are registered for GET as well as POST (/api/plugins/:id/webhooks/:key -> Service.InvokeWebhook), so the earlier GET/HEAD whitelist still let a disallowed-origin browser GET trigger that unauthenticated side effect via drive-by CSRF. Reject every method (including GET/HEAD) with 403 for a disallowed origin. This loses nothing legitimate: a response the browser could read cross-origin needs CORS headers we only emit for allowed origins. Non-browser clients (CLI/curl) and resource loads (<img>/<script>) send no Origin and take the wildcard branch, so they are unaffected. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Kandev Agent <agent@kandev.dev> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>