Skip to content

Commit 4b7011b

Browse files
JoviDeCroockphryneaskitten
authored
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>
1 parent b4c46f2 commit 4b7011b

File tree

3 files changed

+31
-1
lines changed

3 files changed

+31
-1
lines changed

Diff for: .changeset/brave-buses-thank.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@urql/next': patch
3+
---
4+
5+
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 for: packages/next-urql/src/DataHydrationContext.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import * as React from 'react';
22
import { ServerInsertedHTMLContext } from 'next/navigation';
33
import type { UrqlResult } from './useUrqlValue';
4+
import { htmlEscapeJsonString } from './htmlescape';
45

56
interface DataHydrationValue {
67
isInjecting: boolean;
@@ -19,7 +20,7 @@ const DataHydrationContext = React.createContext<
1920

2021
function transportDataToJS(data: any) {
2122
const key = 'urql_transport';
22-
return `(window[Symbol.for("${key}")] ??= []).push(${JSON.stringify(data)})`;
23+
return `(window[Symbol.for("${key}")] ??= []).push(${htmlEscapeJsonString(JSON.stringify(data))})`;
2324
}
2425

2526
export const DataHydrationContextProvider = ({

Diff for: packages/next-urql/src/htmlescape.ts

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// --------------------------------------------------------------------------------
2+
//
3+
// copied from
4+
// https://github.com/vercel/next.js/blob/6bc07792a4462a4bf921a72ab30dc4ab2c4e1bda/packages/next/src/server/htmlescape.ts
5+
// License: https://github.com/vercel/next.js/blob/6bc07792a4462a4bf921a72ab30dc4ab2c4e1bda/packages/next/license.md
6+
//
7+
// --------------------------------------------------------------------------------
8+
9+
// This utility is based on https://github.com/zertosh/htmlescape
10+
// License: https://github.com/zertosh/htmlescape/blob/0527ca7156a524d256101bb310a9f970f63078ad/LICENSE
11+
12+
const ESCAPE_LOOKUP: { [match: string]: string } = {
13+
"&": "\\u0026",
14+
">": "\\u003e",
15+
"<": "\\u003c",
16+
"\u2028": "\\u2028",
17+
"\u2029": "\\u2029",
18+
};
19+
20+
export const ESCAPE_REGEX = /[&><\u2028\u2029]/g;
21+
22+
export function htmlEscapeJsonString(str: string): string {
23+
return str.replace(ESCAPE_REGEX, (match) => ESCAPE_LOOKUP[match]);
24+
}

0 commit comments

Comments
 (0)