doxa-maps/internal/check-poster-render-pinned.mjs
Doxa Maps 3268105643 The served capitals-and-cities table is integrity-pinned too, and a forged one is not drawn
Following the generator's pin (studio's follow-up): the Natural Earth table the sheets fetch at print
time (boundaries/ne-places.json) is data, not code, but pinned the same way — its sha384 sits beside the
generator's in poster-render.integrity.js (bun run pin:poster-render writes both), and posterSheets hashes
the fetched text before parsing it. A table that is not the pinned one is not used: the sheet draws no
city dots and says so in the console, rather than plotting places it cannot vouch for.

check:poster-render-pinned, two-sided for the table as for the generator: the pin matches the source
and the served copy; a FORGED table (every name replaced) yields exactly the sheet drawn with no places;
the genuine table IS used (its sheet differs) — the probe's pins now span Chad, because a single pin
framed too small an area for any place to fall inside it and the check could see nothing.
check:poster-browser-equiv still byte-identical; gates:pre green.
2026-09-29 07:31:17 +00:00

80 lines
5.5 KiB
JavaScript

#!/usr/bin/env bun
/**
* check-poster-render-pinned — the map-sheet generator a browser loads at print time is the one this repo
* pinned, and anything else is refused BEFORE it runs.
*
* The generator is served (assets/lib/poster-render.js) and imported at print time rather than bundled.
* Code loaded that way executes on partner pages, so it is integrity-pinned like every embed: the bundle
* carries its sha384 (library/packet/poster-render.integrity.js) and posterSheets runs it only on a match.
*
* Two-sided:
* · the pin equals the sha384 of the source AND of the served copy (a stale pin names the fix);
* · a TAMPERED copy is refused, and its code never executes (a planted side effect stays unset);
* · the GENUINE copy loads and draws a real sheet.
*/
import { readFileSync, writeFileSync, mkdtempSync, mkdirSync, rmSync } from 'node:fs'
import { createHash } from 'node:crypto'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { pathToFileURL } from 'node:url'
const REPO = new URL('..', import.meta.url).pathname
const pinOf = (t) => `sha384-${createHash('sha384').update(t, 'utf8').digest('base64')}`
let fails = 0
const ok = (c, m) => { console.log(` ${c ? 'ok ' : 'FAIL'} ${m}`); if (!c) fails++ }
const source = readFileSync(join(REPO, 'library/packet/poster-render.js'), 'utf8')
const served = readFileSync(join(REPO, 'public/dx-maps/assets/lib/poster-render.js'), 'utf8')
const { POSTER_RENDER_SHA384, NE_PLACES_SHA384 } = await import(join(REPO, 'library/packet/poster-render.integrity.js'))
ok(POSTER_RENDER_SHA384 === pinOf(source),
`the pin matches library/packet/poster-render.js${POSTER_RENDER_SHA384 === pinOf(source) ? '' : ' — run: node library/scripts/posters/pin-poster-render.mjs'}`)
ok(pinOf(served) === pinOf(source), 'the served copy (assets/lib/poster-render.js) is the pinned file')
const placesSrc = readFileSync(join(REPO, 'library/scripts/posters/data/ne-places.json'), 'utf8')
const placesServed = readFileSync(join(REPO, 'public/dx-maps/assets/boundaries/ne-places.json'), 'utf8')
ok(NE_PLACES_SHA384 === pinOf(placesSrc) && pinOf(placesServed) === pinOf(placesSrc),
'the capitals-and-cities pin matches its source and the served copy (boundaries/ne-places.json)')
const { generateSheet, countryZone } = await import(join(REPO, 'library/packet/posterSheets.js'))
const zone = countryZone({ id: 'country-tcd', iso3: 'TCD', label: 'Chad', count: 1 })
// Probe pins spread across Chad, so the sheet's view holds real capitals and cities (one pin frames too
// small an area for any place to fall inside it, and the places check below would see nothing).
const pins = [[12.1, 15.0], [13.8, 20.8], [9.1, 18.4], [17.9, 19.1], [8.6, 16.1]]
.map(([lat, lng], i) => ({ name: `Probe ${i + 1}`, lat, lng, slug: `probe-${i}`, country_code: 'TCD', population: 1000 }))
// TAMPERED: the pinned file plus one line that would run on import.
const tmp = mkdtempSync(join(tmpdir(), 'poster-pin-'))
try {
mkdirSync(join(tmp, 'lib'), { recursive: true })
writeFileSync(join(tmp, 'lib/poster-render.js'), `globalThis.__POSTER_TAMPERED_RAN__ = true;\n${source}`)
let refused = null
try { await generateSheet({ zone, pins, format: '3x3', seriesId: 'country', assetsBase: pathToFileURL(tmp).href }) }
catch (e) { refused = String(e?.message || e) }
ok(!!refused && /integrity/.test(refused), `a tampered generator is refused${refused ? ` ("${refused.slice(0, 70)}…")` : ' — IT WAS NOT'}`)
ok(globalThis.__POSTER_TAMPERED_RAN__ !== true, 'and its code never executed')
} finally { rmSync(tmp, { recursive: true, force: true }) }
// GENUINE: the served copy loads and draws.
const adm0 = JSON.parse(readFileSync(join(REPO, 'public/dx-maps/assets/boundaries/world-adm0.geojson'), 'utf8'))
const html = await generateSheet({ zone, pins, format: '3x3', seriesId: 'country', adm0,
assetsBase: pathToFileURL(join(REPO, 'public/dx-maps/assets')).href, date: '2026-09-29' })
ok(typeof html === 'string' && html.length > 10000 && /<svg\b/.test(html), `the pinned generator loads and draws a sheet (${html.length} bytes)`)
// THE PLACES TABLE, two-sided: a tampered table is NOT used (the sheet equals one drawn with no places),
// and the genuine one IS (its sheet differs) — so the check can see the places at all.
{
const base = { zone, pins, format: '3x3', seriesId: 'country', adm0, date: '2026-09-29' }
const none = await generateSheet({ ...base, assetsBase: pathToFileURL(join(REPO, 'public/dx-maps/assets')).href, nePlaces: [] })
const tmp2 = mkdtempSync(join(tmpdir(), 'places-pin-'))
try {
mkdirSync(join(tmp2, 'lib'), { recursive: true }); mkdirSync(join(tmp2, 'boundaries'), { recursive: true })
writeFileSync(join(tmp2, 'lib/poster-render.js'), source)
const doctored = JSON.parse(placesSrc); doctored.places = doctored.places.map((q) => ({ ...q, name: 'Forged' }))
writeFileSync(join(tmp2, 'boundaries/ne-places.json'), JSON.stringify(doctored))
const tampered = await generateSheet({ ...base, assetsBase: pathToFileURL(tmp2).href })
ok(tampered === none && !tampered.includes('Forged'), 'a tampered capitals-and-cities table is not used — the sheet draws no city dots')
} finally { rmSync(tmp2, { recursive: true, force: true }) }
ok(html !== none, 'the genuine table IS used (its sheet differs from one with no places)')
}
if (fails) { console.log(`POSTER-RENDER-PINNED: FAIL (${fails})`); process.exit(1) }
console.log('POSTER-RENDER-PINNED: PASS — the served generator and places table are the pinned ones, a tampered generator is refused unrun, a tampered table is not used, and the genuine copies draw')