From bbf0638e884d29cac21cb1f51fa0d41dce36d3a1 Mon Sep 17 00:00:00 2001 From: xpeng5278-web Date: Wed, 14 Oct 2026 08:10:04 +0800 Subject: [PATCH] fix(javascript): report EventSource and sendBeacon URLs as network endpoints (#3044) Co-authored-by: xpeng5278-web <330743750+xpeng5278-web@users.noreply.github.com> --- ...ascriptStaticAnalysis.event_source.test.ts | 40 +++++++++++++++++++ ...vascriptStaticAnalysis.send_beacon.test.ts | 34 ++++++++++++++++ .../javascriptStaticAnalysisHelpers.ts | 11 ++++- 3 files changed, 84 insertions(+), 1 deletion(-) create mode 100644 src/domain/javascript/javascriptStaticAnalysis.event_source.test.ts create mode 100644 src/domain/javascript/javascriptStaticAnalysis.send_beacon.test.ts diff --git a/src/domain/javascript/javascriptStaticAnalysis.event_source.test.ts b/src/domain/javascript/javascriptStaticAnalysis.event_source.test.ts new file mode 100644 index 000000000..0fa5d6543 --- /dev/null +++ b/src/domain/javascript/javascriptStaticAnalysis.event_source.test.ts @@ -0,0 +1,40 @@ +import { describe, expect, it } from "vitest"; + +import { analyzeJavaScriptStaticSource } from "./javascriptStaticAnalysis.js"; + +const endpoints = (source: string) => + analyzeJavaScriptStaticSource(source).endpoints.map( + ({ kind, value, mechanism }) => ({ kind, value, mechanism }), + ); + +describe("server-sent event endpoints", () => { + it("reports EventSource URLs like WebSocket URLs", () => { + expect( + endpoints(` + new WebSocket("wss://chat.example/ws"); + new EventSource("/api/chat/stream"); + new window.EventSource("https://chat.example/events", { withCredentials: true }); + `), + ).toEqual([ + { + kind: "network", + value: "wss://chat.example/ws", + mechanism: "call:WebSocket", + }, + { + kind: "network", + value: "/api/chat/stream", + mechanism: "call:EventSource", + }, + { + kind: "network", + value: "https://chat.example/events", + mechanism: "call:window.EventSource", + }, + ]); + }); + + it("keeps a dynamic EventSource URL unreported", () => { + expect(endpoints("new EventSource(url);")).toEqual([]); + }); +}); diff --git a/src/domain/javascript/javascriptStaticAnalysis.send_beacon.test.ts b/src/domain/javascript/javascriptStaticAnalysis.send_beacon.test.ts new file mode 100644 index 000000000..fd26b88b5 --- /dev/null +++ b/src/domain/javascript/javascriptStaticAnalysis.send_beacon.test.ts @@ -0,0 +1,34 @@ +import { describe, expect, it } from "vitest"; + +import { analyzeJavaScriptStaticSource } from "./javascriptStaticAnalysis.js"; + +const endpoints = (source: string) => + analyzeJavaScriptStaticSource(source).endpoints.map( + ({ kind, value, mechanism }) => ({ kind, value, mechanism }), + ); + +describe("beacon endpoints", () => { + it("reports navigator.sendBeacon URLs", () => { + expect( + endpoints(` + navigator.sendBeacon("/api/telemetry", payload); + window.navigator.sendBeacon("https://metrics.example/collect"); + `), + ).toEqual([ + { + kind: "network", + value: "/api/telemetry", + mechanism: "call:navigator.sendBeacon", + }, + { + kind: "network", + value: "https://metrics.example/collect", + mechanism: "call:window.navigator.sendBeacon", + }, + ]); + }); + + it("keeps a dynamic beacon URL unreported", () => { + expect(endpoints("navigator.sendBeacon(url, payload);")).toEqual([]); + }); +}); diff --git a/src/domain/javascript/javascriptStaticAnalysisHelpers.ts b/src/domain/javascript/javascriptStaticAnalysisHelpers.ts index e2cc73f20..14827e1c2 100644 --- a/src/domain/javascript/javascriptStaticAnalysisHelpers.ts +++ b/src/domain/javascript/javascriptStaticAnalysisHelpers.ts @@ -554,7 +554,13 @@ export const endpointArgument = ( receiverFact?: JavaScriptOpenReceiverFact, ): string | undefined => { if (receiverFact === "window") return undefined; - if (name === "fetch" || name.endsWith(".fetch") || name === "WebSocket") + if ( + name === "fetch" || + name.endsWith(".fetch") || + name === "WebSocket" || + name === "EventSource" || + name.endsWith(".EventSource") + ) return stringValue(args[0]); if (name.endsWith(".open")) return xhrOpenUrl(name, args[0], args[1], receiverFact); @@ -574,6 +580,9 @@ export const endpointArgument = ( ? undefined : value; } + // navigator.sendBeacon(url, data) queues a POST to its first argument. + if (name === "sendBeacon" || name.endsWith(".sendBeacon")) + return stringValue(args[0]); if (name.endsWith("loadURL")) return stringValue(args[0]); return undefined; };