From 35f5d000d79d6763b2065880ccde478e167c8391 Mon Sep 17 00:00:00 2001 From: Alan Agius <17563226+alan-agius4@users.noreply.github.com> Date: Fri, 6 Mar 2026 08:20:47 +0000 Subject: [PATCH] fix(@angular/build): normalize line endings for CSP hash generation When `autoCsp` reads an `index.html` with CRLF line endings, it generates hashes based on the CRLF content. However, the transformed file is always written with LF line endings, causing CSP violations. This commit ensures that script content line endings are normalized to LF before hashing to match the output file. Closes #32709 --- .../build/src/utils/index-file/auto-csp.ts | 13 +++-- .../src/utils/index-file/auto-csp_spec.ts | 53 +++++++++++++------ 2 files changed, 42 insertions(+), 24 deletions(-) diff --git a/packages/angular/build/src/utils/index-file/auto-csp.ts b/packages/angular/build/src/utils/index-file/auto-csp.ts index c50e0bfce3f2..0e1dfe3ed916 100644 --- a/packages/angular/build/src/utils/index-file/auto-csp.ts +++ b/packages/angular/build/src/utils/index-file/auto-csp.ts @@ -52,12 +52,7 @@ function isJavascriptMimeType(mimeType: string): boolean { * @returns whether to add the script tag to the dynamically loaded script tag */ function shouldDynamicallyLoadScriptTagBasedOnType(scriptType: string | undefined): boolean { - return ( - scriptType === undefined || - scriptType === '' || - scriptType === 'module' || - isJavascriptMimeType(scriptType) - ); + return !scriptType || scriptType === 'module' || isJavascriptMimeType(scriptType); } /** @@ -67,7 +62,11 @@ function shouldDynamicallyLoadScriptTagBasedOnType(scriptType: string | undefine * @returns The hash of the text formatted appropriately for CSP. */ export function hashTextContent(scriptText: string): string { - const hash = crypto.createHash(HASH_FUNCTION).update(scriptText, 'utf-8').digest('base64'); + // Normalize CRLF to LF to ensure consistent since the rewriter might normalize the line endings. + const hash = crypto + .createHash(HASH_FUNCTION) + .update(scriptText.replace(/\r\n?/g, '\n'), 'utf-8') + .digest('base64'); return `'${HASH_FUNCTION}-${hash}'`; } diff --git a/packages/angular/build/src/utils/index-file/auto-csp_spec.ts b/packages/angular/build/src/utils/index-file/auto-csp_spec.ts index 1ec5f2ab06aa..2f3337004f6e 100644 --- a/packages/angular/build/src/utils/index-file/auto-csp_spec.ts +++ b/packages/angular/build/src/utils/index-file/auto-csp_spec.ts @@ -15,13 +15,13 @@ const getCsps = (html: string) => { ).map((m) => m[1]); // Only capture group. }; -const ONE_HASH_CSP = +const CSP_SINGLE_HASH_REGEX = /script-src 'strict-dynamic' 'sha256-[^']+' https: 'unsafe-inline';object-src 'none';base-uri 'self';/; -const TWO_HASH_CSP = +const CSP_TWO_HASHES_REGEX = /script-src 'strict-dynamic' (?:'sha256-[^']+' ){2}https: 'unsafe-inline';object-src 'none';base-uri 'self';/; -const FOUR_HASH_CSP = +const CSP_FOUR_HASHES_REGEX = /script-src 'strict-dynamic' (?:'sha256-[^']+' ){4}https: 'unsafe-inline';object-src 'none';base-uri 'self';/; describe('auto-csp', () => { @@ -38,8 +38,8 @@ describe('auto-csp', () => { `); const csps = getCsps(result); - expect(csps.length).toBe(1); - expect(csps[0]).toMatch(ONE_HASH_CSP); + expect(csps).toHaveSize(1); + expect(csps[0]).toMatch(CSP_SINGLE_HASH_REGEX); expect(csps[0]).toContain(hashTextContent("console.log('foo');")); }); @@ -56,8 +56,8 @@ describe('auto-csp', () => { `); const csps = getCsps(result); - expect(csps.length).toBe(1); - expect(csps[0]).toMatch(ONE_HASH_CSP); + expect(csps).toHaveSize(1); + expect(csps[0]).toMatch(CSP_SINGLE_HASH_REGEX); expect(result).toContain(`var scripts = [['./main.js', '', false, false]];`); }); @@ -74,8 +74,8 @@ describe('auto-csp', () => { `); const csps = getCsps(result); - expect(csps.length).toBe(1); - expect(csps[0]).toMatch(ONE_HASH_CSP); + expect(csps).toHaveSize(1); + expect(csps[0]).toMatch(CSP_SINGLE_HASH_REGEX); // Our loader script appears after the HTML text content. expect(result).toMatch( /Some text<\/div>\s* +
Some text
+ + \r\n + `); + + const csps = getCsps(result); + expect(result).not.toContain(`\r\n`); + expect(csps).toHaveSize(1); + expect(csps[0]).toMatch(CSP_SINGLE_HASH_REGEX); + expect(csps[0]).toContain(hashTextContent(`\r\nconsole.log('foo');\r\n`)); + }); });