GHSA-43FC-V873-QW85
Vulnerability from github – Published: 2026-07-08 20:30 – Updated: 2026-07-08 20:30Summary
The unstable_redirect() helper exported from waku/router/server (packages/waku/src/router/define-router.tsx:156–161) accepts an arbitrary string and reflects it unchanged into the HTTP Location response header with no URL validation, scheme restriction, or path-only enforcement. Any application that passes user-controlled input to this helper — the natural pattern documented in the JSDoc and official fixtures — is vulnerable to open redirect attacks. An attacker who convinces a victim to click a crafted link can silently redirect the browser to an arbitrary external domain, enabling phishing, credential harvesting, and OAuth token theft. Additionally, scheme-relative URLs (//evil.example/) bypass naive https?://-only allow-list filters that developers might add as ad-hoc mitigations.
Dynamic PoC confirmed against waku 1.0.0-beta.0 (commit 8e9f542) in an isolated Docker environment. Two independent dynamic runs produced identical results.
Root Cause
packages/waku/src/router/define-router.tsx:156–161:
export function unstable_redirect(
location: string, // only URL `pathname` is supported.
status: 303 | 307 | 308 = 307,
): never {
throw createCustomError('Redirect', { status, location });
}
The JSDoc comment states "only URL pathname is supported", but this constraint is expressed as documentation only — the function performs no validation. The location value propagates via createCustomError (custom-errors.ts:22–26) into an error digest, is recovered by getErrorInfo in the request handler (handler.ts:79–89), and reflected directly into headers.location of the outgoing Response with no sanitization:
if (info?.location) {
headers.location = info.location; // handler.ts:87 — unvalidated reflection
}
return new Response(body, { status, headers });
Trigger (one-line summary)
Any developer-supplied user input passed to unstable_redirect() is reflected unchanged into the HTTP Location header, enabling navigation to an attacker-controlled domain.
Affected Entry Surfaces
unstable_redirect(location, status?)—waku/router/serverpublic export- Any page/route component that passes
searchParams,query, or other user- controlled strings tounstable_redirect(the standard post-login or callback redirect pattern) - All waku adapters (Node.js, Cloudflare Workers, Vercel Edge, Deno) share the same
handler.tsreflection path; cross-runtime CRLF parity is unaudited (see note below)
Additional Defense-in-Depth Concern
On Node.js, CRLF injection via the Location header is rejected by node:_http_outgoing.setHeader (ERR_INVALID_CHAR). This defense is platform-specific and not present in the waku source. Cloudflare Workers, Deno Deploy, and other edge runtimes have not been verified to offer equivalent protection. A cross-runtime audit is recommended.
Suggested Fix Outline
Validate the location argument inside unstable_redirect before the error is thrown: reject any value that does not begin with a single / (no //), and reject any value containing control characters (\x00–\x1f). An opt-in allow-list for intentional cross-origin redirects can be provided via a framework configuration option.
Disclosure
| Field | Value |
|---|---|
| Reporter | j0hndo (dohyun4466@gmail.com) |
| Discovery date | 2026-05-17 |
| Embargo | 90 days from acknowledgment |
| Patched version | Not yet available |
| Public references | CWE-601; OWASP A01:2021 |
{
"affected": [
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 1.0.0-beta.0"
},
"package": {
"ecosystem": "npm",
"name": "waku"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "1.0.0-beta.1"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-49456"
],
"database_specific": {
"cwe_ids": [
"CWE-601"
],
"github_reviewed": true,
"github_reviewed_at": "2026-07-08T20:30:21Z",
"nvd_published_at": null,
"severity": "LOW"
},
"details": "## Summary\n\nThe `unstable_redirect()` helper exported from `waku/router/server` (`packages/waku/src/router/define-router.tsx:156\u2013161`) accepts an arbitrary string and reflects it unchanged into the HTTP `Location` response header with no URL validation, scheme restriction, or path-only enforcement. Any application that passes user-controlled input to this helper \u2014 the natural pattern documented in the JSDoc and official fixtures \u2014 is vulnerable to open redirect attacks. An attacker who convinces a victim to click a crafted link can silently redirect the browser to an arbitrary external domain, enabling phishing, credential harvesting, and OAuth token theft. Additionally, scheme-relative URLs (`//evil.example/`) bypass naive `https?://`-only allow-list filters that developers might add as ad-hoc mitigations.\n\nDynamic PoC confirmed against **waku 1.0.0-beta.0** (commit `8e9f542`) in an isolated Docker environment. Two independent dynamic runs produced identical results.\n\n---\n\n## Root Cause\n\n`packages/waku/src/router/define-router.tsx:156\u2013161`:\n\n```ts\nexport function unstable_redirect(\n location: string, // only URL `pathname` is supported.\n status: 303 | 307 | 308 = 307,\n): never {\n throw createCustomError(\u0027Redirect\u0027, { status, location });\n}\n```\n\nThe JSDoc comment states \"only URL `pathname` is supported\", but this constraint is expressed as documentation only \u2014 the function performs **no validation**. The `location` value propagates via `createCustomError` (`custom-errors.ts:22\u201326`) into an error digest, is recovered by `getErrorInfo` in the request handler (`handler.ts:79\u201389`), and reflected directly into `headers.location` of the outgoing `Response` with no sanitization:\n\n```ts\nif (info?.location) {\n headers.location = info.location; // handler.ts:87 \u2014 unvalidated reflection\n}\nreturn new Response(body, { status, headers });\n```\n\n---\n\n## Trigger (one-line summary)\n\nAny developer-supplied user input passed to `unstable_redirect()` is reflected unchanged into the HTTP `Location` header, enabling navigation to an attacker-controlled domain.\n\n---\n\n## Affected Entry Surfaces\n\n- `unstable_redirect(location, status?)` \u2014 `waku/router/server` public export\n- Any page/route component that passes `searchParams`, `query`, or other user-\n controlled strings to `unstable_redirect` (the standard post-login or callback\n redirect pattern)\n- All waku adapters (Node.js, Cloudflare Workers, Vercel Edge, Deno) share the same\n `handler.ts` reflection path; cross-runtime CRLF parity is unaudited (see note\n below)\n\n---\n\n## Additional Defense-in-Depth Concern\n\nOn Node.js, CRLF injection via the `Location` header is rejected by `node:_http_outgoing.setHeader` (`ERR_INVALID_CHAR`). This defense is **platform-specific** and not present in the waku source. Cloudflare Workers, Deno Deploy, and other edge runtimes have not been verified to offer equivalent protection. A cross-runtime audit is recommended.\n\n---\n\n## Suggested Fix Outline\n\nValidate the `location` argument inside `unstable_redirect` before the error is thrown: reject any value that does not begin with a single `/` (no `//`), and reject any value containing control characters (`\\x00`\u2013`\\x1f`). An opt-in allow-list for intentional cross-origin redirects can be provided via a framework configuration option.\n\n---\n\n## Disclosure\n\n| Field | Value |\n|------------------|------------------------------------|\n| Reporter | j0hndo (`dohyun4466@gmail.com`) |\n| Discovery date | 2026-05-17 |\n| Embargo | 90 days from acknowledgment |\n| Patched version | Not yet available |\n| Public references| CWE-601; OWASP A01:2021 |",
"id": "GHSA-43fc-v873-qw85",
"modified": "2026-07-08T20:30:21Z",
"published": "2026-07-08T20:30:21Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/wakujs/waku/security/advisories/GHSA-43fc-v873-qw85"
},
{
"type": "PACKAGE",
"url": "https://github.com/wakujs/waku"
},
{
"type": "WEB",
"url": "https://github.com/wakujs/waku/releases/tag/v1.0.0-beta.1"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:N/I:L/A:N",
"type": "CVSS_V3"
}
],
"summary": "Waku has an Open Redirect via `unstable_redirect` Helper"
}
Sightings
| Author | Source | Type | Date | Other |
|---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or observed by the user.
- Confirmed: The vulnerability has been validated from an analyst's perspective.
- Published Proof of Concept: A public proof of concept is available for this vulnerability.
- Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
- Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
- Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
- Not confirmed: The user expressed doubt about the validity of the vulnerability.
- Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.