Configuring SRI for jsDelivr and unpkg
Permalink to "Configuring SRI for jsDelivr and unpkg"Part of CDN Trust Mapping & Routing, this page shows how to pin an exact version on jsDelivr or unpkg and attach a SHA-384 integrity plus crossorigin="anonymous" so a public-registry asset can never execute with unexpected bytes.
Quick Reference
Permalink to "Quick Reference"| Property | jsDelivr | unpkg |
|---|---|---|
| npm URL form | https://cdn.jsdelivr.net/npm/[email protected]/dist/file.js |
https://unpkg.com/[email protected]/dist/file.js |
| GitHub URL form | https://cdn.jsdelivr.net/gh/user/[email protected]/file.js |
not applicable |
| Version pinning | Exact @1.2.3 required for SRI |
Exact @1.2.3 required for SRI |
| Breaks SRI | @latest, @3, no version |
@latest, @3, no version |
| Hash source | “Copy SRI Hash” button on file page | ?meta JSON integrity field |
| CORS header | Access-Control-Allow-Origin: * |
Access-Control-Allow-Origin: * |
| Default algorithm | sha384 |
sha384 |
Pin the exact version, hash the exact URL, and always ship integrity and crossorigin together.
Why Exact Versions Are Non-Negotiable
Permalink to "Why Exact Versions Are Non-Negotiable"Subresource Integrity binds an HTML tag to one specific sequence of bytes. A public registry CDN like jsDelivr or unpkg serves many byte sequences behind a single package name, disambiguated by the version in the URL. When you pin npm/[email protected]/dist/vue.global.prod.js, the CDN treats that URL as immutable and serves identical bytes forever — which is exactly what an integrity token needs. The moment you use a mutable reference such as @latest, @3, or a bare package name, the CDN is free to resolve it to newer bytes on any request. Your frozen sha384-… token then describes an old file the edge no longer serves, and the browser blocks it. This mutability question is the crux of applying SRI to any third-party origin, and it feeds directly into the per-origin policy work in Mapping CDN Origins to SRI Policies.
Both CDNs return permissive CORS headers, so the browser can read the response body to compute the hash — provided the tag opts into a CORS-eligible fetch with crossorigin="anonymous".
The two CDNs resolve URLs differently in ways that matter for integrity. jsDelivr serves from npm (/npm/), GitHub (/gh/), and WordPress (/wp/) namespaces, and every one supports an exact-version pin. unpkg mirrors the npm registry only, resolving [email protected]/dist/file.js to the file inside that published tarball. In both cases the published version is immutable — npm forbids republishing a version’s contents — so a fully pinned URL is a stable hashing target by construction. Where mutability creeps back in is any layer that transforms the tarball’s bytes on the way out: jsDelivr’s on-the-fly minification, jsDelivr’s combine concatenation, or an entrypoint redirect when you omit the file path and let the CDN pick main from package.json. Reference an explicit file path, and the URL you hash is the URL the browser fetches.
Canonical Implementation
Permalink to "Canonical Implementation"A version-pinned jsDelivr script with a SHA-384 integrity token and crossorigin:
<script
src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.global.prod.js"
integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC"
crossorigin="anonymous"></script>
Get the token straight from the file page’s Copy SRI Hash button on jsdelivr.com, or compute it from the exact URL so you hash precisely what the edge serves — the same pipe described in Generating SRI Hashes with OpenSSL and shasum:
curl -sL https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.global.prod.js \
| openssl dgst -sha384 -binary | openssl base64 -A
# → prefix the output with sha384- for the integrity attribute
Read that URL as four independent decisions rather than one string. The origin decides which CORS headers come back, the namespace decides which registry the path is resolved against, the version segment decides whether the response is immutable, and the file path decides whether the edge serves the tarball’s bytes or something it generated. Get any one of the four wrong and the digest you computed describes a response the browser will not receive.
Variant Examples
Permalink to "Variant Examples"unpkg pinned URL with ?meta hash source
Permalink to "unpkg pinned URL with ?meta hash source"unpkg serves the same package tree and exposes per-file digests through its metadata endpoint. Appending ?meta to a pinned URL returns JSON whose file entries include an integrity field:
curl -s 'https://unpkg.com/[email protected]/umd/react.production.min.js?meta'
# → { "path": "/umd/react.production.min.js", "integrity": "sha384-…", ... }
<script
src="https://unpkg.com/[email protected]/umd/react.production.min.js"
integrity="sha384-…"
crossorigin="anonymous"></script>
Stylesheet from jsDelivr
Permalink to "Stylesheet from jsDelivr"The same rules apply to <link> — pin the version, add integrity and crossorigin:
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css"
integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH"
crossorigin="anonymous">
Combined jsDelivr bundle — handle with care
Permalink to "Combined jsDelivr bundle — handle with care"jsDelivr can concatenate several pinned files into one response with the combine path. The digest must be taken over the combined output, and the URL must pin every component version:
<!-- Hash computed over the exact combined response bytes -->
<script
src="https://cdn.jsdelivr.net/combine/npm/[email protected]/dist/vue.global.prod.js,npm/[email protected]/dist/vue-router.global.prod.js"
integrity="sha384-…"
crossorigin="anonymous"></script>
Prefer separate pinned tags where you can: a combined URL couples several packages into one hash, so bumping any one version invalidates the token and forces you to recompute the whole bundle.
Gotchas and Edge Cases
Permalink to "Gotchas and Edge Cases"@latestand range URLs silently break on the next publish.npm/pkg@latest,npm/pkg@3, and barenpm/pkgall resolve to a moving target. The bytes change when the package updates, your frozen token no longer matches, and every user is blocked at once. Only an exact@1.2.3URL is SRI-safe.- Omitting
crossorigin="anonymous"blocks the file even with a correct hash. Without it the browser issues a no-CORS request and receives an opaque response whose body it cannot read. SRI cannot validate opaque responses, so the resource is blocked before the hash is ever compared — a failure that looks nothing like a hash mismatch and is easy to misdiagnose, as How CORS and crossorigin Affect SRI works through in detail. - jsDelivr auto-minification can change the bytes. When you request a
.min.jspath that the package did not publish, jsDelivr generates a minified version on the fly. If jsDelivr later updates its minifier, those generated bytes can differ, invalidating your token. Pin to a file the package actually publishes, and always hash the exact URL you reference. - Hash the delivered bytes, not a local
node_modulescopy. A local install may differ from the registry tarball the CDN serves (build metadata, line endings). Compute the digest from the live pinned URL, not from disk. - Entrypoint URLs without a file path can redirect.
https://unpkg.com/[email protected](no file) resolves to the package’smainfield via a redirect, and the target can differ between versions. Always reference the explicit file —/umd/react.production.min.js— so the hashed URL is deterministic. - Copy-button hashes still deserve a re-check. The jsDelivr button and unpkg
?metaare conveniences. Re-hashing the live URL in CI guarantees the token matches what the edge serves today.
Those failure modes reduce to three questions you can ask of any public-registry URL before you hash it: is the version pinned, is the file path explicit, and does the package actually publish that file? Each “no” lands on a different remedy, and only the path where all three answer “yes” gives you a URL whose bytes are fixed for the lifetime of the tag. If none of the three can be satisfied for a given vendor script, the fallback is to stop consuming it from a public origin at all and follow Self-Hosting Third-Party Scripts instead, where you own the bytes and therefore own the hash.
Verification Steps
Permalink to "Verification Steps"1. Confirm both attributes are present
Permalink to "1. Confirm both attributes are present"grep -E 'cdn\.jsdelivr\.net|unpkg\.com' dist/index.html | grep -E 'integrity=|crossorigin='
Expected — every third-party tag shows a sha384- token adjacent to crossorigin="anonymous".
2. Re-hash the live pinned URL
Permalink to "2. Re-hash the live pinned URL"curl -sL https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.global.prod.js \
| openssl dgst -sha384 -binary | openssl base64 -A
The output must match the token in your HTML exactly. A mismatch means the URL is mutable or the wrong bytes were hashed.
3. Confirm clean browser validation
Permalink to "3. Confirm clean browser validation"Load the page with DevTools open. A stale or wrong token produces:
Failed to find a valid digest in the 'integrity' attribute for resource
'https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.global.prod.js' with
computed SHA-384 integrity 'sha384-…'. The resource has been blocked.
No error plus a 200 OK in the Network tab confirms the pin and token are correct.
Frequently Asked Questions
Permalink to "Frequently Asked Questions"Why does @latest break Subresource Integrity?
An @latest or range URL resolves to whatever version the CDN currently considers newest. When the package publishes an update the served bytes change, but the integrity token in your HTML is frozen to the old bytes, so the browser blocks the resource. SRI only works against an immutable, exact-version URL.
Where do I get the SRI hash for a jsDelivr file?
Each file page on jsdelivr.com has a Copy SRI Hash button that emits a sha384- token for the exact version you selected. You can also compute it yourself with openssl over the pinned URL, which is the most reliable source because it hashes the precise bytes the edge serves.
Does unpkg expose integrity hashes?
Yes. Appending ?meta to a pinned unpkg URL returns a JSON listing in which each file entry includes an integrity field with a sha-384 digest. Treat it as a convenience and still re-hash the delivered bytes before shipping to production.
Can I use crossorigin with jsDelivr and unpkg?
You must. Both CDNs return Access-Control-Allow-Origin: * so crossorigin=“anonymous” produces a CORS-eligible response the browser can hash. Omitting crossorigin yields an opaque response and the browser blocks the resource without ever comparing the hash.
Related
Permalink to "Related"- CDN Trust Mapping & Routing — parent workflow that classifies public-registry origins into trust tiers and gates deployments
- Mapping CDN Origins to SRI Policies — the per-origin policy matrix that governs which registries may serve which paths
- How to Calculate SHA-256 vs SHA-384 for SRI — computing the
sha384-token from a URL or file - SRI with Cloudflare and Fastly Edge Transforms — what to do when the edge rewrites, compresses or injects into a response you have already hashed