CVE-2024-24556: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') (CWE-79)
value: urql is a GraphQL client that exposes a set of helpers for several frameworks.  The `@urql/next` package is vulnerable to XSS. To exploit this an attacker would need to ensure that the response returns `html` tags and that the web-application is using streamed responses (non-RSC). This vulnerability is due to improper escaping of html-like characters in the response-stream. To fix this vulnerability upgrade to version 1.1.1
CWE description: The product does not neutralize or incorrectly neutralizes user-controllable input before it is placed in output that is used as a web page that is served to other users.
CVSS v3 base score: 6.1
Published: 2024-01-30T18:15Z
Repository: https://github.com/urql-graphql/urql
Language: TypeScript
Files changed: .changeset/brave-buses-thank.md, packages/next-urql/src/DataHydrationContext.ts, packages/next-urql/src/htmlescape.ts
Commit date: 2024-01-30 17:08:48 +0000
Commit message: Merge pull request from GHSA-qhjf-hm5j-335w

* escape html characters in JSON string before injecting it into stream

* Add changeset

---------

Co-authored-by: Lenz Weber-Tronic <lorenz.weber-tronic@apollographql.com>
Co-authored-by: Phil Pluckthun <phil@kitten.sh>
Diff: diff --git a/.changeset/brave-buses-thank.md b/.changeset/brave-buses-thank.md
new file mode 100644
index 00000000..97ab2336
--- /dev/null
+++ b/.changeset/brave-buses-thank.md
@@ -0,0 +1,5 @@
+---
+'@urql/next': patch
+---
+
+Fix `CVE-2024-24556`, addressing an XSS vulnerability, where `@urql/next` failed to escape HTML characters in JSON payloads injected into RSC hydration bodies. When an attacker is able to manipulate strings in the JSON response in RSC payloads, this could cause HTML to be evaluated via a typical XSS vulnerability (See [`GHSA-qhjf-hm5j-335w`](https://github.com/urql-graphql/urql/security/advisories/GHSA-qhjf-hm5j-335w) for details.)
diff --git a/packages/next-urql/src/DataHydrationContext.ts b/packages/next-urql/src/DataHydrationContext.ts
index a2aca921..a007914b 100644
--- a/packages/next-urql/src/DataHydrationContext.ts
+++ b/packages/next-urql/src/DataHydrationContext.ts
@@ -1,8 +1,9 @@
 import * as React from 'react';
 import { ServerInsertedHTMLContext } from 'next/navigation';
 import type { UrqlResult } from './useUrqlValue';
+import { htmlEscapeJsonString } from './htmlescape';
 
 interface DataHydrationValue {
   isInjecting: boolean;
   operationValuesByKey: Record<number, UrqlResult>;
   RehydrateScript: () =>
@@ -17,11 +18,11 @@ const DataHydrationContext = React.createContext<
   DataHydrationValue | undefined
 >(undefined);
 
 function transportDataToJS(data: any) {
   const key = 'urql_transport';
-  return `(window[Symbol.for("${key}")] ??= []).push(${JSON.stringify(data)})`;
+  return `(window[Symbol.for("${key}")] ??= []).push(${htmlEscapeJsonString(JSON.stringify(data))})`;
 }
 
 export const DataHydrationContextProvider = ({
   nonce,
   children,
diff --git a/packages/next-urql/src/htmlescape.ts b/packages/next-urql/src/htmlescape.ts
new file mode 100644
index 00000000..be7acf73
--- /dev/null
+++ b/packages/next-urql/src/htmlescape.ts
@@ -0,0 +1,24 @@
+// --------------------------------------------------------------------------------
+//
+// copied from
+// https://github.com/vercel/next.js/blob/6bc07792a4462a4bf921a72ab30dc4ab2c4e1bda/packages/next/src/server/htmlescape.ts
+// License: https://github.com/vercel/next.js/blob/6bc07792a4462a4bf921a72ab30dc4ab2c4e1bda/packages/next/license.md
+//
+// --------------------------------------------------------------------------------
+
+// This utility is based on https://github.com/zertosh/htmlescape
+// License: https://github.com/zertosh/htmlescape/blob/0527ca7156a524d256101bb310a9f970f63078ad/LICENSE
+
+const ESCAPE_LOOKUP: { [match: string]: string } = {
+  "&": "\\u0026",
+  ">": "\\u003e",
+  "<": "\\u003c",
+  "\u2028": "\\u2028",
+  "\u2029": "\\u2029",
+};
+
+export const ESCAPE_REGEX = /[&><\u2028\u2029]/g;
+
+export function htmlEscapeJsonString(str: string): string {
+  return str.replace(ESCAPE_REGEX, (match) => ESCAPE_LOOKUP[match]);
+}

Vulnerable code:   return `(window[Symbol.for("${key}")] ??= []).push(${JSON.stringify(data)})`;
Fixed code: ---
'@urql/next': patch
---

Fix `CVE-2024-24556`, addressing an XSS vulnerability, where `@urql/next` failed to escape HTML characters in JSON payloads injected into RSC hydration bodies. When an attacker is able to manipulate strings in the JSON response in RSC payloads, this could cause HTML to be evaluated via a typical XSS vulnerability (See [`GHSA-qhjf-hm5j-335w`](https://github.com/urql-graphql/urql/security/advisories/GHSA-qhjf-hm5j-335w) for details.)
import { htmlEscapeJsonString } from './htmlescape';
  return `(window[Symbol.for("${key}")] ??= []).push(${htmlEscapeJsonString(JSON.stringify(data))})`;
// --------------------------------------------------------------------------------
//
// copied from
// https://github.com/vercel/next.js/blob/6bc07792a4462a4bf921a72ab30dc4ab2c4e1bda/packages/next/src/server/htmlescape.ts
// License: https://github.com/vercel/next.js/blob/6bc07792a4462a4bf921a72ab30dc4ab2c4e1bda/packages/next/license.md
//
// --------------------------------------------------------------------------------

// This utility is based on https://github.com/zertosh/htmlescape
// License: https://github.com/zertosh/htmlescape/blob/0527ca7156a524d256101bb310a9f970f63078ad/LICENSE

const ESCAPE_LOOKUP: { [match: string]: string } = {
  "&": "\\u0026",
  ">": "\\u003e",
  "<": "\\u003c",
  "\u2028": "\\u2028",
  "\u2029": "\\u2029",
};

export const ESCAPE_REGEX = /[&><\u2028\u2029]/g;

export function htmlEscapeJsonString(str: string): string {
  return str.replace(ESCAPE_REGEX, (match) => ESCAPE_LOOKUP[match]);
}