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.