66 * found in the LICENSE file at https://angular.dev/license
77 */
88
9- import { autoCsp , hashScriptText } from './auto-csp' ;
9+ import { autoCsp , hashTextContent } from './auto-csp' ;
10+
11+ // Utility function to grab the meta tag CSPs from the HTML response.
12+ const getCsps = ( html : string ) => {
13+ return Array . from (
14+ html . matchAll ( / < m e t a h t t p - e q u i v = " C o n t e n t - S e c u r i t y - P o l i c y " c o n t e n t = " ( [ ^ " ] * ) " > / g) ,
15+ ) . map ( ( m ) => m [ 1 ] ) ; // Only capture group.
16+ } ;
17+
18+ const ONE_HASH_CSP =
19+ / s c r i p t - s r c ' s t r i c t - d y n a m i c ' ' s h a 2 5 6 - [ ^ ' ] + ' h t t p s : ' u n s a f e - i n l i n e ' ; o b j e c t - s r c ' n o n e ' ; b a s e - u r i ' s e l f ' ; / ;
20+
21+ const FOUR_HASH_CSP =
22+ / s c r i p t - s r c ' s t r i c t - d y n a m i c ' (?: ' s h a 2 5 6 - [ ^ ' ] + ' ) { 4 } h t t p s : ' u n s a f e - i n l i n e ' ; o b j e c t - s r c ' n o n e ' ; b a s e - u r i ' s e l f ' ; / ;
1023
1124describe ( 'auto-csp' , ( ) => {
1225 it ( 'should rewrite a single inline script' , async ( ) => {
@@ -21,9 +34,10 @@ describe('auto-csp', () => {
2134 </html>
2235 ` ) ;
2336
24- expect ( result ) . toContain (
25- `<meta http-equiv="Content-Security-Policy" content="script-src 'strict-dynamic' ${ hashScriptText ( "console.log('foo');" ) } https: 'unsafe-inline';object-src 'none';base-uri 'self';">` ,
26- ) ;
37+ const csps = getCsps ( result ) ;
38+ expect ( csps . length ) . toBe ( 1 ) ;
39+ expect ( csps [ 0 ] ) . toMatch ( ONE_HASH_CSP ) ;
40+ expect ( csps [ 0 ] ) . toContain ( hashTextContent ( "console.log('foo');" ) ) ;
2741 } ) ;
2842
2943 it ( 'should rewrite a single source script' , async ( ) => {
@@ -38,9 +52,9 @@ describe('auto-csp', () => {
3852 </html>
3953 ` ) ;
4054
41- expect ( result ) . toContain (
42- `<meta http-equiv="Content-Security-Policy" content="script-src 'strict-dynamic' 'sha256-cfa69N/DhgtxzDzIHo+IFj9KPigQLDJgb6ZGZa3g5Cs=' https: 'unsafe-inline';object-src 'none';base-uri 'self';">` ,
43- ) ;
55+ const csps = getCsps ( result ) ;
56+ expect ( csps . length ) . toBe ( 1 ) ;
57+ expect ( csps [ 0 ] ) . toMatch ( ONE_HASH_CSP ) ;
4458 expect ( result ) . toContain ( `var scripts = [['./main.js', undefined, false, false]];` ) ;
4559 } ) ;
4660
@@ -56,9 +70,9 @@ describe('auto-csp', () => {
5670 </html>
5771 ` ) ;
5872
59- expect ( result ) . toContain (
60- `<meta http-equiv="Content-Security-Policy" content="script-src 'strict-dynamic' 'sha256-cfa69N/DhgtxzDzIHo+IFj9KPigQLDJgb6ZGZa3g5Cs=' https: 'unsafe-inline';object-src 'none';base-uri 'self';">` ,
61- ) ;
73+ const csps = getCsps ( result ) ;
74+ expect ( csps . length ) . toBe ( 1 ) ;
75+ expect ( csps [ 0 ] ) . toMatch ( ONE_HASH_CSP ) ;
6276 // Our loader script appears after the HTML text content.
6377 expect ( result ) . toMatch (
6478 / S o m e t e x t < \/ d i v > \s * < s c r i p t > \s * v a r s c r i p t s = \[ \[ ' .\/ m a i n .j s ' , u n d e f i n e d , f a l s e , f a l s e \] \] ; / ,
@@ -80,9 +94,9 @@ describe('auto-csp', () => {
8094 </html>
8195 ` ) ;
8296
83- expect ( result ) . toContain (
84- `<meta http-equiv="Content-Security-Policy" content="script-src 'strict-dynamic' 'sha256-oK8+CQgKHPljcYJpTNKJt/y0A0oiBIm3LRke3EhzHVQ=' https: 'unsafe-inline';object-src 'none';base-uri 'self';">` ,
85- ) ;
97+ const csps = getCsps ( result ) ;
98+ expect ( csps . length ) . toBe ( 1 ) ;
99+ expect ( csps [ 0 ] ) . toMatch ( ONE_HASH_CSP ) ;
86100 expect ( result ) . toContain (
87101 `var scripts = [['./main1.js', undefined, false, false],['./main2.js', undefined, true, false],['./main3.js', 'module', true, true]];` ,
88102 ) ;
@@ -107,9 +121,12 @@ describe('auto-csp', () => {
107121 </html>
108122 ` ) ;
109123
110- expect ( result ) . toContain (
111- `<meta http-equiv="Content-Security-Policy" content="script-src 'strict-dynamic' ${ hashScriptText ( "console.log('foo');" ) } 'sha256-6q4qOp9MMB///5kaRda2I++J9l0mJiqWRxQ9/8hoSyw=' ${ hashScriptText ( "console.log('bar');" ) } 'sha256-AUmEDzNdja438OLB3B8Opyxy9B3Tr1Tib+aaGZdhhWQ=' https: 'unsafe-inline';object-src 'none';base-uri 'self';">` ,
112- ) ;
124+ const csps = getCsps ( result ) ;
125+ expect ( csps . length ) . toBe ( 1 ) ;
126+ // Exactly four hashes for the four scripts that remain (inline, loader, inline, loader).
127+ expect ( csps [ 0 ] ) . toMatch ( FOUR_HASH_CSP ) ;
128+ expect ( csps [ 0 ] ) . toContain ( hashTextContent ( "console.log('foo');" ) ) ;
129+ expect ( csps [ 0 ] ) . toContain ( hashTextContent ( "console.log('bar');" ) ) ;
113130 // Loader script for main.js and main2.js appear after 'foo' and before 'bar'.
114131 expect ( result ) . toMatch (
115132 / c o n s o l e .l o g \( ' f o o ' \) ; < \/ s c r i p t > \s * < s c r i p t > \s * v a r s c r i p t s = \[ \[ ' .\/ m a i n .j s ' , u n d e f i n e d , f a l s e , f a l s e \] , \[ ' .\/ m a i n 2 .j s ' , u n d e f i n e d , f a l s e , f a l s e \] \] ; [ \s \S ] * c o n s o l e .l o g \( ' b a r ' \) ; / ,
0 commit comments