VNX-353: Third-party script or stylesheet loaded without Subresource Integrity
Reports a <script src> or <link rel="stylesheet" href> that loads code from another origin (an absolute http(s):// or protocol-relative // URL) with no integrity= hash, in HTML and server-side templates.
| Field | Value |
|---|---|
| Rule ID | VNX-353 |
| Severity | Medium |
| SARIF level | warning |
| Kind | sast |
| Languages | HTML, PHP |
| CWE | CWE-353 |
| CAPEC | CAPEC-66 |
| ATT&CK | T1190 |
| CWSS vector | CWSS:1.0/TI:M/AP:RU/AL:S/IC:N/FC:LT/RP:RU/RL:S/AV:L/AS:N/IN:A/SC:A/BI:M/DI:H/EX:M/EC:N/P:C |
| Tags | integrity-check subresource-integrity supply-chain cwe-353 |
| Source | internal/sast/rules/vnx-353.rego |
Overview
VNX-353 maps to CWE-353: Missing Support for Integrity Check. It reports, in .html/.htm pages and template files (ERB, EJS, Handlebars, Mustache, Jinja/Django/Nunjucks/Twig/Liquid, JSP, Razor, PHP, Vue, Svelte, Go templates):
- a
<script>whosesrcis an absolutehttp(s)://or protocol-relative//URL, and - a
<link rel="stylesheet">whosehrefis such a URL,
when the tag (which may span up to six lines) carries no integrity= attribute.
Not reported: tags with integrity=, same-origin and relative URLs, a URL built from a template expression, other <link> relations (canonical, preconnect, icon, alternate), anchors, HTML-escaped and commented-out tags, prose files, and endpoints whose content varies per request and cannot be pinned by a hash (Google Tag Manager/Analytics, reCAPTCHA, hCaptcha, Turnstile, Google Fonts and Bunny Fonts CSS, Google Maps/APIs/Sign-In, Stripe.js, Font Awesome kits, Facebook and Twitter widgets).
Why This Matters
A script from a CDN runs with the full authority of your page: it can read the DOM, cookies not marked HttpOnly and form input. Without an integrity hash the browser runs whatever the CDN serves, so a compromised CDN account, a hijacked package version or (for http://) anyone on the network path can replace it.
What Gets Flagged
<!-- FLAGGED -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css">
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.8.2/angular.min.js"></script>
<!-- NOT FLAGGED -->
<script src="https://code.jquery.com/jquery-3.7.1.min.js"
integrity="sha256-/JqT3SQfawRcv/BIHPThkBvs0OEvtFFmqPF/lYI/Cxo="
crossorigin="anonymous"></script>
<script src="/static/js/app.js"></script>
Remediation
- Add
integrity="sha384-..."andcrossorigin="anonymous"to every third-party script and stylesheet; CDNs such as jsDelivr and cdnjs publish the hash. - Pin an exact version in the URL: a hash cannot be kept for
@latestor a major-version range. - Better still, bundle or self-host the dependency so it ships with your own code.
- Add a Content-Security-Policy
script-srcthat lists the allowed origins.
References
- CWE-353: Missing Support for Integrity Check
- MDN: Subresource Integrity
- OWASP Third Party JavaScript Management Cheat Sheet
Suppressing this rule
Put a nosec comment on the line, or on line 1 to cover the whole file. Use the comment marker of the file’s language (#, //, -- or ;):
# nosec vnx-353
// nosec vnx-353
Or record an ignore rule: vulnetix ignore add --rule vnx-353 --reason "...". See Suppressing findings.