diff --git a/sdk/test/index.test.ts b/sdk/test/index.test.ts index 5ed97ad..55c358c 100644 --- a/sdk/test/index.test.ts +++ b/sdk/test/index.test.ts @@ -152,6 +152,45 @@ describe("index 入口", () => { expect(ev.grant_ref).toBe(null); }); + it("track 归属权威性回归(T5 审查遗留):宿主伪造归属键被快照覆盖,业务字段原样保留", async () => { + await loadSdk([["init", { siteId: "site-auth", endpoint: "/e", flushMs: 60000 }]]); + g.ctk!("track", { + event_id: "e-evil", + event_name: "page_stay", + occurred_at: new Date().toISOString(), + path: "/x", + title: "t", + properties: {}, + site_id: "evil-site", // 宿主伪造归属键:spread 宿主在前、归属在后(index.ts track 分支) + session_id: "evil-sess", + }); + g.ctk!("flush"); + + // 信封 site_id 恒取 init 配置(transport.envelope 读 cfg.siteId)—— + // 伪造的 evil-site 批次根本不存在 + expect(bodiesOf("evil-site")).toHaveLength(0); + const bodies = bodiesOf("site-auth"); + expect(bodies).toHaveLength(1); // 伪造键被覆盖 → 与 page_view/sdk_loaded 同四元组,单组子批 + const evs = bodies[0].events; + expect(evs.map((e: { event_name: string }) => e.event_name)).toEqual([ + "page_view", + "sdk_loaded", + "page_stay", + ]); + const ev = evs[2] as Record; + // 事件归属四元组来自 PageSession 快照(非宿主伪造值) + expect(ev.site_id).toBe("site-auth"); + expect(ev.session_id).toMatch(UUID_RE); + expect(ev.session_id).toBe(evs[0].session_id); // 与初始 page_view 同会话(非 evil-sess) + expect(ev.visitor_id).toBe(evs[0].visitor_id); + expect(ev.page_id).toBe(evs[0].page_id); + // 信封归属同取分组键(事件顶层快照字段):亦非伪造值 + expect(bodies[0].session_id).toBe(evs[0].session_id); + // 宿主业务字段不被归属展开覆盖 + expect(ev.event_id).toBe("e-evil"); + expect(ev.properties).toEqual({}); + }); + it("init 接线 business(T5):产品页路径下队列 [page_view,sdk_loaded,product_view];wa.me 点击 → contact_click beacon 冒烟", async () => { setUrl("/product/hydraulic-press-100t/"); await loadSdk([["init", { siteId: "site-biz", endpoint: "/e", flushMs: 60000 }]]);