Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions companion/src/wire.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,13 +136,21 @@ function scrubEvent(event: string): string {
if (!line.startsWith("data:")) return line;
const raw = line.slice(5).trimStart();
if (!raw) return line;
let parsed;
try {
return `data: ${JSON.stringify(scrub(JSON.parse(raw)))}`;
parsed = JSON.parse(raw);
} catch {
// not JSON: pass it through rather than dropping it. A frame this
// code does not understand is still the harness's to send.
return line;
}
Comment on lines +139 to 144
try {
return `data: ${JSON.stringify(scrub(parsed))}`;
} catch {
// scrub() recurses, so a body nested deep enough throws RangeError
// where JSON.parse handles it fine. Passing the original line through
// sends exactly what the scrubber exists to withhold — the same class
// of issue the proxy's JSON response path already handles.
return "data: {}";
}
})
.join(eol);
}
13 changes: 13 additions & 0 deletions companion/test/wire.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,4 +166,17 @@ describe("createSseScrubber", () => {
it("still handles a bare CR, which the spec also allows", () => {
expect(createSseScrubber()('data: {"a":1,"resumeCursors":{}}\r\r')).toBe('data: {"a":1}\r\r');
});

it("replaces unscrubable JSON data instead of leaking withheld keys", () => {
// scrub() recurses; a body nested a few thousand deep throws RangeError
// where JSON.parse handles it fine. A single try-catch around both treated
// that as "not JSON" and returned the original unscrubbed line — sending
// exactly what the scrubber exists to withhold.
let json = '{"resumeCursors":{"ghost":"leak-me"}}';
for (let i = 0; i < 12_000; i++) json = `{"n":${json}}`;
const out = createSseScrubber()(`data: ${json}\n\n`);
expect(out).not.toContain("resumeCursors");
expect(out).not.toContain("leak-me");
expect(out).toBe("data: {}\n\n");
});
});