From a5ab04ec753a38b72ee70b0873946d2889d1aab7 Mon Sep 17 00:00:00 2001
From: doxa-maps-88655c70
<88627970+doxa-maps-88655c70@users.noreply.github.com>
Date: Thu, 24 Sep 2026 19:13:46 +0000
Subject: [PATCH] Measure the basemap, in a real embed, on the token the page
ships
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
"The map renders" had never been measured. Every browser gate here
forces a Mapbox token onto the page from a shared file; that token is
restricted and 403s on the basemap, while the leaf bakes a different
one that works. The gates then counted PINS — which are drawn from our
own GeoJSON and paint with no token at all — so they passed on a
basemap the probe itself had broken.
This gate forces nothing. It serves the leaf on one port and a host
page on another so the iframe is genuinely cross-origin, loads it as a
stranger's browser would, and takes evidence two ways: Mapbox responses
counted by status, and colour variety in a slice of the canvas. The
gospel map passes — 9 tile responses, none denied, and the screenshot
shows coastlines, ocean shading and country labels.
The negative control runs every time, and the check reports PROVED
NOTHING rather than a pass if the control succeeds. That is not
decoration: the first two attempts at the control did nothing at all.
A page-level init script never reaches a cross-origin iframe, and even
at context level it was ignored, because this leaf injects `tk`
straight into the element and only falls back to window.MAP_TOKEN —
so it is immune to the forced-token hazard by construction. The
control had to move to the wire. With the token rewritten there, 6 of
8 tile requests are denied and the basemap vanishes, leaving only the
pins and choropleth: the picture that shows exactly why a pin count
was never evidence of a map.
---
internal/check-embed-basemap-paints.mjs | 210 ++++++++++++++++++++++++
internal/verify-browser-all.mjs | 3 +
package.json | 1 +
3 files changed, 214 insertions(+)
create mode 100644 internal/check-embed-basemap-paints.mjs
diff --git a/internal/check-embed-basemap-paints.mjs b/internal/check-embed-basemap-paints.mjs
new file mode 100644
index 000000000..4c7118bca
--- /dev/null
+++ b/internal/check-embed-basemap-paints.mjs
@@ -0,0 +1,210 @@
+/**
+ * check-embed-basemap-paints.mjs — the basemap really paints, in a real third-party iframe,
+ * on the token the page actually ships with.
+ *
+ * WHY THIS EXISTS. Every browser probe in this repo forced a Mapbox token onto the page
+ * (`?tk=`, or `window.MAP_TOKEN`) from a shared file. That token is `u=doxalife`, which is
+ * restricted and 403s on the basemap and country boundaries. The leaf pages BAKE a different
+ * token, `u=gosystems`, which works. So every probe measured a basemap that was broken only
+ * because the probe broke it — and passed anyway, because it went on to count PINS, which are
+ * drawn from our own GeoJSON and paint with no token at all.
+ *
+ * The consequence is that "the map renders" has never actually been measured. A pin count is
+ * not a basemap. This check exists to make that claim measurable, and it deliberately does
+ * NOT force a token: it loads the page exactly as a stranger's browser would.
+ *
+ * IT IS A REAL EMBED. The leaf is served on one port and the host page on another, so the
+ * iframe is genuinely cross-origin — the same relationship a third-party site has to it.
+ *
+ * IT CAN FAIL, AND IS SHOWN TO. A check that cannot fail proves nothing, so every run also
+ * loads the SAME page with the restricted token forced on, and requires that run to fail the
+ * basemap test. If the negative control passes, the check reports that it proved nothing
+ * rather than reporting success — a broken detector must never read as a green result.
+ *
+ * Evidence is taken two independent ways, because either alone has a failure mode:
+ * 1. NETWORK — Mapbox tile/style responses, counted by status. 401/403 is decisive.
+ * 2. PIXELS — colour variety in a slice of the canvas. A page can 200 on everything and
+ * still paint nothing, and a screenshot is the only witness to that.
+ */
+import { chromium } from 'playwright'
+import { spawn } from 'node:child_process'
+import { writeFileSync, mkdtempSync, readFileSync } from 'node:fs'
+import { resolve, dirname } from 'node:path'
+import { tmpdir } from 'node:os'
+import { fileURLToPath } from 'node:url'
+
+const HERE = dirname(fileURLToPath(import.meta.url))
+const REPO = resolve(HERE, '..')
+const PUBLIC = resolve(REPO, 'public')
+
+const LEAF = 'doxa-maps/gospel-resources/resources-map/index.html'
+const leafPort = 5810 + Math.floor(Math.random() * 60)
+const hostPort = leafPort + 1
+
+/** Mapbox endpoints that actually draw the map. Telemetry is not evidence of anything. */
+const isBasemap = (u) =>
+ /api\.mapbox\.com\/(styles|v4|fonts|tilesets)|tiles\.mapbox\.com/.test(u) &&
+ !/events\.mapbox\.com/.test(u)
+
+function serve(dir, port) {
+ return spawn(process.execPath, [resolve(REPO, 'internal/serve-strict.mjs'), '--port', String(port), '--directory', dir], {
+ stdio: 'ignore',
+ })
+}
+
+async function waitFor(url, ms = 20000) {
+ const until = Date.now() + ms
+ while (Date.now() < until) {
+ if ((await fetch(url).catch(() => null))?.ok) return true
+ await new Promise((r) => setTimeout(r, 250))
+ }
+ return false
+}
+
+/* A third-party page: nothing but an iframe, served from another origin. */
+const hostDir = mkdtempSync(resolve(tmpdir(), 'gr-embed-'))
+writeFileSync(
+ resolve(hostDir, 'index.html'),
+ `
third-party host
+
+
+`,
+)
+
+const leafSrv = serve(PUBLIC, leafPort)
+const hostSrv = serve(hostDir, hostPort)
+const stop = () => {
+ try { leafSrv.kill() } catch {}
+ try { hostSrv.kill() } catch {}
+}
+
+if (!(await waitFor(`http://127.0.0.1:${leafPort}/${LEAF}`)) || !(await waitFor(`http://127.0.0.1:${hostPort}/index.html`))) {
+ console.error('EMBED-BASEMAP: NOT MEASURED — a server did not come up.')
+ stop()
+ process.exit(1)
+}
+
+const browser = await chromium.launch({
+ args: ['--use-gl=swiftshader', '--enable-unsafe-swiftshader', '--ignore-gpu-blocklist', '--no-sandbox'],
+})
+
+/**
+ * @param {string|null} forceToken null = the page's OWN baked token (the real case).
+ */
+async function run(forceToken) {
+ /* The init script MUST be installed on the CONTEXT, not the page: the leaf lives in a
+ cross-origin iframe, and a page-level init script never reaches it. Installing it on the
+ page made both runs use the baked token and the check correctly reported that it had
+ proved nothing — which is the only reason this was caught rather than shipped as a pass. */
+ const ctx = await browser.newContext({ viewport: { width: 1200, height: 820 } })
+ const page = await ctx.newPage()
+ if (forceToken) {
+ /* THE CONTROL HAS TO WORK AT THE NETWORK LAYER. Setting window.MAP_TOKEN does nothing to
+ this leaf: it injects `tk` straight into the element's profile-config, and the window
+ global is only a fallback for leaves that do not. That is worth knowing on its own —
+ this page is immune to the forced-token hazard by construction — but it means the only
+ way to ask "what if the token were the restricted one" is to rewrite the token on the
+ requests themselves. */
+ await ctx.route(/api\.mapbox\.com|tiles\.mapbox\.com/, async (route) => {
+ const u = new URL(route.request().url())
+ if (u.searchParams.has('access_token')) u.searchParams.set('access_token', forceToken)
+ await route.continue({ url: u.toString() })
+ })
+ }
+ const net = { ok: 0, denied: 0, other: 0 }
+ page.on('response', (r) => {
+ if (!isBasemap(r.url())) return
+ const s = r.status()
+ if (s === 401 || s === 403) net.denied++
+ else if (s >= 200 && s < 300) net.ok++
+ else net.other++
+ })
+ await page.goto(`http://127.0.0.1:${hostPort}/index.html`, { waitUntil: 'networkidle', timeout: 60000 })
+ await page.waitForTimeout(12000)
+
+ const frame = page.frames().find((f) => f.url().includes('resources-map'))
+ if (!frame) return { net, pixels: null, reason: 'the iframe never loaded the leaf' }
+
+ /* A slice well away from the sheet and the controls, so we sample map, not chrome. */
+ const seen = await frame.evaluate(() => (window.MAP_TOKEN || '').slice(0, 24) || '(none on window)')
+ const shot = await page.screenshot({ clip: { x: 60, y: 180, width: 700, height: 420 } })
+ const png = shot
+ await page.close()
+ await ctx.close()
+ return { net, png, seen }
+}
+
+const real = await run(null)
+const forcedTokenPath = '/tmp/mbtk'
+let restricted = ''
+try { restricted = readFileSync(forcedTokenPath, 'utf8').trim() } catch { /* control simply cannot run */ }
+const control = restricted ? await run(restricted) : null
+
+/* Pixel variety, measured in-browser so no image library is needed. */
+const shotPage = await browser.newPage()
+async function variety(png) {
+ if (!png) return 0
+ return shotPage.evaluate(async (b64) => {
+ const img = new Image()
+ img.src = `data:image/png;base64,${b64}`
+ await img.decode()
+ const c = document.createElement('canvas')
+ c.width = img.width
+ c.height = img.height
+ c.getContext('2d').drawImage(img, 0, 0)
+ const d = c.getContext('2d').getImageData(0, 0, c.width, c.height).data
+ const seen = new Set()
+ for (let i = 0; i < d.length; i += 4) {
+ seen.add(((d[i] >> 3) << 10) | ((d[i + 1] >> 3) << 5) | (d[i + 2] >> 3))
+ }
+ return seen.size
+ }, png.toString('base64'))
+}
+
+const realColours = await variety(real.png)
+const ctlColours = control ? await variety(control.png) : null
+if (process.env.EMBED_SHOTS) {
+ const { writeFileSync: wf } = await import('node:fs')
+ if (real.png) wf(`${process.env.EMBED_SHOTS}/embed-baked.png`, real.png)
+ if (control && control.png) wf(`${process.env.EMBED_SHOTS}/embed-restricted.png`, control.png)
+ console.log(` shots written to ${process.env.EMBED_SHOTS}`)
+}
+await browser.close()
+stop()
+
+/* A painted basemap has many colours; a blank ground has a handful. */
+const COLOUR_FLOOR = 40
+const realPaints = real.net.denied === 0 && real.net.ok > 0 && realColours >= COLOUR_FLOOR
+
+console.log(`EMBED-BASEMAP: cross-origin iframe, leaf on :${leafPort} inside a host on :${hostPort}.`)
+console.log(` baked token tiles ${real.net.ok} ok / ${real.net.denied} denied · ${realColours} distinct colours`)
+if (control) {
+ console.log(` restricted token tiles ${control.net.ok} ok / ${control.net.denied} denied · ${ctlColours} distinct colours`)
+ console.log(` token the frame used: baked="${real.seen}" control="${control.seen}"`)
+}
+
+if (!control) {
+ console.error(`EMBED-BASEMAP: NOT MEASURED — ${forcedTokenPath} is absent, so the negative control could not run.`)
+ console.error(' Without a control this check cannot be shown to fail, and a result it cannot')
+ console.error(' fail on is not evidence. Refusing to report a pass.')
+ process.exit(1)
+}
+
+const controlFails = control.net.denied > 0 || ctlColours < COLOUR_FLOOR
+if (!controlFails) {
+ console.error('EMBED-BASEMAP: PROVED NOTHING — the restricted token painted a basemap too,')
+ console.error(' so this check cannot tell a working token from a broken one. Fix the control')
+ console.error(' before trusting any pass it reports.')
+ process.exit(1)
+}
+
+if (!realPaints) {
+ console.error('EMBED-BASEMAP: FAIL — the page does NOT paint a basemap on its own baked token')
+ console.error(` in a third-party iframe (${real.net.denied} denied tile response(s), ${realColours} colours).`)
+ console.error(' Pins can still draw here: they come from our GeoJSON and need no token, which is')
+ console.error(' exactly why a pin count must never be read as "the map renders".')
+ process.exit(1)
+}
+
+console.log('EMBED-BASEMAP: PASS — the basemap paints on the baked token in a third-party iframe,')
+console.log(' and the same page with the restricted token forced on fails, so the check discriminates.')
diff --git a/internal/verify-browser-all.mjs b/internal/verify-browser-all.mjs
index 5ee868b21..f12f7d42e 100644
--- a/internal/verify-browser-all.mjs
+++ b/internal/verify-browser-all.mjs
@@ -71,6 +71,9 @@ const GATES = [
// and answer a tap. It also scans every link in the index against the screened-publisher
// list, which is a doctrine boundary rather than a rendering one.
{ script: 'verify:gospel-map', why: 'the gospel map renders its pins, answers a tap, and links only screened publishers' },
+ // verify:gospel-map counts pins, which paint with no token at all. This one is the only
+ // gate that measures the BASEMAP, in a real cross-origin iframe, on the baked token.
+ { script: 'check:embed-basemap', why: "the gospel map's basemap paints in a third-party iframe on its own baked token" },
{ script: 'verify:gate1', why: 'the Gate-1 chain filters live data end to end' },
{ script: 'check:a11y-button-names', why: 'every visible button has an accessible name' },
{ script: 'check:nav-reachable', why: 'a phone can open the cap-5 tab rail' },
diff --git a/package.json b/package.json
index ff2530c07..bbf368490 100644
--- a/package.json
+++ b/package.json
@@ -155,6 +155,7 @@
"check:coverage-consistent:poison": "node internal/check-gospel-coverage-consistent.mjs --poison",
"check:surfaces-read-the-tier": "node internal/check-surfaces-read-the-tier.mjs # a surface may READ the tier the pin index computed, never re-derive 'this group has nothing' from the raw resource fields. That hand-rolled field list went stale every time a source was added: 7 codes drawn as having nothing while the map showed them a link, then 28 when Scripture Earth landed. Static, sub-second. Self-test: `bun run check:surfaces-read-the-tier:poison` re-injects the old formula into every surface and each must be caught.",
"check:surfaces-read-the-tier:poison": "node internal/check-surfaces-read-the-tier.mjs --poison",
+ "check:embed-basemap": "node internal/check-embed-basemap-paints.mjs # the gospel map's BASEMAP must paint inside a genuinely cross-origin iframe on the token the page BAKES, not one a probe forces on it. Every other browser gate here forces a restricted token (u=doxalife) that 403s on the basemap, then counts PINS — which come from our own GeoJSON and paint with no token at all — so 'the map renders' had never been measured. Runs a negative control every time by rewriting the token on the wire; if the control passes it reports PROVED NOTHING rather than a pass. ~60s, needs a browser.",
"check:synced-css:self-test": "node internal/check-synced-css-copies.mjs --self-test",
"serve:permissive": "python3 -m http.server 4173 --bind 127.0.0.1 --directory public # LOOPBACK ONLY. This was --bind 0.0.0.0, which published the built tree on every interface of whatever machine ran a gate; measured on the build VPS it answered HTTP 200 on the box's PUBLIC IP while a browser gate was running. Every consumer (verify-browser-all, check:a11y-button-names, check:nav-reachable, smoke:share-all) already defaults to 127.0.0.1, so nothing needed the wider bind. Override with BASE_URL/BASE if you deliberately want to reach it from another host.",
"check:doc-scripts": "node internal/check-doc-quotes-scripts.mjs",