Supply Chain Security for CDN: Protecting Third-Party Script Dependencies

· Sarah Mitchell · 10 min read

Every modern website loads JavaScript from third-party CDNs. Analytics libraries, UI frameworks, polyfills, font loaders — these dependencies form a supply chain that most teams never audit after initial integration. When a CDN-hosted library is compromised, every site loading it becomes an attack vector. The Polyfill.io incident proved this is not theoretical: a domain change gave attackers control over scripts embedded in more than 100,000 websites. Securing this supply chain requires understanding the attack surface and layering multiple defense mechanisms. For foundational CDN security concepts, see CDN Security Headers: CSP, HSTS, and Beyond.

The CDN Supply Chain Attack Surface

Third-party scripts loaded from public CDNs like cdnjs, jsDelivr, and unpkg introduce trust dependencies that most developers do not fully map. When you add a script tag pointing to a CDN-hosted library, you trust the CDN operator, the package maintainer, their npm credentials, their CI/CD pipeline, and every transitive dependency in the package tree. A compromise anywhere in this chain delivers malicious code directly into your users' browsers with the full privileges of your origin.

CDN Supply Chain Attack Surface CDN Supply Chain — Attack Vectors Developer npm install npm Registry Package source Public CDN cdnjs / jsDelivr User Browser Executes scripts 1. Account Takeover Compromise npm credentials 2. CDN Compromise Tamper with hosted files 3. Domain Takeover Acquire CDN domain control Defense Layers SRI Hashes Verify file integrity CSP Policies Restrict script origins Import Maps Centralize versioning Version Pinning Lock dependencies
Figure 1: CDN Supply Chain Attack Surface — from developer to browser, with defense layers

The attack surface has three primary vectors. Account takeover targets npm maintainer credentials through phishing, credential stuffing, or token leakage in CI logs. CDN compromise involves directly tampering with files hosted on a CDN, either through infrastructure access or by exploiting the CDN's build pipeline. Domain takeover occurs when an attacker gains control of a CDN domain through expiration, registrar compromise, or acquisition — as happened with Polyfill.io.

Subresource Integrity: Cryptographic Verification

Subresource Integrity (SRI) is the single most effective defense against CDN supply chain attacks. SRI adds a cryptographic hash to script and link tags. The browser computes the hash of the downloaded file and refuses to execute it if the hash does not match. This guarantees that the exact file you reviewed and tested is the file your users receive, regardless of what happens on the CDN between your deployment and their request.

Performance Comparison: Edge vs Origin Processing
Metric Edge Processing Origin Processing Improvement
Latency (p50)8–15 ms120–300 ms15–20x faster
Cache Hit Ratio85–95%N/AReduces origin load
TLS Handshake1–5 ms (nearby PoP)50–150 ms10–30x faster
Bandwidth Cost$0.01–0.04/GB$0.05–0.12/GB3–5x cheaper
Availability99.99% (multi-PoP)99.9% (single DC)10x fewer outages
<!-- SRI-protected script loading -->
<script
  src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js"
  integrity="sha384-OYlGGIKFt3Xn4sthrV3GpMoVWxBKRvz3PZ2hu5GHxR0oLHBBgaGNfHqBIplDigL"
  crossorigin="anonymous"></script>

<!-- SRI for stylesheets -->
<link
  rel="stylesheet"
  href="https://cdn.jsdelivr.net/npm/[email protected]/normalize.min.css"
  integrity="sha384-n8MVjX3btmFCyo+kW3iB40MUEWqFLmjV9LeRzGCqVYMa5GWDSMXht07sFBEVPJC"
  crossorigin="anonymous">

The crossorigin="anonymous" attribute is required for SRI to work with cross-origin resources. Without it, the browser cannot access the response body to compute the hash. The integrity attribute accepts multiple space-separated hashes, allowing you to list hashes for different algorithms or different acceptable versions of a file.

Generating SRI Hashes

Generate SRI hashes from the command line or integrate them into your build pipeline:

# Generate sha384 hash for a local file
openssl dgst -sha384 -binary lodash.min.js | openssl base64 -A
# Output: OYlGGIKFt3Xn4sthrV3GpMoVWxBKRvz3PZ2hu5GHxR0oLHBBgaGNfHqBIplDigL

# Generate hash for a remote URL
curl -s https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js \
  | openssl dgst -sha384 -binary | openssl base64 -A

# Using the srihash.org API or shasum
shasum -b -a 384 lodash.min.js | awk '{ print $1 }' | xxd -r -p | base64

Automate SRI hash generation in your CI/CD pipeline. Every time a dependency is updated, regenerate and commit the new hash. This creates an auditable trail of exactly which file content was approved for production.

Content Security Policy for Script Control

CSP restricts which origins can serve executable scripts to your page. While SRI verifies file integrity, CSP controls the allowed sources. Together they form a defense-in-depth strategy: CSP blocks scripts from unauthorized domains, and SRI blocks tampered scripts from authorized domains. For a complete CSP implementation guide, see CDN Security Headers.

# Strict CSP with nonce-based script loading
Content-Security-Policy:
  default-src 'self';
  script-src 'nonce-{random}' 'strict-dynamic'
    https://cdnjs.cloudflare.com
    https://cdn.jsdelivr.net;
  style-src 'self' 'unsafe-inline'
    https://fonts.googleapis.com;
  font-src https://fonts.gstatic.com;
  connect-src 'self' https://api.example.com;
  img-src 'self' data: https:;
  frame-ancestors 'none';
  base-uri 'self';
  require-sri-for script style;

The require-sri-for directive forces SRI verification on all script and style loads. If a tag lacks an integrity attribute, the browser blocks it. This prevents developers from accidentally bypassing SRI by forgetting the hash on a new dependency.

CSP + SRI Defense Matrix CSP + SRI Defense Matrix Script Origin Allowed by CSP? SRI Hash Match? Result cdnjs.cloudflare.com/lodash.js YES YES ALLOWED evil-cdn.example/malicious.js NO N/A BLOCKED cdnjs.cloudflare.com/tampered.js YES NO BLOCKED cdn.jsdelivr.net/no-integrity.js YES MISSING require-sri-for require-sri-for directive blocks scripts without integrity attribute even from allowed origins
Figure 2: CSP + SRI defense matrix — how the two mechanisms complement each other

Import Maps: Centralized Dependency Resolution

Import maps provide a browser-native mechanism for mapping JavaScript module specifiers to specific URLs. Instead of scattering CDN URLs across hundreds of script tags, you define a single JSON mapping that the browser uses to resolve bare import specifiers. This centralizes version management and makes CDN provider switches a single-file change.

<script type="importmap">
{
  "imports": {
    "lodash": "https://cdn.jsdelivr.net/npm/[email protected]/lodash.js",
    "react": "https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.production.min.js",
    "chart.js": "https://cdn.jsdelivr.net/npm/[email protected]/dist/chart.umd.min.js"
  },
  "scopes": {
    "/admin/": {
      "react": "https://cdnjs.cloudflare.com/ajax/libs/react/18.3.0/umd/react.production.min.js"
    }
  }
}
</script>

<!-- Now modules can use bare specifiers -->
<script type="module">
  import _ from 'lodash';
  import React from 'react';
</script>

The scopes feature lets different parts of your application use different versions of the same package. The admin section in this example uses React 18.3 while the rest of the site uses 18.2. This is useful during gradual migrations or when different features have incompatible dependency requirements.

Version Pinning and Lock Files

Public CDNs like jsDelivr and unpkg support version ranges in URLs. A URL like /npm/lodash@^4/lodash.min.js resolves to the latest compatible version, which changes without your knowledge or consent. This is a supply chain risk: a compromised package version automatically propagates to your production site.

<!-- DANGEROUS: version range auto-updates -->
<script src="https://cdn.jsdelivr.net/npm/lodash@^4/lodash.min.js"></script>

<!-- SAFE: exact version pinned -->
<script src="https://cdn.jsdelivr.net/npm/[email protected]/lodash.min.js"
  integrity="sha384-OYlGGIKFt3Xn4sthrV3GpMoVWxBKRvz3PZ2hu5GHxR0oLHBBgaGNfHqBIplDigL"
  crossorigin="anonymous"></script>

<!-- SAFEST: exact version + exact file hash -->
<script src="https://cdn.jsdelivr.net/npm/[email protected]/lodash.min.js"
  integrity="sha384-OYlGGIKFt3Xn4sthrV3GpMoVWxBKRvz3PZ2hu5GHxR0oLHBBgaGNfHqBIplDigL
             sha512-WFN04846sdKMIP5LKNphMaWzU7YpMyCU245etK3g/2ARYbPK9Ub18eG+ljU96qKRCWh+quCY7yefSmlkQw1ANQ=="
  crossorigin="anonymous"></script>

Always pin exact versions in CDN URLs and combine them with SRI hashes. Treat CDN URL updates the same way you treat package.json updates: review the changelog, run tests, and commit the change with a clear description of what changed and why.

Self-Hosting as a Defense Strategy

Self-hosting third-party scripts eliminates the CDN as a trust dependency entirely. You download the exact version you need, verify its hash, bundle it with your application, and serve it from your own origin infrastructure. The trade-off is losing CDN cache sharing (a user who loaded lodash from cdnjs on another site will not have it cached for yours) and accepting the operational burden of monitoring and updating dependencies yourself.

# Self-hosting workflow
# 1. Download the exact version
curl -o vendor/lodash-4.17.21.min.js \
  https://cdn.jsdelivr.net/npm/[email protected]/lodash.min.js

# 2. Verify the hash
echo "expected_hash  vendor/lodash-4.17.21.min.js" | sha384sum -c

# 3. Commit to version control with the hash
git add vendor/lodash-4.17.21.min.js
git commit -m "vendor: add lodash 4.17.21 (sha384: expected_hash)"

# 4. Serve from your own CDN
# script src="/vendor/lodash-4.17.21.min.js"

For high-security applications — banking, healthcare, government — self-hosting is the recommended approach. The performance penalty is minimal when your own CDN delivers the files, and you eliminate an entire class of supply chain attacks.

Build-Time Verification Pipeline

Integrate supply chain checks into your CI/CD pipeline to catch compromised dependencies before they reach production. A verification pipeline should check three things: that pinned versions match lock file expectations, that SRI hashes in HTML match the actual files, and that no new domains have been added to script sources without review.

#!/bin/bash
# ci/verify-supply-chain.sh

# Check all script tags have SRI
echo "Checking SRI coverage..."
grep -rn '<script.*src=' dist/ | grep -v 'integrity=' | grep -v 'type="application/ld+json"'
if [ $? -eq 0 ]; then
  echo "ERROR: Scripts without SRI found"
  exit 1
fi

# Verify SRI hashes match downloaded files
echo "Verifying SRI hashes..."
for url in $(grep -ohP 'src="(https://[^"]+)"' dist/index.html | grep -oP 'https://[^"]+'); do
  expected=$(grep -oP "integrity=\"sha384-[^\"]+\"" dist/index.html | head -1 | grep -oP 'sha384-[^"]+')
  actual=$(curl -s "$url" | openssl dgst -sha384 -binary | openssl base64 -A)
  if [ "sha384-$actual" != "$expected" ]; then
    echo "HASH MISMATCH: $url"
    exit 1
  fi
done

# Check CSP allows only known CDN domains
echo "Checking CSP domain allowlist..."
allowed="cdnjs.cloudflare.com cdn.jsdelivr.net fonts.googleapis.com fonts.gstatic.com"
# ... validate against CSP header

Monitoring and Incident Response

Even with SRI and CSP in place, you need monitoring to detect supply chain attacks that bypass your defenses or target vectors you have not covered. CSP violation reports are the first line of detection. Configure the report-uri or report-to directive to send CSP violations to a monitoring endpoint, and alert on anomalies. For comprehensive monitoring strategies, see CDN Monitoring and Observability.

Monitor your CDN dependencies for known vulnerabilities using services like Snyk, Socket.dev, or GitHub's Dependabot. These tools analyze your dependency tree for packages with known security issues and alert you when a package you use is compromised. For npm-sourced CDN dependencies, subscribe to security advisories for every package in your dependency tree.

Build an incident response playbook for supply chain compromises. When a CDN-hosted library is compromised, your response should include: immediately replacing the CDN reference with a self-hosted copy, rotating any credentials or tokens that could have been exfiltrated, scanning your CSP violation logs for evidence of data exfiltration, and notifying users who may have been affected. The faster you can detect and respond, the smaller the blast radius. See CDN Incident Response Playbooks for detailed procedures.

External References

References & Standards: Cloudflare documentation · OWASP CDN Security