/** * Tests for `policy-shapes.mjs` — the two functions that answer the 2026-09-04 * `--apply` failure recorded in `docs/09` Part 3. * * The first case is that failure verbatim: the `SecurityHeadersConfig` the live * `Managed-SecurityHeadersPolicy` returns, empty `ContentSecurityPolicy` and * all, which is what `create-response-headers-policy` rejected. * * node infra/cloudfront/policy-shapes.test.mjs */ import { withoutEmptyMembers, emptyObjectPaths, isEmptyObject, } from './policy-shapes.mjs'; let pass = 0; const failures = []; const eq = (a, b) => JSON.stringify(a) === JSON.stringify(b); const t = (name, got, want) => { if (eq(got, want)) pass += 1; else failures.push( `${name}\n got ${JSON.stringify(got)}\n want ${JSON.stringify(want)}`, ); }; /* The live source policy, copied from `get-response-headers-policy` on 67f7725c-6f97-4210-82d7-5512b31e9d03 [verified 2026-09-04]. */ const LIVE_SECURITY_HEADERS = { XSSProtection: { Override: false, Protection: true, ModeBlock: true }, FrameOptions: { Override: false, FrameOption: 'SAMEORIGIN' }, ReferrerPolicy: { Override: false, ReferrerPolicy: 'strict-origin-when-cross-origin', }, ContentSecurityPolicy: {}, ContentTypeOptions: { Override: true }, StrictTransportSecurity: { Override: false, AccessControlMaxAgeSec: 31536000, }, }; /* ---- the incident itself ------------------------------------------------ */ const stripped = withoutEmptyMembers(LIVE_SECURITY_HEADERS); t( 'the 2026-09-04 breach: ContentSecurityPolicy is dropped', Object.keys(stripped).sort(), [ 'ContentTypeOptions', 'FrameOptions', 'ReferrerPolicy', 'StrictTransportSecurity', 'XSSProtection', ], ); t( 'and five survive — the count docs/09 Part 3 tells the operator to read', Object.keys(stripped).length, 5, ); t( 'the surviving members are untouched', stripped.StrictTransportSecurity, LIVE_SECURITY_HEADERS.StrictTransportSecurity, ); t('nothing empty is left behind', emptyObjectPaths(stripped), []); /* ---- the placeholder one level up, which a SecurityHeadersConfig-only strip turned into a hard abort (adversarial-reviewer, round 1) ------------- */ t( 'a top-level policy-config member is dropped', withoutEmptyMembers({ Name: 'p', CorsConfig: {}, SecurityHeadersConfig: stripped, }), { Name: 'p', SecurityHeadersConfig: stripped }, ); /* ---- and the one BELOW that, which the first repair still aborted on (adversarial-reviewer, round 2) -------------------------------------- */ t( 'a CorsConfig member is dropped, and the emptied CorsConfig with it', withoutEmptyMembers({ Name: 'p', CorsConfig: { AccessControlExposeHeaders: {} }, }), { Name: 'p' }, ); t( 'but a CorsConfig that still has content survives', withoutEmptyMembers({ CorsConfig: { AccessControlExposeHeaders: {}, OriginOverride: false }, }), { CorsConfig: { OriginOverride: false } }, ); /* ---- things that must NOT be discarded ---------------------------------- */ t( 'an empty ARRAY is kept — {Quantity: 0, Items: []} is valid and common', withoutEmptyMembers({ RemoveHeadersConfig: { Quantity: 0, Items: [] } }), { RemoveHeadersConfig: { Quantity: 0, Items: [] } }, ); t( 'false, 0, null and empty string are kept', withoutEmptyMembers({ a: false, b: 0, c: null, d: '' }), { a: false, b: 0, c: null, d: '' }, ); t( 'array elements are recursed into but never removed', withoutEmptyMembers({ Items: [{ Header: 'X', Sub: {} }, {}] }), { Items: [{ Header: 'X' }, {}] }, ); t( 'the custom-headers list the script builds is untouched', withoutEmptyMembers({ CustomHeadersConfig: { Quantity: 1, Items: [{ Header: 'X-Robots-Tag', Value: 'noindex', Override: true }], }, }), { CustomHeadersConfig: { Quantity: 1, Items: [{ Header: 'X-Robots-Tag', Value: 'noindex', Override: true }], }, }, ); t('stripping is idempotent', withoutEmptyMembers(stripped), stripped); /* ---- the drift comparison: {} and absent must normalise alike ------------ Round 1's repair stripped children but left `norm({})` as "{}" against `norm(undefined)` as "null", which reported permanent, unrepairable drift on the intake form's own path. */ const norm = (o) => { const v = withoutEmptyMembers(o); return JSON.stringify(isEmptyObject(v) ? null : (v ?? null)); }; t('norm({}) equals norm(undefined)', norm({}), norm(undefined)); t('norm({CorsConfig:{}}) equals norm({})', norm({ CorsConfig: {} }), norm({})); t( 'but a real difference still differs', norm({ a: 1 }) === norm({ a: 2 }), false, ); /* ---- emptyObjectPaths, the post-condition ------------------------------- */ t( 'reports the incident path', emptyObjectPaths({ SecurityHeadersConfig: LIVE_SECURITY_HEADERS }), ['SecurityHeadersConfig.ContentSecurityPolicy'], ); t( 'reports round 2s deeper path', emptyObjectPaths({ CorsConfig: { AccessControlExposeHeaders: {} } }), ['CorsConfig.AccessControlExposeHeaders'], ); t( 'reports an empty object inside an array, with its index', emptyObjectPaths({ Items: [{ Header: 'X' }, {}] }), ['Items[1]'], ); t( 'reports every one, not just the first', emptyObjectPaths({ a: {}, b: { c: {} } }), ['a', 'b.c'], ); t('silent on an empty array', emptyObjectPaths({ a: [] }), []); t( 'silent on null, undefined and primitives', emptyObjectPaths({ a: null, b: undefined, c: 1, d: 'x', e: true }), [], ); t('names the root when the whole config is empty', emptyObjectPaths({}), [ '(root)', ]); /* ---- the invariant the two functions exist to hold together ------------- */ t( 'THE INVARIANT: nothing survives the strip that the assertion would report', emptyObjectPaths( withoutEmptyMembers({ Name: 'adr-sml-pdf-noindex', SecurityHeadersConfig: LIVE_SECURITY_HEADERS, CorsConfig: { AccessControlExposeHeaders: {} }, ServerTimingHeadersConfig: {}, CustomHeadersConfig: { Quantity: 1, Items: [{ Header: 'X-Robots-Tag', Value: 'noindex', Override: true }], }, }), ), [], ); if (failures.length) { console.error( `policy-shapes: ${failures.length} FAILED\n - ${failures.join('\n - ')}`, ); process.exit(1); } console.log(`policy-shapes: ${pass} of ${pass} cases pass`);