{"id":"CVE-2026-49455","aliases":["GHSA-75w3-gmqx-993q"],"url":"https://o3.security/vulnerability/CVE-2026-49455","summary":"Waku: Cross-Origin CSRF on RSC Server Action Dispatch","details":"## Summary\n\nWaku's RSC request dispatcher invokes server actions without validating the request's `Origin` (or `Sec-Fetch-Site`) header. A cross-origin web attacker can therefore cause a victim browser to issue an authenticated `POST` to a registered server action endpoint using a CORS-safelisted content type (`text/plain`), which does not trigger a preflight. Any state-mutating server action that the application exposes via `'use server'` can be invoked with the victim's cookies attached. A working proof-of-concept demonstrates the vulnerability against waku 1.0.0-beta.0 dev server: a cross-origin POST with `Content-Type: text/plain` invokes a registered `'use server'` action and returns HTTP 200 with an RSC stream response. The same defect affects the progressive-enhancement (no-JavaScript) server action path: a cross-origin HTML form auto-submitting `multipart/form-data` reaches the dispatch through a second unguarded branch of the request handler, dynamically confirmed on 2026-05-17. Both branches were confirmed exploitable from opaque-origin contexts (sandboxed iframes, `file://` navigation, browser extension pages), which send `Origin: null` — a value no Origin guard exists to reject. This is the same vulnerability class previously disclosed for Next.js Server Actions (GHSA-mq59-m269-xvcx); waku's implementation is broader in that no Origin check exists at all in the default request handler.\n\n\n## Root cause\n\nIn `packages/waku/src/lib/utils/request.ts`:\n\n```ts\n// line 29\nif (pathname.startsWith(rscPathPrefix)) {\n  rscPath = decodeRscPath(pathname.slice(rscPathPrefix.length));\n  const actionId = decodeFuncId(rscPath);\n  if (actionId) {\n    const body = await getActionBody(req);\n    const args = await decodeReply(body, { temporaryReferences });\n    const action = await loadServerAction(actionId);\n    // ...action is invoked with attacker-supplied args\n  }\n}\n```\n\nThe sibling `else if (req.method === 'POST')` branch (line 61) does enforce a method check, but only for non-RSC paths. The RSC dispatch branch has no equivalent guard. For comparison, frameworks in the same category (Next.js, Remix) compare the request's `Origin` header against the configured host before invoking a server action.\n\n## Trigger (one-line summary)\n\nA cross-origin `POST` with `Content-Type: text/plain` to `/<rscBase>/<encoded-action-path>` reaches `loadServerAction` and invokes a registered `'use server'` action with the victim's browser-attached cookies.\n\n## Affected entry surfaces\n\n- All HTTP adapters exposed by waku (`packages/waku/src/adapters/node.ts`, `cloudflare.ts`, `vercel.ts`, `edge.ts`) — each chains through the same `request.ts:getInput` dispatcher.\n- Any server action declared with `'use server'` and reachable from the Vite module graph (i.e., normal application code).\n\n## Suggested fix outline\n\nAdd an `Origin` (and ideally `Sec-Fetch-Site`) validation step in the RSC dispatch branch of `getInput`, gated against the configured base URL host, with an opt-in `allowedOrigins` configuration option for legitimate cross-origin scenarios. Exact patch sketches will be provided in the follow-up comment.\n\n## Disclosure\n\n| Field | Value |\n|---|---|\n| Reporter | j0hndo (dohyun4466@gmail.com) |\n| Discovery date | 2026-05-15 |\n| Embargo | 90 days from acknowledgement (operator open to extension on request) |\n| Comparable precedent | Next.js GHSA-mq59-m269-xvcx (\"null origin can bypass Server Actions CSRF checks\"), CVSS 5.3, fixed Next.js 16.1.7 |\n| References | OWASP CSRF cheat sheet; Fetch spec § CORS-safelisted request-header (text/plain). |\n\n## Workarounds (publish-safe)\n\nUntil a framework-level fix is released, operators can mitigate by placing an `Origin`-validating reverse proxy or middleware in front of waku that rejects requests whose `Origin` header does not match the application's host on `POST` requests to the configured `rscBase` prefix. Note that Vite's `server.allowedHosts` guard (which rejects unrecognized `Host` headers with HTTP 403) applies only to `waku dev`; production deployments using `waku build && waku start` do not have an equivalent guard and are fully exposed without an external mitigation layer.","published":"2026-09-03T18:15:42.014Z","modified":"2026-10-21T02:58:23.425428463Z","cvss":{"score":6.5,"severity":"MEDIUM","vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:N"},"epss":{"score":0.00119,"percentile":0.02015,"asOf":"2026-09-12"},"cisaKev":null,"exploitsKnown":0,"affectedPackages":[{"ecosystem":"npm","name":"waku","fixedVersion":"1.0.0-beta.1"}],"fix":null,"references":[{"type":"WEB","url":"https://github.com/wakujs/waku/releases/tag/v1.0.0-beta.1"},{"type":"ADVISORY","url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/49xxx/CVE-2026-49455.json"},{"type":"ADVISORY","url":"https://github.com/wakujs/waku/security/advisories/GHSA-75w3-gmqx-993q"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-49455"},{"type":"PACKAGE","url":"https://github.com/wakujs/waku"}],"provenance":{"sources":["OSV.dev","FIRST.org (EPSS)"],"lastVerified":"2026-10-21T02:58:23.425428463Z"}}