Command
build
Description
When subresourceIntegrity and security.autoCsp are both enabled, the entry scripts lose their SRI protection.
With only subresourceIntegrity: true, the index.html contains:
<script src="polyfills-ABC.js" type="module" crossorigin="anonymous" integrity="sha384-..."></script>
<script src="main-XYZ.js" type="module" crossorigin="anonymous" integrity="sha384-..."></script>
When autoCsp is also enabled, these tags get replaced by the generated loader script. The loader only carries over src, type, async and defer:
<script>
var scripts = [['polyfills-ABC.js', 'module', false, false],['main-XYZ.js', 'module', false, false]];
scripts.forEach(function(scriptUrl) {
var s = document.createElement('script');
s.src = scriptUrl[0];
s.type = scriptUrl[1];
s.async = !!scriptUrl[2];
s.defer = !!scriptUrl[3];
document.lastElementChild.appendChild(s);
});
</script>
So integrity and crossorigin are gone and the browser no longer verifies the two entry bundles. The lazy chunks are fine, because their hashes live in the inline importmap, which autoCsp hashes into the policy and leaves as is. The entry scripts are the only gap.
There is no warning about this at build time. Users who enable both options probably expect them to work together.
The cause is createLoaderScript in packages/angular/build/src/utils/index-file/auto-csp.ts: the tuples collected in the startTag handler don't include the integrity and crossorigin attributes.
Reproduced with @angular/build 22.1.2.
Describe the solution you'd like
Extend the loader tuples with the two attributes. Dynamically created script elements support both properties, so this should be all that's needed:
var scripts = [['polyfills-ABC.js', 'module', false, false, 'sha384-...', 'anonymous'], ...];
scripts.forEach(function (scriptUrl) {
var s = document.createElement('script');
s.src = scriptUrl[0];
s.type = scriptUrl[1];
s.async = !!scriptUrl[2];
s.defer = !!scriptUrl[3];
if (scriptUrl[4]) { s.integrity = scriptUrl[4]; }
if (scriptUrl[5]) { s.crossOrigin = scriptUrl[5]; }
document.lastElementChild.appendChild(s);
});
The loader is hashed into the CSP after it is generated, so the hash stays correct.
Describe alternatives you've considered
Post-processing the built index.html to patch the loader and recompute its hash in the meta tag. Works, but depends on the internals of the generated loader, which can change between Angular versions.
Keeping the plain script tags for entry scripts is not an option, since 'strict-dynamic' blocks parser-inserted script tags regardless of host allowlists. That is the reason the loader exists in the first place.
Command
build
Description
When
subresourceIntegrityandsecurity.autoCspare both enabled, the entry scripts lose their SRI protection.With only
subresourceIntegrity: true, the index.html contains:When
autoCspis also enabled, these tags get replaced by the generated loader script. The loader only carries oversrc,type,asyncanddefer:So
integrityandcrossoriginare gone and the browser no longer verifies the two entry bundles. The lazy chunks are fine, because their hashes live in the inline importmap, which autoCsp hashes into the policy and leaves as is. The entry scripts are the only gap.There is no warning about this at build time. Users who enable both options probably expect them to work together.
The cause is
createLoaderScriptinpackages/angular/build/src/utils/index-file/auto-csp.ts: the tuples collected in thestartTaghandler don't include theintegrityandcrossoriginattributes.Reproduced with
@angular/build22.1.2.Describe the solution you'd like
Extend the loader tuples with the two attributes. Dynamically created script elements support both properties, so this should be all that's needed:
The loader is hashed into the CSP after it is generated, so the hash stays correct.
Describe alternatives you've considered
Post-processing the built index.html to patch the loader and recompute its hash in the meta tag. Works, but depends on the internals of the generated loader, which can change between Angular versions.
Keeping the plain script tags for entry scripts is not an option, since
'strict-dynamic'blocks parser-inserted script tags regardless of host allowlists. That is the reason the loader exists in the first place.