Repository navigation
Keep the page's security policy on: load the virtual screen reader as a plain script (0.13.0) - #48
Merged
Merged
Conversation
… a plain script Every virtual screen reader command asked Playwright to ignore the page's Content-Security-Policy, because Guidepup's reader was imported as an ES module from a blob URL and a strict policy refuses that. On a locked-down site the tool switched the lock off. Guidepup's browser build is now bundled into one plain IIFE script with esbuild at install/publish time (scripts/build-virtual-screen-reader.mjs, run by `prepare`) and shipped as dist/guidepup-virtual-screen-reader.js with Guidepup's MIT licence beside it. injectVirtualScreenReader, the one loader every command uses, evaluates it with page.evaluate(source) over the DevTools protocol, which a page's CSP does not govern. This is how Deque's official @axe-core/playwright loads axe (page.evaluate(this.script())), and how runAxe here already loaded axe. The CSP bypass is removed everywhere. The --record overlay set its styles with inline style="" markup, which a strict policy drops; it now sets them from script. @guidepup/virtual-screen-reader is pinned to exactly 0.32.1: 0.33.0's start() opens a persistent user-event session that can break later typing in a React input (guidepup/virtual-screen-reader#250). A test checks the shipped script never calls setup(. test/csp.test.js serves a page with `default-src 'none'; script-src 'none'` over a local HTTP server and checks audit, scan announcements and the record overlay; it fails on the old loader. Version 0.13.0. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this means for users
A page can tell the browser which scripts it may run (its Content-Security-Policy). Until now, every virtual screen reader command (
audit,page,nav,speak,repl,screenshot, the per-finding announcements inscan, the dashboard and the MCP server) asked the browser to ignore that policy, Playwright'sbypassCSP: true. On a locked-down site the tool switched the lock off.Now the lock stays on, and the reader still works. Example: a page served with
default-src 'none'; script-src 'none'.auditFailed to fetch dynamically imported module: blob:…scanfinding for the nameless buttonscreen reader says "button"audit --recordoverlayHow: the way Deque's
@axe-core/playwrightloads axescripts/build-virtual-screen-reader.mjsbundles Guidepup's browser build into one plain script with esbuild (format: "iife", global__srCliGuidepupVirtualScreenReader). It runs onnpm installand beforenpm publish(prepare), and ships asdist/guidepup-virtual-screen-reader.jswith Guidepup's MIT licence beside it (dist/guidepup-virtual-screen-reader.LICENSE).dist/is in thefilesallowlist, andnpm pack --dry-runlists both files.injectVirtualScreenReaderinsrc/bridge.jsis the one loader every command uses. It evaluates the plain script withpage.evaluate(source)over the DevTools protocol, which a page's policy does not govern. That is exactly how Deque's official@axe-core/playwrightinjects axe (page.evaluate(this.script())), and howrunAxehere already loaded axe. It is the same approach accessibility-evidence-engine took (Speak through Guidepup's virtual screen reader instead of our own rules accessibility-evidence-engine#123).bridge.js,services/scanner.jsandcommands/scan.js(including--chrome-profile), and from the README, CLAUDE.md and DEVLOG. A test keeps it out of the whole repo.--recordoverlay used inlinestyle="…"markup, which a strict policy drops. It now sets the same styles from script.Pinned:
@guidepup/virtual-screen-readerexactly 0.32.10.33.0's
virtual.start()opens a persistent user-event session that can break later typing in a React input (guidepup/virtual-screen-reader#250), and this CLI reads people's real pages. I checked the built bundle: in 0.32.1,start()only sets the container, a DOM observer and a focus listener. The bundle never calls.setup(anywhere, and it only uses the one-shotclick/keyboard/pointer/typecalls. A test guards this. The 0.33 header-in-a-card fix is already in 0.32.1:audit — header landmarkspasses.Tests
test/csp.test.js: a local HTTP server sendsContent-Security-Policy: default-src 'none'; script-src 'none'. The test checks thatauditreads the page, thatscanattaches "button" to thebutton-namefinding and that the--recordoverlay keeps its styles. It also checks that nothing in the repo uses the CSP bypass, that the shipped script is 0.32.1 and has no.setup(call, and that the licence ships beside it.auditdies withFailed to fetch dynamically imported moduleand the scan announcement goes missing. With the old inline-style overlay, the caption bar loses its styles.npm test: 170/170 pass.node bin/cli.js scan docs/index.html --fail-on minorexits 0.Release
Bumps the version to 0.13.0. Merging this publishes 0.13.0 to npm through
publish.yml. DEVLOG has a dated entry (2026-10-17). Not merged: that is the owner's call.🤖 Generated with Claude Code