diff --git a/internal/check-poster-render-pinned.mjs b/internal/check-poster-render-pinned.mjs index d7c864d60..af175eba2 100644 --- a/internal/check-poster-render-pinned.mjs +++ b/internal/check-poster-render-pinned.mjs @@ -12,7 +12,7 @@ * · 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 { readFileSync, writeFileSync, mkdtempSync, mkdirSync, rmSync, existsSync } from 'node:fs' import { createHash } from 'node:crypto' import { tmpdir } from 'node:os' import { join } from 'node:path' @@ -98,5 +98,36 @@ ok(typeof html === 'string' && html.length > 10000 && / 10000, `run as a classic script it draws the module's sheet byte for byte (${a.length} bytes)`) } +// THE PACKET TEXT (library/packet/packetStrings.js), served at lib/packet-strings.json and pinned. Two-sided: +// the GENUINE served copy loads every language, and a TAMPERED one is refused — the print falls back to the +// bundled English block, so the forged text never reaches a print document. +{ + const { PACKET_STRINGS_SHA384 } = await import(join(REPO, 'library/packet/poster-render.integrity.js')) + const stringsSrc = readFileSync(join(REPO, 'library/scripts/posters/packet-strings.json'), 'utf8') + const servedStrings = join(REPO, 'public/dx-maps/assets/lib/packet-strings.json') + ok(PACKET_STRINGS_SHA384 === pinOf(stringsSrc), + `the packet-strings pin matches its source${PACKET_STRINGS_SHA384 === pinOf(stringsSrc) ? '' : ' — run: node library/scripts/posters/pin-poster-render.mjs'}`) + ok(existsSync(servedStrings) && pinOf(readFileSync(servedStrings, 'utf8')) === pinOf(stringsSrc), + 'the served copy (assets/lib/packet-strings.json) is the pinned file') + const { loadPacketStrings } = await import(join(REPO, 'library/packet/packetStrings.js')) + const genuine = await loadPacketStrings(pathToFileURL(join(REPO, 'public/dx-maps/assets')).href) + const langs = Object.keys(genuine).filter((k) => k !== '_meta') + ok(langs.length === 11 && genuine.zh?.zone_affinity_A017 === JSON.parse(stringsSrc).zh.zone_affinity_A017, + `the genuine served copy loads all ${langs.length} languages`) + const tmp3 = mkdtempSync(join(tmpdir(), 'strings-pin-')) + const warn = console.warn; console.warn = () => {} + try { + mkdirSync(join(tmp3, 'lib'), { recursive: true }) + const forged = JSON.parse(stringsSrc); forged.zh.zone_affinity_A017 = 'FORGED' + writeFileSync(join(tmp3, 'lib/packet-strings.json'), JSON.stringify(forged, null, 2) + '\n') + const tampered = await loadPacketStrings(pathToFileURL(tmp3).href) + ok(!JSON.stringify(tampered).includes('FORGED') && Object.keys(tampered).join() === 'en' + && tampered.en.zone_affinity_A017 === JSON.parse(stringsSrc).en.zone_affinity_A017, + 'a tampered packet-strings table is refused — the print falls back to the bundled English block') + const absent = await loadPacketStrings(pathToFileURL(join(tmp3, 'nowhere')).href) + ok(Object.keys(absent).join() === 'en', 'an unserved table falls back to English rather than failing the print') + } finally { console.warn = warn; rmSync(tmp3, { recursive: true, force: true }) } +} + 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') diff --git a/internal/generate-tree.mjs b/internal/generate-tree.mjs index eb6f6e438..935eba149 100644 --- a/internal/generate-tree.mjs +++ b/internal/generate-tree.mjs @@ -1722,6 +1722,10 @@ function main() { //