Skip to content

Commit bd84d33

Browse files
committed
review(analytics): drop bare catch, snapshot 400 body, add zip-bomb test
- Remove inner try/catch in encodeAnalyticsBody; runtime compression errors now propagate to the existing Result.error in sendAnalyticsEventBatch instead of being swallowed silently. - Convert the invalid-gzip test to toMatchInlineSnapshot for parity with other error-path tests. - Add a regression test that gzips a 9 MB zero buffer to exercise the MAX_DECOMPRESSED_BYTES guard.
1 parent fe34b10 commit bd84d33

2 files changed

Lines changed: 33 additions & 8 deletions

File tree

apps/e2e/tests/backend/endpoints/api/v1/analytics-events-batch.test.ts

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,36 @@ it("rejects a binary body that isn't valid gzip", async ({ expect }) => {
214214
rawBody: new Uint8Array([0, 1, 2, 3, 4, 5]),
215215
});
216216

217-
expect(res.status).toBe(400);
217+
expect(res).toMatchInlineSnapshot(`
218+
NiceResponse {
219+
"status": 400,
220+
"body": "Invalid encoded analytics body",
221+
"headers": Headers { <some fields may have been hidden> },
222+
}
223+
`);
224+
});
225+
226+
it("rejects a gzipped body that decompresses past the server size cap", async ({ expect }) => {
227+
await Project.createAndSwitch({ config: { magic_link_enabled: true } });
228+
await Project.updateConfig({ apps: { installed: { analytics: { enabled: true } } } });
229+
await Auth.Otp.signIn();
230+
231+
// 9 MB of zeros gzips to ~9 KB but decompresses past the 8 MB server cap.
232+
const bomb = gzipSync(Buffer.alloc(9 * 1024 * 1024));
233+
234+
const res = await niceBackendFetch("/api/v1/analytics/events/batch", {
235+
method: "POST",
236+
accessType: "client",
237+
rawBody: bomb,
238+
});
239+
240+
expect(res).toMatchInlineSnapshot(`
241+
NiceResponse {
242+
"status": 400,
243+
"body": "Invalid encoded analytics body",
244+
"headers": Headers { <some fields may have been hidden> },
245+
}
246+
`);
218247
});
219248

220249
it("handles click event data containing a truncated surrogate pair (lone high surrogate)", async ({ expect }) => {

packages/stack-shared/src/interface/client-interface.ts

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -136,13 +136,9 @@ async function encodeAnalyticsBody(jsonBody: string): Promise<{ body: BodyInit,
136136
if (typeof CompressionStreamCtor !== "function" || typeof Blob === "undefined" || typeof Response === "undefined") {
137137
return { body: jsonBody, contentType: "application/json" };
138138
}
139-
try {
140-
const stream = new Blob([jsonBody]).stream().pipeThrough(new CompressionStreamCtor("gzip"));
141-
const buffer = await new Response(stream).arrayBuffer();
142-
return { body: new Uint8Array(buffer), contentType: "application/octet-stream" };
143-
} catch {
144-
return { body: jsonBody, contentType: "application/json" };
145-
}
139+
const stream = new Blob([jsonBody]).stream().pipeThrough(new CompressionStreamCtor("gzip"));
140+
const buffer = await new Response(stream).arrayBuffer();
141+
return { body: new Uint8Array(buffer), contentType: "application/octet-stream" };
146142
}
147143

148144
export class StackClientInterface {

0 commit comments

Comments
 (0)