diff --git a/lib/__tests__/lite-scripts.test.ts b/lib/__tests__/lite-scripts.test.ts index f8b5bbc05..8f301e8f4 100644 --- a/lib/__tests__/lite-scripts.test.ts +++ b/lib/__tests__/lite-scripts.test.ts @@ -1,4 +1,5 @@ import { describe, expect, it } from 'vitest'; +import { createHash } from 'node:crypto'; import { mkdtempSync, mkdirSync, writeFileSync, readFileSync, rmSync, existsSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; @@ -24,7 +25,9 @@ import { buildReadme, buildRedirects, buildRootRedirect, + buildScriptHashCsp, buildStalwartEntry, + buildStalwartEntryCsp, buildStalwartManifest, buildStalwartReadme, buildZip, @@ -36,6 +39,7 @@ import { findSegmentPrefetchDirs, flightTextRowsWithNeedles, inlineJson, + inlineScriptHashes, installStalwartFetchShim, isReservedStalwartSegment, listZipEntries, @@ -54,6 +58,7 @@ import { stalwartRewriteIndex, stalwartZipEntryProblems, unexpectedApiStrings, + withScriptHashCsp, } from '../../scripts/lite/lib.mjs'; import { collectTestPaths, planRemovals, runPrepare } from '../../scripts/lite/prepare.mjs'; import { runPostbuild } from '../../scripts/lite/postbuild.mjs'; @@ -160,6 +165,35 @@ describe('lite build helpers', () => { expect(buildHeaders({ basePath: '/w', connectSrc: '*' })).toMatch(/^\/w\/\*/); }); + // The header policy has to allow inline scripts (the export ships hydration + // scripts and one header cannot list every page's hashes); each page pins + // its own inline scripts with a policy enforced alongside it. + it('pins each page to its own inline scripts', () => { + const sha = (text: string) => `'sha256-${createHash('sha256').update(text).digest('base64')}'`; + const html = [ + 'x', + '', + '', + '', + '', + '', + ].join(''); + + expect(inlineScriptHashes(html)).toEqual([ + sha('self.__next_f.push([1,"a"])'), + sha('import("./m.js")'), + sha('\nwindow.x = 1;\n'), + ]); + + const pinned = withScriptHashCsp(html); + const meta = ``; + expect(pinned.indexOf(meta)).toBe(pinned.indexOf('') + ''.length); + expect(pinned.indexOf(meta)).toBeLessThan(pinned.indexOf('')).toMatch(/^')).toMatch(/^]*>