Skip to content

web-development

2 posts with the tag “web-development”

Content Security Policy (CSP) Guide for Web Developers

Content Security Policy is one of the most effective defenses against XSS attacks, but it’s also one of the most confusing security headers to configure. Get it wrong and your site breaks. Get it right and an entire class of attacks becomes impossible.

Here’s a practical guide to CSP — what it does, how to build one, and how to use Gasoline to generate a policy from your actual traffic.

CSP tells the browser which sources are allowed to load resources on your page. If a script tries to load from an origin not in your policy, the browser blocks it.

Without CSP, an XSS vulnerability means an attacker can:

  • Load scripts from any domain (<script src="https://evil.com/steal.js">)
  • Execute inline JavaScript (<script>document.cookie</script>)
  • Inject styles that hide or modify content

With CSP, even if an attacker injects HTML, the browser refuses to execute scripts or load resources from unauthorized origins.

CSP is delivered as an HTTP response header:

Content-Security-Policy: default-src 'self'; script-src 'self' https://cdn.example.com; style-src 'self' 'unsafe-inline'

Or as a <meta> tag (with some limitations):

<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' https://cdn.example.com">

Each directive controls a resource type:

DirectiveControlsExamples
default-srcFallback for all resource types'self'
script-srcJavaScript'self' https://cdn.jsdelivr.net
style-srcCSS'self' 'unsafe-inline'
img-srcImages'self' https: data:
font-srcWeb fonts'self' https://fonts.gstatic.com
connect-srcXHR, fetch, WebSocket'self' https://api.example.com wss://ws.example.com
frame-srcIframes'self' https://www.youtube.com
media-srcAudio and video'self'
worker-srcWeb Workers, Service Workers'self'
object-srcPlugins (Flash, Java)'none'
base-uri<base> element'self'
form-actionForm submission targets'self'

If a specific directive isn’t set, default-src is used as the fallback.

ValueMeaning
'self'Same origin as the page
'none'Block everything
'unsafe-inline'Allow inline scripts/styles (weakens XSS protection)
'unsafe-eval'Allow eval(), new Function() (weakens XSS protection)
https:Any HTTPS origin
data:Data URIs (data:image/png;base64,...)
https://cdn.example.comSpecific origin
*.example.comWildcard subdomain
'nonce-abc123'Scripts/styles with matching nonce attribute
'sha256-...'Scripts/styles with matching hash
Content-Security-Policy: default-src 'none'; script-src 'self'; style-src 'self'; img-src 'self'; font-src 'self'; connect-src 'self'

This allows only same-origin resources. Everything else is blocked.

Using a CDN for scripts? Add it:

script-src 'self' https://cdn.jsdelivr.net

Using Google Fonts? Add both origins:

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

API on a different domain? Add it to connect-src:

connect-src 'self' https://api.myapp.com

Not sure your policy is correct? Use Content-Security-Policy-Report-Only instead:

Content-Security-Policy-Report-Only: default-src 'self'; script-src 'self'; report-uri /csp-reports

The browser logs violations but doesn’t block anything. Review the reports, adjust the policy, then switch to enforcement.

The biggest challenge with CSP is knowing which origins your page actually loads resources from. A modern web application might use:

  • Your own CDN for static assets
  • Google Fonts for typography
  • A JavaScript CDN (jsdelivr, unpkg, cdnjs)
  • An analytics service (Google Analytics, Segment)
  • A payment processor (Stripe)
  • An error tracker (Sentry)
  • Social media embeds
  • Ad networks

You could audit every <script>, <link>, <img>, and fetch() call in your codebase. Or you could let Gasoline do it.

Gasoline observes all network traffic during your browsing session and generates a CSP from what it sees:

generate({format: "csp"})

Strict — only high-confidence origins (observed 3+ times from 2+ pages):

generate({format: "csp", mode: "strict"})

This gives you the tightest possible policy. If a script was only loaded once, it might be ad injection or a browser extension — strict mode excludes it.

Moderate — includes medium-confidence origins:

generate({format: "csp", mode: "moderate"})

Good for most production use cases.

Report-Only — generates a Content-Security-Policy-Report-Only header:

generate({format: "csp", mode: "report_only"})

Deploy this first to find violations before enforcing.

Gasoline automatically excludes:

  • Browser extension origins (chrome-extension://, moz-extension://) — these shouldn’t be in your CSP
  • Development server origins — localhost on a different port than your app
  • Low-confidence origins — observed only once on one page (likely noise)

Don’t want analytics in your CSP? Exclude it:

generate({format: "csp", mode: "strict",
exclude_origins: ["https://analytics.google.com", "https://www.googletagmanager.com"]})

The output includes:

  • Ready-to-use header string — copy-paste into your server config
  • Meta tag equivalent — for static sites
  • Per-origin details — which directive each origin maps to, confidence level, observation count
  • Filtered origins — what was excluded and why
  • Warnings — e.g., “only 3 pages observed — visit more pages for broader coverage”
script-src 'self' 'unsafe-inline'

This defeats the purpose of CSP for scripts. Inline scripts are the primary XSS vector. Use nonces or hashes instead:

<!-- Server generates a unique nonce per request -->
<script nonce="abc123">
// This script is allowed
</script>
script-src 'self' 'nonce-abc123'

Your page loads fine, but all API calls fail. connect-src controls fetch/XHR destinations — if your API is on a different origin, you need to allow it.

script-src 'self' https:

This allows scripts from any HTTPS origin. An attacker can host a script on any HTTPS server and your CSP won’t block it. Be specific about which origins you allow.

Deploying a new CSP without testing breaks things. Always start with Content-Security-Policy-Report-Only, check for violations, then switch to enforcement.

Even in 2026, you should explicitly block plugins:

object-src 'none'

This prevents Flash and Java plugin exploitation (still a vector in some corporate environments).

Next.js uses inline scripts for hydration. You’ll need nonce-based CSP:

middleware.ts
export function middleware(request) {
const nonce = crypto.randomUUID();
const csp = `script-src 'self' 'nonce-${nonce}'; style-src 'self' 'unsafe-inline';`;
// Pass nonce to components via headers
}

CRA inlines a runtime chunk. Either:

  • Disable inline runtime: INLINE_RUNTIME_CHUNK=false
  • Use hash-based CSP for the known inline script

Vite’s dev server uses inline scripts and HMR WebSocket. Dev CSP will differ from production.

  1. Browse your app through its main flows with Gasoline connected
  2. Generate a CSP: generate({format: "csp", mode: "report_only"})
  3. Deploy in report-only mode and monitor for violations
  4. Adjust — add any legitimate origins that were missed
  5. Switch to enforcement once violations are resolved
  6. Regenerate periodically as your dependencies change

Gasoline takes the guesswork out of step 1 — you don’t have to audit your codebase manually. It sees every origin your page communicates with and builds the policy from observation.

Subresource Integrity (SRI) Explained: Protect Your Site from CDN Compromise

Every <script src="https://cdn.example.com/library.js"> on your page is a trust decision. You’re trusting that the CDN will always serve the exact file you expect. If the CDN is compromised, hacked, or serves a corrupted file, your users execute the attacker’s code.

Subresource Integrity (SRI) eliminates this risk. Here’s how it works and how to implement it.

In 2018, a cryptocurrency mining script was injected into the British Airways website through a compromised third-party script. In 2019, Magecart attacks hit thousands of e-commerce sites through CDN compromises. In 2021, the ua-parser-js npm package was hijacked to serve malware.

The attack pattern is always the same:

  1. Attacker compromises a CDN, package registry, or hosting provider
  2. The script content changes (malware added, data exfiltration code injected)
  3. Every website loading that script from that CDN now serves the attacker’s code
  4. Users’ data is stolen, credentials harvested, or cryptocurrency mined

SRI prevents step 3. Even if the CDN is compromised, the browser refuses to execute the modified script.

SRI adds a cryptographic hash to your <script> and <link> tags:

<script src="https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js"
integrity="sha384-OYp56H6p7T3JKjPdRfL7gAHEdJ7yrfCCe3Ew5EHouvE7qdLCHs6PGoGMOQ/pA6j"
crossorigin="anonymous"></script>

When the browser downloads the file, it computes the SHA-384 hash of the content and compares it to the integrity attribute. If they don’t match — because the file was modified, corrupted, or replaced — the browser refuses to execute it.

Format: algorithm-base64hash

integrity="sha384-OYp56H6p7T3JKjPdRfL7gAHEdJ7yrfCCe3Ew5EHouvE7qdLCHs6PGoGMOQ/pA6j"

Supported algorithms:

  • SHA-256sha256-...
  • SHA-384sha384-... (recommended — good balance of security and performance)
  • SHA-512sha512-...

You can include multiple hashes for fallback:

integrity="sha384-abc... sha256-xyz..."

The browser accepts the resource if any hash matches.

SRI requires CORS. Add crossorigin="anonymous" to cross-origin scripts:

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

Without crossorigin, the browser can’t compute the hash for cross-origin resources and SRI silently fails.

ThreatSRI Protection
CDN compromise (attacker modifies hosted files)Blocks modified scripts
Man-in-the-middle attacks (on HTTP resources)Blocks tampered resources
CDN serving wrong versionBlocks unexpected content
Package registry hijacking (modified npm package)Blocks modified bundles
DNS hijacking (CDN domain points to attacker)Blocks attacker’s response
ThreatWhy SRI Doesn’t Help
First-party script compromiseSRI is for third-party resources
XSS via inline scriptsUse CSP for inline script protection
Version pinningSRI verifies content, not version semantics
AvailabilityIf the CDN is down, SRI can’t make it work
Terminal window
# Generate a hash for a local file
cat library.js | openssl dgst -sha384 -binary | openssl base64 -A
# Generate a hash for a remote file
curl -s https://cdn.example.com/library.js | openssl dgst -sha384 -binary | openssl base64 -A

Gasoline observes which third-party scripts and stylesheets your page loads and generates SRI hashes automatically:

generate({format: "sri"})

Output per resource:

  • URL — the resource location
  • Hashsha384-... in browser-standard format
  • Ready-to-use HTML tag<script> or <link> with integrity and crossorigin attributes
  • File size — for reference
  • Already protected — flags resources that already have SRI

Filter to specific resource types or origins:

generate({format: "sri", resource_types: ["script"]})
generate({format: "sri", origins: ["https://cdn.jsdelivr.net"]})

The output is copy-paste ready:

<script src="https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js"
integrity="sha384-OYp56H6p7T3JKjPdRfL7gAHEdJ7yrfCCe3Ew5EHouvE7qdLCHs6PGoGMOQ/pA6j"
crossorigin="anonymous"></script>

Replace your existing tags with integrity-protected versions:

<!-- Before -->
<script src="https://cdn.example.com/chart.js"></script>
<!-- After -->
<script src="https://cdn.example.com/chart.js"
integrity="sha384-abc123..."
crossorigin="anonymous"></script>

Use the webpack-subresource-integrity plugin:

const { SubresourceIntegrityPlugin } = require('webpack-subresource-integrity');
module.exports = {
output: { crossOriginLoading: 'anonymous' },
plugins: [new SubresourceIntegrityPlugin()]
};

Vite doesn’t generate SRI natively. Use a plugin or add integrity attributes to your HTML template for third-party CDN scripts.

For scripts loaded via <Script> component, add the integrity prop:

<Script
src="https://cdn.example.com/analytics.js"
integrity="sha384-abc123..."
crossOrigin="anonymous"
/>

If a CDN updates the file content (even whitespace changes), the hash won’t match and the browser blocks the script.

Fix: Pin to specific versions (lodash@4.17.21 not lodash@latest) and update hashes when you upgrade versions.

Some CDNs serve different content based on the User-Agent header (e.g., minified vs unminified). This means the hash differs across browsers.

Fix: Use CDNs that serve consistent content regardless of User-Agent. Gasoline warns you when it detects Vary: User-Agent on a resource.

If your framework dynamically creates <script> elements, you’ll need to add integrity attributes programmatically:

const script = document.createElement('script');
script.src = 'https://cdn.example.com/lib.js';
script.integrity = 'sha384-abc123...';
script.crossOrigin = 'anonymous';
document.head.appendChild(script);

Service workers can modify responses, which breaks SRI. If you’re using a service worker that caches CDN resources, ensure it passes through the original response without modification.

SRI and CSP work together:

  • CSP restricts which origins can load resources
  • SRI verifies the content of resources from allowed origins

CSP alone: an attacker compromises an allowed CDN → your CSP still allows the compromised script.

SRI alone: an attacker injects a <script> from a new origin → SRI doesn’t help because the new tag doesn’t have an integrity attribute.

Both together: CSP blocks scripts from unauthorized origins, and SRI blocks modified scripts from authorized origins. The attacker needs to compromise your specific CDN and produce content that matches the hash — which is cryptographically impossible.

  1. Browse your app with Gasoline connected
  2. Generate SRI hashes: generate({format: "sri"})
  3. Add integrity attributes to your third-party script and link tags
  4. Test — verify all resources load correctly
  5. Regenerate when you update third-party library versions

Yes, if you load scripts or stylesheets from third-party CDNs. The implementation cost is minimal (add two attributes to each tag) and the protection against supply chain attacks is significant.

Skip it for first-party resources served from your own domain. SRI adds value when you don’t control the server — if you control both the page and the resource server, CSP provides sufficient protection.

The combination of SRI for third-party resources and CSP for all resources gives you defense in depth against the most common web supply chain attacks.