-
Notifications
You must be signed in to change notification settings - Fork 341
Description
Summary
- Context: The
get_as_url_to_tag_dictfunction inwebpack_loader/utils.pygenerates 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 includeintegrityandnonceattributes. - 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_extensionuses a strictendswithcheck that excludes.js.gzand.css.gzfiles when filtering byjsorcssextensions, despiteget_as_url_to_tag_dicthaving logic to support these compressed formats. - The use of
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.