squirrelscan
GitHub

Subresource Integrity: which scripts need an integrity hash

Cross-origin scripts and stylesheets with no integrity hash can be swapped by a compromised CDN. squirrel lists every one, page by page.

What Subresource Integrity is

Subresource Integrity (SRI) is an integrity attribute on a <script> or a <link rel="stylesheet"> that carries a cryptographic hash of the file the page expects. The browser fetches the resource, hashes it, and refuses to run or apply it when the hash does not match. That closes the gap where a third-party host serves different bytes than the ones you reviewed.

SRI only works on assets whose content never changes at a given URL. A versioned CDN build qualifies. A script that updates itself server-side does not, because the pinned hash goes stale on the vendor’s next deploy and the embed stops loading. Same-origin resources do not need it: anyone who can change them can change the page that references them.

What squirrel checks

The rule runs on every crawled page. It looks at every <script src> and every <link rel~="stylesheet" href> whose URL resolves to a cross-origin http: or https: address, and emits a single check named sri:

  • Warn when one or more of those resources has a missing or empty integrity attribute. Report message: 2 cross-origin resource(s) without Subresource Integrity. The wording is the same for one resource or many. Each URL is listed and labelled script or stylesheet. Severity warning.
  • Pass when every cross-origin resource carries integrity. Message: All cross-origin scripts and stylesheets use Subresource Integrity.
  • Info when the page loads no cross-origin scripts or stylesheets at all. Message: No cross-origin scripts or stylesheets on this page — SRI not applicable.

Relative and protocol-relative URLs are resolved against the page URL before the origin comparison, so //cdn.example.com/x.js counts as cross-origin and /local.js does not. Eleven hostnames are exempt because their vendors document SRI as unsupported: js.stripe.com, checkout.stripe.com, www.paypal.com, www.paypalobjects.com, www.google.com, www.gstatic.com, js.hcaptcha.com, www.googletagmanager.com, www.google-analytics.com, ssl.google-analytics.com and connect.facebook.net.

Two paths on cdn.shopify.com are exempt too: /shopifycloud/ and /storefront/. Shopify injects the scripts under them into every store and updates them in place, so a store owner cannot pin a hash. The rest of cdn.shopify.com, such as theme assets under /s/files/ and theme app extensions under /extensions/, is still checked: those files are versioned and can carry integrity. Add more hosts with the exempt_hosts option.

Elements inside <noscript> are skipped: a browser with scripting enabled reads <noscript> content as plain text, so nothing in it loads or renders. See content inside noscript.

How to fix it

<script
  src="https://cdn.example.com/lib@1.4.2/lib.min.js"
  integrity="sha384-BASE64_HASH_OF_THE_FILE"
  crossorigin="anonymous"
></script>

The attribute goes on the tag that loads the resource. For a cross-origin classic script like this one, add crossorigin="anonymous" and make sure the host returns a permissive Access-Control-Allow-Origin, or the browser cannot read the body to hash it. Module scripts already fetch with CORS, and a same-origin resource needs no crossorigin attribute at all. jsDelivr, cdnjs and unpkg publish the hash next to the file URL.

A self-updating third-party script that is not on the exemption list still gets flagged. Pinning a hash there breaks the embed on the vendor’s next release, so list its host in exempt_hosts instead of inventing an integrity value.

Rule IDsecurity/sri
CategorySecurity
ScopePer-page
Severitywarning
Weight5/10

Options

OptionTypeDefaultDescription
exempt_hostsarray of hostnames[]Extra hostnames whose scripts and stylesheets update in place, exempt from the check

Each entry is a bare hostname such as static.klaviyo.com, matched exactly, ignoring case and a terminal dot: no scheme, port, path or * wildcard. Every resource from that host is exempt, so list only hosts whose files change at the same URL. An entry that is not a hostname is reported by squirrel config validate, and squirrel audit stops before it crawls, naming the entry: [rule_options."security/sri"] exempt_hosts.0: must be a hostname such as static.example.com, with no scheme, port or path.

Configuration example

[rule_options."security/sri"]
exempt_hosts = ["static.klaviyo.com", "plausible.io"]

Enable / disable

Disable this rule

[rules]
disable = ["security/sri"]

Disable all Security rules

[rules]
disable = ["security/*"]

Enable only this rule

[rules]
enable = ["security/sri"]

enable replaces the default ["*"], so this is the only rule selected. Leave disable out: a rule that matches any disable pattern is skipped even when enable names it. Only an enabled setting under a rule’s own [rule_options] table overrides both lists.

Security findings ship in every audit next to the SEO, performance and agent experience rules. See Website security scan with AI for how an agent works through a report.

References

Check your site

Run squirrel audit https://example.com and open the Security section of the report. Every cross-origin script and stylesheet without an integrity hash is listed by page. Signed-out local audits need no account and cost nothing. Signed-in audits spend credits.

Type to search…

↑↓ navigate↵ openesc close