Summary
- Context: The
get_as_url_to_tag_dict function in webpack_loader/utils.py generates HTML tags for Webpack bundles, supporting both standard tags and <link rel="preload"> tags.
- Bug: When generating preload tags for JavaScript bundles (
is_preload=True), the function fails to include integrity and nonce attributes.
- Actual vs. expected: For JavaScript preloads, it generates a basic
<link> tag without security attributes, whereas for regular <script> tags and all CSS tags (both regular and preload), it correctly includes them.
- Impact: Security policies like CSP and SRI are violated for preloaded JavaScript, which can cause the browser to block the preload or redundantly fetch the resource when the actual
<script> tag is encountered.
Code with bug
if chunk['name'].endswith(('.js', '.js.gz')):
if is_preload:
result[chunk['url']] = (
'<link rel="preload" as="script" href="{0}" {1}/>' # <-- BUG 🔴 Missing {2} and {3} for integrity and nonce
).format(''.join([chunk['url'], suffix]), attrs)
else:
result[chunk['url']] = (
'<script src="{0}"{2}{3}{1}></script>'
).format(
''.join([chunk['url'], suffix]),
attrs,
loader.get_integrity_attr(chunk, request, attrs_l),
loader.get_nonce_attr(chunk, request, attrs_l),
)
Evidence
- Reproduction Test: A test case was created where a request with a CSP nonce and a configuration with integrity enabled were used.
def test_js_preload_missing_integrity_and_nonce(self):
with patch('webpack_loader.utils.get_loader') as mock_get_loader:
mock_loader = mock_get_loader.return_value
mock_loader.get_bundle.return_value = [
{'name': 'main.js', 'url': '/static/main.js', 'integrity': 'sha256-js-integrity'}
]
mock_loader.config = {'INTEGRITY': True, 'CSP_NONCE': True, 'CACHE': False}
mock_loader.get_integrity_attr.return_value = ' integrity="sha256-js-integrity" '
mock_loader.get_nonce_attr.return_value = 'nonce="test-nonce" '
request = self.factory.get('/')
request.csp_nonce = "test-nonce"
tag_dict = get_as_url_to_tag_dict('main', request=request, is_preload=True, extension='js')
tag = tag_dict['/static/main.js']
self.assertIn('integrity="sha256-js-integrity"', tag) # Fails here
self.assertIn('nonce="test-nonce"', tag) # Fails here
- Comparison with CSS: In the same file, CSS preloads are handled correctly by including all placeholders:
elif chunk['name'].endswith(('.css', '.css.gz')):
result[chunk['url']] = (
'<link href="{0}" rel={2}{3}{4}{1}/>'
).format(
''.join([chunk['url'], suffix]),
attrs,
'"stylesheet"' if not is_preload else '"preload" as="style"',
loader.get_integrity_attr(chunk, request, attrs_l),
loader.get_nonce_attr(chunk, request, attrs_l),
)
Why has this bug gone undetected?
Most users likely use render_bundle without the is_preload=True flag, or they do not have both SRI (INTEGRITY=True) and CSP (CSP_NONCE=True) configured. When preloading is used without these security features, the generated tag is valid HTML. Furthermore, if a browser ignores the preload due to missing integrity but later loads the script tag successfully, the developer might only notice a slight performance degradation (double fetch) rather than an outright failure, unless they check browser console warnings.
Recommended fix
Update the JavaScript preload block in get_as_url_to_tag_dict to include the integrity and nonce attributes, mirroring the logic used for regular script tags and CSS tags:
if is_preload:
result[chunk['url']] = (
'<link rel="preload" as="script" href="{0}"{2}{3} {1}/>' # <-- FIX 🟢
).format(
''.join([chunk['url'], suffix]),
attrs,
loader.get_integrity_attr(chunk, request, attrs_l), # <-- FIX 🟢
loader.get_nonce_attr(chunk, request, attrs_l), # <-- FIX 🟢
)
Related bugs
- In the same file,
_filter_by_extension uses a strict endswith check that excludes .js.gz and .css.gz files when filtering by js or css extensions, despite get_as_url_to_tag_dict having logic to support these compressed formats.
- The use of
OrderedDict[str, str]() as a constructor (line 78) causes a TypeError in Python 3.8, which is a supported version according to setup.py.
History
This bug was introduced in commit 755c67e (@hugobessa, 2024-07-28, PR #413). While adding support for CSP nonces and improving Subresource Integrity (SRI) handling, the change updated standard script tags and all CSS tags (including preloads) but neglected to apply the same security attributes to JavaScript preload tags.
Summary
get_as_url_to_tag_dictfunction inwebpack_loader/utils.pygenerates HTML tags for Webpack bundles, supporting both standard tags and<link rel="preload">tags.is_preload=True), the function fails to includeintegrityandnonceattributes.<link>tag without security attributes, whereas for regular<script>tags and all CSS tags (both regular and preload), it correctly includes them.<script>tag is encountered.Code with bug
Evidence
Why has this bug gone undetected?
Most users likely use
render_bundlewithout theis_preload=Trueflag, or they do not have both SRI (INTEGRITY=True) and CSP (CSP_NONCE=True) configured. When preloading is used without these security features, the generated tag is valid HTML. Furthermore, if a browser ignores the preload due to missing integrity but later loads the script tag successfully, the developer might only notice a slight performance degradation (double fetch) rather than an outright failure, unless they check browser console warnings.Recommended fix
Update the JavaScript preload block in
get_as_url_to_tag_dictto include the integrity and nonce attributes, mirroring the logic used for regular script tags and CSS tags:Related bugs
_filter_by_extensionuses a strictendswithcheck that excludes.js.gzand.css.gzfiles when filtering byjsorcssextensions, despiteget_as_url_to_tag_dicthaving logic to support these compressed formats.OrderedDict[str, str]()as a constructor (line 78) causes aTypeErrorin Python 3.8, which is a supported version according tosetup.py.History
This bug was introduced in commit 755c67e (@hugobessa, 2024-07-28, PR #413). While adding support for CSP nonces and improving Subresource Integrity (SRI) handling, the change updated standard script tags and all CSS tags (including preloads) but neglected to apply the same security attributes to JavaScript preload tags.