{"id":"GHSA-75w3-gmqx-993q","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 `/\u003crscBase\u003e/\u003cencoded-action-path\u003e` 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.","aliases":["CVE-2026-49455"],"modified":"2026-07-08T20:56:35.443454Z","published":"2026-07-08T20:27:11Z","database_specific":{"github_reviewed_at":"2026-07-08T20:27:11Z","nvd_published_at":null,"cwe_ids":["CWE-352"],"severity":"MODERATE","github_reviewed":true},"references":[{"type":"WEB","url":"https://github.com/wakujs/waku/security/advisories/GHSA-75w3-gmqx-993q"},{"type":"PACKAGE","url":"https://github.com/wakujs/waku"}],"affected":[{"package":{"name":"waku","ecosystem":"npm","purl":"pkg:npm/waku"},"ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"1.0.0-beta.1"}]}],"database_specific":{"last_known_affected_version_range":"\u003c= 1.0.0-beta.0","source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/07/GHSA-75w3-gmqx-993q/GHSA-75w3-gmqx-993q.json"}}],"schema_version":"1.9.0","severity":[{"type":"CVSS_V3","score":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:N"}]}