Repository navigation
Clients load no code from third-party hosts at runtime - #3041
Merged
Merged
Conversation
The report, admin and legacy participation pages fetched code, styles, fonts and images from CDNs on every load. When one of those hosts was unreachable the page broke (the report reads window.d3 from d3js.org), and every load told those hosts who was viewing. Each dependency now comes from a pinned npm package and is served from the client's own build, at the version the page was already loading: - report: d3 4.13.0, d3-scale-chromatic 1.5.0 and Plotly 2.27.0 are copied byte for byte into the build and loaded by the same script tags, in the same order, from our host; the unused Font Awesome 4.4.0 stylesheet (no element uses its classes) is removed. - admin: the Space Mono font comes from @fontsource/space-mono 5.3.0 (the same font version and glyphs as Google Fonts serves); the fifteen Twemoji 14.0.2 images the pages draw are shipped as client assets. - legacy participation: window.jwt_decode is jwt-decode 3.1.2, the version the jsdelivr @latest link resolved to. Simple Analytics, Google Analytics (only when configured), the login provider and the deliberate embeds are unchanged. A Cypress check blocks every non-local host, clears and disables the browser cache, and loads every report view (including topic and collective statement views), both participation clients and the admin pages; it fails on any third-party request other than Simple Analytics, any page error, or any of our own assets failing to load. The report browser proof blocks non-local hosts the same way. Unit tests in each client pin the templates and versions. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018NVGBuYCk4EZmiz4csUv9s
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.
Observable changes
d3js.org,cdn.plot.ly,maxcdn.bootstrapcdn.com,fonts.googleapis.com/fonts.gstatic.com,cdnjs.cloudflare.com(Twemoji images),cdn.jsdelivr.net. The same files now come from our own host.topicReportscreenshots are byte-identical;reportdiffers in 752 of 39.3M pixels andtopicStatsin 157 pixels, all in the page-header URL text (the local server port) and the export file names (the timestamp). Every chart is pixel-identical.report_bundle.*.jsunchanged byte for byte (639,317 bytes, same content hash).index_report.html1,085 → 977 bytes. New static files, byte-identical to what the CDNs served: d3 4.13.0 (221,957), d3-scale-chromatic 1.5.0 (19,831), Plotly 2.27.0 (3,598,158), each with a.gzsibling like the bundle has. The Font Awesome 4.4.0 stylesheet (26,711) is no longer loaded: nothing in the report uses its classes.admin_bundle.*.js248,663 → 248,740 bytes gzipped. New static files: Space Mono CSS (4,140), woff2 for latin, latin-ext and vietnamese in 400/700/400-italic (124,672), woff fallbacks (93,280), 15 Twemoji PNGs (11,949).js/jwt-decode-3.1.2.js(3,688 bytes, 1,365 gzipped) served.No server, database, schema or route change. The participation path is untouched: the only participation-client change is where
jwt_decodeis loaded from.What changed
d3js.org/d3.v4.min.js(4.13.0),d3js.org/d3-scale-chromatic.v1.min.js(1.5.0),cdn.plot.ly/plotly-2.27.0.min.jsaswindow.d3/window.Plotlyd3@4.13.0,d3-scale-chromatic@1.5.0as aliases so the bundledd34.6 andd3-scale-chromatic1.1 modules stay as they are;plotly.js-dist-min@2.27.0), copied unminified into the build and loaded by the same script tags in the same order@fontsource/space-mono@5.3.0: same font version (1.003), all 225 latin glyph outlines and advance widths identical (checked with fontTools); same three styles, subsets andfont-display: swapreact-easy-emojiimages from cdnjs (Twemoji 14.0.2)public/twemoji/14.0.2/72x72, via a one-line wrapper that sets the base URL (credits Twemoji's CC-BY 4.0 in source)cdn.jsdelivr.net/npm/jwt-decode@latest/...(resolved to 3.1.2)jwt-decode@3.1.2build/jwt-decode.js, same synchronous positionDeliberate third parties, unchanged, for a ruling
scripts.simpleanalyticscdn.com,queue.simpleanalyticscdn.com): report, admin (production builds), legacy participation.www.googletagmanager.com): legacy participation, only whenGA_TRACKING_IDis set.unpkg.com/tailwindcssandcdn.auth0.com/js/auth0/9.19.0./donate(donorbox.org iframe);/home2(topic-map demo iframe from thepolis-delphiS3 bucket);/embed,/embedReportand their preprod variants (hardcodedpol.is/preprod.pol.is).commentsReportandtopicsVizReport): the generated HTML loadsunpkg.com(deck.gl@latest,apache-arrow@latest), Google Fonts and maxcdn. Fixing that is a change to how Delphi generates them, not to the clients.The guard
e2e/cypress/e2e/self-contained/third-party-hosts.cy.js(npm run test:self-contained; also part ofnpm test): blocks every non-local request, clears and disables the browser cache (cached files never reach the network, so without this the check passed on edge once another page had loaded them), and loads every report view the server routes (including the topic and collective statement views), both participation clients, the signed-out admin pages and the signed-in conversation pages. It fails on any blocked request to a host other than Simple Analytics, any page error, or any of our own scripts, styles, fonts or images failing to load, and checkswindow.d3.version,window.Plotly.version,window.jwt_decode, the Space Mono faces and the emoji images. The report browser proof (ci/dynamo-removal/report-proof.cjs) now blocks non-local hosts the same way, so a CDN outage can no longer fail it. Each client has a unit test pinning its template and versions.Found along the way (not changed)
e2e/cypress/support/e2e.jsswallows every uncaught exception, so a broken page never failed the suite; the guard records exceptions itself.client-participationnpm run lintreports 43 errors on edge and on this branch, none in changed files; no CI job runs it.Checks run (local Docker stack, non-default ports)
d3js.org(twice),cdn.plot.lyand maxcdn; legacy participation listscdn.jsdelivr.net; admin pages listfonts.googleapis.comandcdnjs.cloudflare.com. The alpha page loads nothing third-party itself; its test fails on edge only through the admin page its sign-out step visits.bash scripts/test-dynamo-removal.shon this branch: pass (report, topicStats and topicReport rendered with non-local hosts blocked; only Simple Analytics was blocked; 0 page errors). The same report proof on edge with hosts blocked fails:Cannot read properties of undefined (reading 'scaleLinear')plus the four CDN requests.npm install,npm test(18 suites, 94 tests),npm run lint,npm run build:prod.npm ci,npm run test:coverage(29 suites, 324 tests),npm run lint,npm run build:prod.npm ci,npm test(159 tests),npm run build:prod.npm run lint(0 errors). Existing specsclient-report/*,client-admin/public-pages,client-admin/conversation,client-participation/anonymous-jwt-flowandalpha-client/votespass;client-report/comment-reportfails only on its hardcodedhttp://localhost/report/(the local run used127.0.0.1on another port).npm installleaves them unchanged.🤖 Generated with Claude Code
https://claude.ai/code/session_018NVGBuYCk4EZmiz4csUv9s