From 63ef693d02fa73e0a4fbb70d8ec6856ea7b17cec Mon Sep 17 00:00:00 2001 From: henbima Date: Sun, 16 Aug 2026 22:13:11 +0800 Subject: [PATCH] feat(browse): allow CPU and network throttling for performance measurement Adds Emulation.setCPUThrottlingRate and Network.emulateNetworkConditions to CDP_ALLOWLIST. Motivation: diagnosing a real "uploads take 1-2 minutes" report, the only machine available was a fast developer workstation. Client-side processing measured 1.4s where the user experienced minutes, so the conclusion had to be reached arithmetically rather than observed. Throttling would have let the measurement reproduce the reporter's conditions directly. Both fit the existing posture rather than widening it: - Emulation already allows setDeviceMetricsOverride, clearDeviceMetricsOverride and setUserAgentOverride, which are equally mutating and scoped to the tab. - Neither method reads page content. setCPUThrottlingRate affects only timing; emulateNetworkConditions constrains traffic rather than inspecting it, so no request bodies, headers or cookies are exposed. Both are output: 'trusted' because they return no page-derived data. scope 'tab' for both, matching the surrounding Emulation entries. Co-Authored-By: Claude Fable 5 --- browse/src/cdp-allowlist.ts | 14 ++++++++++++++ browse/test/cdp-allowlist.test.ts | 13 +++++++++++++ 2 files changed, 27 insertions(+) diff --git a/browse/src/cdp-allowlist.ts b/browse/src/cdp-allowlist.ts index 9e1f9f655..b4faa46ff 100644 --- a/browse/src/cdp-allowlist.ts +++ b/browse/src/cdp-allowlist.ts @@ -162,6 +162,20 @@ export const CDP_ALLOWLIST: ReadonlyArray = Object.freeze([ output: 'trusted', justification: 'Media type/feature override (prefers-color-scheme, prefers-reduced-motion, prefers-contrast, forced-colors) so a11y and dark-mode CSS branches are testable. Returns an empty result; no page content. NOTE: like setUserAgentOverride the override persists on the tab until cleared with an empty features array.', }, + { + domain: 'Emulation', + method: 'setCPUThrottlingRate', + scope: 'tab', + output: 'trusted', + justification: 'CPU slowdown multiplier on the active tab, for measuring performance on a realistic low-end client instead of the developer workstation. Same domain and mutating character as setDeviceMetricsOverride; affects only timing, reads nothing, exfiltrates nothing.', + }, + { + domain: 'Network', + method: 'emulateNetworkConditions', + scope: 'tab', + output: 'trusted', + justification: 'Bandwidth/latency emulation on the active tab, for measuring page behaviour on a slow connection. Constrains traffic rather than reading it — no request bodies, headers or cookies are exposed.', + }, // ─── Page capture (output, not navigation) ───────────────── { domain: 'Page', diff --git a/browse/test/cdp-allowlist.test.ts b/browse/test/cdp-allowlist.test.ts index 73d339f23..0693781a1 100644 --- a/browse/test/cdp-allowlist.test.ts +++ b/browse/test/cdp-allowlist.test.ts @@ -82,6 +82,19 @@ describe('CDP allowlist (T2: deny-default)', () => { expect(e!.output).toBe('trusted'); }); + it('CPU + network throttling are allowed, tab-scoped, trusted (#2602)', () => { + // Perf-measurement emulation (PR #2602 by @henbima): both constrain the + // tab's timing/traffic, read nothing, and return empty results — same + // posture argument as setEmulatedMedia (#2419) and setDeviceMetricsOverride. + for (const method of ['Emulation.setCPUThrottlingRate', 'Network.emulateNetworkConditions']) { + expect(isCdpMethodAllowed(method)).toBe(true); + const e = lookupCdpMethod(method); + expect(e).not.toBeNull(); + expect(e!.scope).toBe('tab'); + expect(e!.output).toBe('trusted'); + } + }); + it('untrusted-output methods cover the read-everything-attacker-controlled cases', () => { // Anything that reads attacker-controlled strings (DOM/AX/CSS selectors) // should be tagged untrusted so the envelope wraps the result.