From 0cbaeadc665dee6aaf9508a93a4b6d6f751663db Mon Sep 17 00:00:00 2001 From: Garry Tan Date: Sun, 16 Aug 2026 09:43:46 -0700 Subject: [PATCH] feat(browse): allow Emulation.setEmulatedMedia on the CDP allowlist (re-derived from #2419) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds Emulation.setEmulatedMedia to the deny-default CDP allowlist: tab-scoped, trusted output (returns an empty result — no page content). Unlocks media type/feature overrides (prefers-color-scheme, prefers-reduced-motion, prefers-contrast, forced-colors) via `$B cdp`, so dark-mode and a11y CSS branches are testable without a headed toggle. Like setUserAgentOverride, the override persists on the tab until cleared with an empty features array — noted in the entry's justification. Registry test pins the entry (allowed + tab scope + trusted output); the PR's VERSION/CHANGELOG stamping is stripped per wave convention (versioning happens at /ship). Tests: cdp-allowlist 7 pass, 0 fail. Re-derived from PR #2419 by @meshailabs. Co-authored-by: meshailabs Co-Authored-By: Claude Fable 5 --- browse/src/cdp-allowlist.ts | 7 +++++++ browse/test/cdp-allowlist.test.ts | 12 ++++++++++++ 2 files changed, 19 insertions(+) diff --git a/browse/src/cdp-allowlist.ts b/browse/src/cdp-allowlist.ts index b9c3a9538..9e1f9f655 100644 --- a/browse/src/cdp-allowlist.ts +++ b/browse/src/cdp-allowlist.ts @@ -155,6 +155,13 @@ export const CDP_ALLOWLIST: ReadonlyArray = Object.freeze([ output: 'trusted', justification: 'UA override on the active tab. NOTE: changes affect future requests; fine for tests.', }, + { + domain: 'Emulation', + method: 'setEmulatedMedia', + scope: 'tab', + 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.', + }, // ─── Page capture (output, not navigation) ───────────────── { domain: 'Page', diff --git a/browse/test/cdp-allowlist.test.ts b/browse/test/cdp-allowlist.test.ts index 8c80b2cdd..73d339f23 100644 --- a/browse/test/cdp-allowlist.test.ts +++ b/browse/test/cdp-allowlist.test.ts @@ -70,6 +70,18 @@ describe('CDP allowlist (T2: deny-default)', () => { expect(isCdpMethodAllowed('Page.captureScreenshot')).toBe(true); }); + it('Emulation.setEmulatedMedia is allowed, tab-scoped, trusted (#2419)', () => { + // Media type/feature override (prefers-color-scheme, prefers-reduced-motion, + // prefers-contrast, forced-colors) so a11y and dark-mode CSS branches are + // testable via $B cdp. Returns an empty result — no page content, so + // trusted output is correct. + expect(isCdpMethodAllowed('Emulation.setEmulatedMedia')).toBe(true); + const e = lookupCdpMethod('Emulation.setEmulatedMedia'); + 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.