Web Security

How to handle CSP for third-party resources?

Learn strategies for implementing Content Security Policy while managing third-party scripts, styles, and resources safely without compromising functionality.

By Inventive HQ Team

To use third-party resources under Content Security Policy, add each provider's exact HTTPS origin to the directive that governs the resource type it loads — script-src for scripts, style-src for CSS, img-src for images and pixels, connect-src for API/XHR/WebSocket calls, font-src for web fonts, and frame-src for embedded iframes — and for scripts, prefer a per-request nonce with 'strict-dynamic' over a plain domain allowlist because a hijacked allowlisted CDN can still serve you malicious code. One vendor usually touches several directives at once, so the reliable method is to run the policy in Content-Security-Policy-Report-Only mode first, read the exact directive named in each browser violation, and add origins there before switching to enforcement.

That is the summary an AI overview will give you. What it can't give you is the part that actually saves the afternoon: which control to reach for per resource, why an allowlist is the weakest of the three trust models, how 'strict-dynamic' rescues you from third-party scripts that inject their own script tags, and the exact directive-by-directive header for the vendors you'll actually integrate. The rest of this article is that reference.

The decision: allowlist, nonce/hash, or strict-dynamic

Every third-party resource forces one choice — how do you tell the browser to trust it? There are three trust models, and they are not equal. An allowlist trusts a whole origin (anything that domain serves). A hash trusts one exact, unchanging snippet. A nonce + 'strict-dynamic' trusts one script you stamped this request, and lets that script vouch for what it loads next. Weakest to strongest, that ordering matters: an allowlisted CDN that gets compromised can still ship you attacker code, while a hash or nonce only trusts markup you personally approved.

CSP third-party resource workflow A four-step pipeline: identify the resource, choose a trust model (allowlist, hash, or nonce with strict-dynamic), test in Report-Only mode, then enforce. A marker travels left to right along the pipeline. Adding a third-party resource under CSP 1 Identify Find the origin + resource type 2 Choose Trust model allowlist / hash / nonce 3 Test Report-Only collect violations 4 Enforce Ship the policy CSP header live

Rule of thumb: allowlist a normal external <script src> you control; hash a fixed inline snippet that never changes; use a nonce with 'strict-dynamic' for a modern strict policy where third-party scripts load their own dependencies. Whichever you pick, verify it in Report-Only mode before enforcing.

The CSP directive cheat sheet for third-party resources

A single vendor rarely lives in one directive. Stripe's checkout, for example, needs script-src (its JS), connect-src (its API), and frame-src (its payment iframe). Map every third-party resource to the directive that actually governs it:

DirectiveControlsThird-party exampleSample value
script-srcJavaScript: <script>, event handlers, evalAnalytics, tag managers, payment SDKsscript-src 'self' https://js.stripe.com https://www.googletagmanager.com
style-srcCSS: stylesheet links, style elements, inline style attributesGoogle Fonts CSS, widget themesstyle-src 'self' https://fonts.googleapis.com
img-srcImages and tracking pixelsAd/analytics pixels, avatars, map tilesimg-src 'self' https: data:
connect-srcfetch, XHR, WebSocket, EventSource, sendBeaconAnalytics beacons, API calls, error trackersconnect-src 'self' https://api.stripe.com https://region1.google-analytics.com
font-srcWeb fonts (@font-face)Google Fonts files, Adobe Fontsfont-src 'self' https://fonts.gstatic.com
frame-srcEmbedded <iframe> / <frame>Payment iframes, YouTube, social embedsframe-src https://js.stripe.com https://www.youtube-nocookie.com

Two directives commonly forgotten: default-src is the fallback for any fetch directive you don't set explicitly, and frame-ancestors (who may embed you — see X-Frame-Options vs CSP frame-ancestors) is separate from frame-src (who you may embed). Also note the CSP Level 3 rename: child-src is superseded by frame-src for iframes and worker-src for workers.

Loading interactive tool...

Common Third-Party Resources

Analytics

Google Analytics, Mixpanel, Segment, etc. typically require:

<script src="https://www.google-analytics.com/ga.js"></script>

Advertising

Google AdSense, DoubleClick, Facebook Pixel, etc.:

<script src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>

Fonts

Google Fonts, Adobe Fonts, etc.:

<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto">

Social Widgets

Facebook, Twitter, LinkedIn buttons and feeds:

<script src="https://platform.twitter.com/widgets.js"></script>

Payment Processors

Stripe, PayPal, Square, etc.:

<script src="https://js.stripe.com/v3/"></script>

CDN Resources

jQuery, Bootstrap, D3.js from CDN:

<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>

CSP Approach for Third-Party Resources

Step 1: Identify All Third-Party Resources

Audit your website to identify all third-party sources:

// Use browser DevTools to see all resources
// Network tab shows all requests
// Note the domains and types (script, style, font, etc.)

Or automate with a script:

# Find all script sources in HTML
grep -o 'src="[^"]*"' index.html | grep -o '"[^"]*"' | sort | uniq

Step 2: Create Whitelist Rules

Add third-party domains to your CSP directives:

Content-Security-Policy:
  default-src 'self';
  script-src 'self' https://www.google-analytics.com https://cdn.example.com;
  style-src 'self' https://fonts.googleapis.com;
  font-src 'self' https://fonts.gstatic.com;
  img-src 'self' https: data:;
  connect-src 'self' https://www.google-analytics.com
Advertisement

Step 3: Handle Special Cases

Some third-party resources don't work well with strict CSP.

Inline Scripts in Third-Party Resources

If third-party code injects inline scripts, allow them with nonces or hashes:

<!-- Third-party widget that injects inline scripts -->
<div id="widget-container"></div>
<script src="https://widgets.example.com/embed.js"></script>

CSP approach:

script-src 'self' https://widgets.example.com 'unsafe-inline'

Or use nonces (better):

<!-- Server generates a fresh, unpredictable nonce for each request -->
<script nonce="abc123def456">
  // Third-party injected code
</script>

CSP:

script-src 'self' 'nonce-abc123def456'

The nonce must be unique per response and unguessable (generate it from a CSPRNG, not a counter or timestamp), and the same value goes in both the header and the tag. Never reuse a nonce across requests — a static nonce is no better than 'unsafe-inline'.

Third-Party Scripts That Load More Scripts ('strict-dynamic')

Modern tag managers and SDKs create their own <script> tags at runtime, so a plain nonce on the root tag isn't enough — the injected children have no nonce and get blocked. 'strict-dynamic' fixes this: it propagates the trust you granted the root script (via its nonce or hash) to any scripts it loads programmatically, and tells the browser to ignore host allowlists and 'unsafe-inline' for scripts entirely.

Content-Security-Policy:
  script-src 'nonce-abc123def456' 'strict-dynamic' 'unsafe-inline' https:;
  object-src 'none';
  base-uri 'none'

This is the recommended CSP Level 3 strict pattern. Old browsers that don't understand 'strict-dynamic' fall back to the https: allowlist and 'unsafe-inline'; modern browsers (Chrome 52+, Firefox 52+, Edge 79+, Safari 15.4+) ignore both and honor only the nonce, so you get a strict policy on new browsers and a working page on old ones — without maintaining a long list of vendor domains.

Dynamic Resource Loading

Some third-party scripts load additional resources dynamically:

// Third-party library might load images, stylesheets, scripts dynamically
const widget = new ThirdPartyWidget({
  apiKey: 'xxx'
});
widget.load(); // Loads additional resources from their CDN

CSP configuration must allow both the initial script and dynamically loaded resources:

script-src 'self' https://cdn.example.com https://api.example.com;
img-src 'self' https://cdn.example.com;
style-src 'self' https://cdn.example.com

Common Third-Party Configurations

Google Analytics

script-src 'self' https://www.google-analytics.com https://googleadservices.com;
connect-src 'self' https://www.google-analytics.com https://www.googletagmanager.com

Google Fonts

style-src 'self' https://fonts.googleapis.com;
font-src 'self' https://fonts.gstatic.com

Stripe (Payment)

script-src 'self' https://js.stripe.com;
connect-src 'self' https://api.stripe.com

Facebook Pixel

script-src 'self' https://connect.facebook.net;
img-src 'self' https://pixel.facebook.com https://www.facebook.com;
connect-src 'self' https://www.facebook.com

Twitter/X Widgets

script-src 'self' https://platform.twitter.com;
frame-src https://twitter.com https://platform.twitter.com;
style-src 'self' 'unsafe-inline'

Risk Management Strategies

1. Use Subresource Integrity (SRI)

Verify that third-party resources haven't been tampered with:

<!-- Specify hash of expected content -->
<script src="https://code.jquery.com/jquery-3.6.0.min.js"
        integrity="sha384-KyZXEAg3QhqLMpG8r+Knujsl5+..."
        crossorigin="anonymous"></script>

The browser verifies the file against the integrity hash before executing. If the content doesn't match — for example, a compromised CDN swapped the file — it's blocked, even though the origin is allowlisted in CSP. That is why SRI complements CSP rather than replacing it: CSP says which origins may load, SRI says this exact file must not have changed.

Don't use require-sri-for. There was once an experimental CSP directive, require-sri-for 'script' 'style', intended to force SRI on all resources. It is obsolete and has been removed from browsers, so don't rely on it. Enforce SRI at the tag level with integrity attributes instead (a build step or CI check that fails when a third-party tag is missing integrity is the practical enforcement mechanism today).

2. Use Content Delivery Networks (CDN) with Integrity

If hosting third-party resources yourself:

script-src 'self' https://mycdn.example.com

Self-hosted is more secure than relying on third-party CDN.

3. Minimize Third-Party Dependencies

Audit whether each third-party resource is necessary:

  • Do you really need this analytics library?
  • Can a lighter alternative be used?
  • Can functionality be built in-house?

Fewer third-party resources = simpler CSP = stronger security.

4. Trusted Vendors Only

Only whitelist vendors you trust and have vetted:

  • Established companies with security track records
  • Regular security audits
  • Proper data handling practices
  • Transparent terms of service

5. Regular Security Audits

Periodically audit third-party usage:

// Script to document all third-party resources
const scripts = Array.from(document.scripts).map(s => s.src).filter(Boolean);
const styles = Array.from(document.styleSheets).map(s => s.href).filter(Boolean);

console.log('Scripts:', scripts);
console.log('Styles:', styles);

Handling Legacy Third-Party Code

Some older third-party resources don't work well with strict CSP:

Option 1: Use 'unsafe-inline' Judiciously

script-src 'self' 'unsafe-inline' https://legacy-vendor.com;
style-src 'self' 'unsafe-inline' https://legacy-vendor.com

Not ideal but sometimes necessary for legacy code.

Option 2: Create a Sandboxed Iframe

Isolate problematic third-party code in an iframe with its own CSP:

<!-- Main page with strict CSP -->
<!-- Other content -->

<!-- Iframe with lenient CSP for problematic third-party code -->
<iframe id="legacy-widget" src="/legacy-widget.html"></iframe>

In /legacy-widget.html:

<!DOCTYPE html>
<html>
<head>
  <!-- Lenient CSP only for this iframe -->
  <meta http-equiv="Content-Security-Policy"
        content="default-src 'unsafe-inline' https://legacy-vendor.com">
</head>
<body>
  <!-- Legacy third-party code here -->
  <script src="https://legacy-vendor.com/problematic-script.js"></script>
</body>
</html>

This isolates the problematic code from your main application.

Option 3: Proxy Third-Party Requests

For some third-party resources, create a proxy endpoint on your server:

// Express.js proxy example
const axios = require('axios');

app.get('/proxy/analytics', async (req, res) => {
  // Proxy third-party analytics request through your server
  const response = await axios.get('https://analytics-provider.com/track', {
    params: req.query
  });
  res.json(response.data);
});

Then in your CSP:

connect-src 'self' /proxy/analytics

This prevents direct communication with the third-party domain.

Testing CSP with Third-Party Resources

Use Report-Only During Implementation

Deploy with the Content-Security-Policy-Report-Only header first. In report-only mode the browser blocks nothing — it only posts a JSON violation report to your endpoint — so you can discover every third-party resource you forgot before anything breaks for real users.

Reporting-Endpoints: csp-endpoint="https://example.com/csp-report"
Content-Security-Policy-Report-Only:
  default-src 'self';
  script-src 'self' [third-party domains];
  report-to csp-endpoint;
  report-uri /csp-report

Use both reporting directives: report-to is the CSP Level 3 standard (it references a named group from the Reporting-Endpoints header via the Reporting API), and report-uri is the deprecated original that older browsers still honor — keep it as a fallback. Monitor the violation stream until it goes quiet, then switch the same policy to the enforcing Content-Security-Policy header. See CSP report-only mode for the full workflow.

Test Cross-Browser

Third-party resources might behave differently in different browsers:

# Test in multiple browsers
# Chrome, Firefox, Safari, Edge

# Verify all third-party content loads
# Check browser console for CSP violations

Automated Testing

Add CSP testing to your CI/CD pipeline:

# Check for CSP violations during tests
# Lighthouse includes CSP auditing
lighthouse https://example.com --view

# Use pa11y-ci or similar tools

Best Practices for Third-Party CSP

  1. Whitelist specific domains: Never use * for third-party resources
  2. Use https only: Require secure transport with https:
  3. Apply SRI when possible: Verify third-party script integrity
  4. Monitor and audit regularly: Keep CSP updated as usage changes
  5. Start restrictive: Begin with strict CSP, then add exceptions
  6. Document decisions: Explain why each third-party domain is whitelisted
  7. Use report-only first: Test policies before enforcement
  8. Review access logs: Monitor third-party resource requests
  9. Have an update process: Plan how to handle third-party security updates
  10. Consider alternatives: Always evaluate if third-party dependency is necessary

Complete Example

A realistic CSP handling multiple common third-parties:

Content-Security-Policy:
  default-src 'self';
  script-src 'self'
    https://www.google-analytics.com
    https://www.googletagmanager.com
    https://connect.facebook.net
    https://js.stripe.com
    https://code.jquery.com;
  style-src 'self'
    https://fonts.googleapis.com
    https://cdn.example.com/styles;
  font-src 'self'
    https://fonts.gstatic.com;
  img-src 'self' https: data:;
  connect-src 'self'
    https://www.google-analytics.com
    https://api.stripe.com;
  frame-src
    https://js.stripe.com;
  report-uri /csp-report

Handling third-party resources with CSP requires balance between functionality and security. By carefully whitelisting trusted vendors, using subresource integrity, and regularly auditing dependencies, you can maintain a strong security posture while leveraging valuable third-party services.

Frequently Asked Questions

How do I allow a third-party script with Content Security Policy?

Add the script's exact origin to the script-src directive, for example script-src 'self' https://js.stripe.com. List the specific HTTPS origin rather than a wildcard, and repeat the resource type in the matching directive if the vendor also loads styles (style-src), images (img-src), fonts (font-src), iframes (frame-src), or makes network calls (connect-src). Many third parties need entries in two or three directives, not just script-src, so read the vendor's published CSP requirements instead of guessing.

Should I use a nonce, a hash, or an allowlist for third-party scripts?

Use an allowlist (the vendor's origin in script-src) for a normal external script tag you control. Use a hash for a fixed inline snippet whose contents never change, such as a static analytics bootstrap. Use a per-request nonce plus 'strict-dynamic' for a modern strict CSP, because it lets a trusted loader script pull in its own dependencies without you having to enumerate every downstream domain. Nonces and hashes are safer than a domain allowlist because a compromised or hijacked allowlisted CDN can still serve you malicious code, whereas a nonce or hash only trusts the exact markup you approved.

What is 'strict-dynamic' in CSP and when should I use it?

'strict-dynamic' tells the browser to propagate the trust you granted a script via a nonce or hash to any further scripts that script loads programmatically, while ignoring host allowlists and 'unsafe-inline'. It is the recommended pattern for CSP Level 3 strict policies because it survives third-party libraries that inject their own script tags without you maintaining a long domain allowlist. A typical policy is script-src 'nonce-RANDOM' 'strict-dynamic' 'unsafe-inline' https:, where older browsers fall back to the allowlist and modern browsers honor the nonce. It is supported in Chrome 52+, Firefox 52+, Edge 79+, and Safari 15.4+.

Why does my third-party widget still get blocked after I added its script domain?

A single script often pulls in additional resources under different directives, and each one is blocked separately. The script tag needs script-src, but its CSS needs style-src, its web fonts need font-src, its tracking pixels and images need img-src, its XHR/fetch/WebSocket calls need connect-src, and any embedded iframe needs frame-src. Open the browser console, read the exact directive named in each CSP violation message, and add the origin to that specific directive. Switching to Content-Security-Policy-Report-Only first lets you collect every violation before you enforce.

Is 'unsafe-inline' safe to use for third-party code?

No, 'unsafe-inline' disables the core XSS protection of CSP by allowing any inline script or style to execute, including anything an attacker injects. Avoid it for script-src whenever possible; prefer nonces or hashes. Note that if you also specify a nonce or hash in script-src, modern browsers ignore 'unsafe-inline', which is exactly how the strict CSP fallback pattern stays safe on new browsers while remaining permissive on old ones. Some third-party libraries still require 'unsafe-inline' for style-src, which is lower risk than for scripts but should still be scoped and revisited.

How do I test a CSP before enforcing it?

Deploy the policy with the Content-Security-Policy-Report-Only header instead of Content-Security-Policy. In report-only mode the browser does not block anything; it only sends a JSON violation report to the endpoint you name, so you can watch real traffic surface every third-party resource you forgot. Pair it with a Reporting-Endpoints header and the report-to directive (the modern replacement for the deprecated report-uri) to collect reports. Once the reports go quiet, switch the same policy to the enforcing header.

Can Subresource Integrity replace a CSP allowlist?

No, they solve different problems and work best together. A CSP allowlist decides which origins are allowed to load at all; Subresource Integrity (SRI) verifies that the exact file you load matches a cryptographic hash, so a hacked CDN serving altered code is rejected. Add an integrity attribute to third-party script and link tags, and keep the origin in your CSP. The old require-sri-for CSP directive that once forced SRI on all resources is obsolete and has been removed from browsers, so enforce SRI at the tag level instead.

What is the difference between report-uri and report-to in CSP?

report-uri is the original CSP directive that posts violation reports to a URL; it is deprecated but still widely honored, so many sites keep it for backward compatibility. report-to is the CSP Level 3 replacement that references a named endpoint group defined in a separate Reporting-Endpoints (or legacy Report-To) response header, using the standardized Reporting API. The practical approach today is to send both: report-to for modern browsers and report-uri as a fallback for older ones.

CSPthird-partysecuritycontent security policy