The browser build runs the same compare_report and inspect_text functions as the CLI inside
Pyodide (CPython compiled to WebAssembly). Scan files are read
with the File API and passed to Python in memory. Nothing is uploaded.
The page compares two scans, inspects one scan with the same pre-flight as watchpost inspect,
and can load the two synthetic example scans that the site serves. Loading the examples leaves the
scan-context confirmation unticked: the user still confirms it before comparing.
- Every runtime file is served from the same origin: the page, Watchpost's two modules,
Pyodide, and the
defusedxmlwheel. No CDN is used. web/_headerssets a content security policy withconnect-src 'self'anddefault-src 'none', so the browser blocks requests to any other origin. The static site has no endpoint that accepts data.- The build verifies the Pyodide tarball against a pinned SHA-512 and installs the
defusedxmlwheel with the same SHA-256 asuv.lock. - Source names in reports are bare filenames from the browser, never local paths.
- The optional context note is only written into the report, as an escaped literal.
- A service worker (
sw.js, allowed byworker-src 'self') caches the site's own files after the first visit, so the page also works offline. It answers only same-origin GET requests. The build fills in its file list and a cache name tied to the files' contents, so a new deployment replaces the old cache.
Requires Node.js 18+, Python 3, pip, and openssl.
bash web/build.sh site
node web/smoke.mjs site
python3 -m http.server -d site 8000The smoke test loads the built Pyodide runtime in Node and checks that the browser report is
byte-identical to examples/report.md. It also checks inspection, the served examples, and that
the service worker's file list matches the build. http.server does not apply _headers, so use
it only for a quick look. Cloudflare applies the headers on deployment. On Windows, Python's
server can send .mjs files as text/plain, which browsers refuse; serve the site from WSL.
wrangler.jsonc deploys site/ as an assets-only Worker. No Worker script runs, so
_headers applies to every response. Static asset requests are free on the Workers Free plan.
- In the Cloudflare dashboard, open Workers & Pages → Create → Import a repository
and select
ejames-dev/watchpost. - Project name:
watchpost(must matchnameinwrangler.jsonc). - Build command:
bash web/build.sh site - Deploy command:
npx wrangler deploy(the default). - Deploy, then add a custom domain under the Worker's Settings → Domains & Routes.
Keep Cloudflare Web Analytics, Real User Measurements (Speed → Real user monitoring → Disable completely), and other injected scripts disabled for this project. A new custom domain starts with RUM in a default mode that injects Cloudflare's beacon script. The content security policy blocks them, and they would weaken the "no third parties" claim.
- Pyodide: change
PYODIDE_VERSIONandPYODIDE_INTEGRITYinweb/build.sh. Get the integrity withnpm view pyodide@<version> dist.integrity. - defusedxml: update
web/requirements.txtfrom the wheel hash inuv.lockand the wheel filename inweb/app.js. The smoke test fails if they drift.