Best Practices

AI Rules for Content Security Policy

AI ships applications with no CSP headers. Rules for script-src, style-src, img-src, connect-src, frame-ancestors, report-uri, and incremental CSP deployment with report-only mode.

8 min read·February 1, 2025

No CSP header — any injected script runs freely with full access to DOM, cookies, and session

Nonce-based script-src, strict-dynamic, connect-src allowlists, frame-ancestors, report-only rollout

AI Ships Applications with No Content Security Policy

AI generates applications with: no Content-Security-Policy header (any injected script runs freely), inline scripts everywhere (CSP cannot protect you if your code relies on inline scripts), external resources from any origin (CDNs, analytics, fonts — all ungoverned), no frame protection (your app can be embedded in any iframe on any site), and no CSP reporting (violations happen silently). Without CSP, a successful XSS attack runs arbitrary JavaScript with full access to the DOM, cookies, and user session.

Modern CSP is: nonce-based (inline scripts require a per-request nonce), strict-dynamic (trusted scripts can load other scripts), source-restricted (only approved origins can serve scripts, styles, images, and connections), frame-controlled (frame-ancestors prevents clickjacking), and reported (violations sent to a reporting endpoint for monitoring). AI generates none of these.

These rules cover: script-src with nonces and strict-dynamic, style-src and img-src policies, connect-src for API allowlists, frame-ancestors for clickjacking prevention, and CSP reporting with report-only mode for safe deployment.

Rule 1: script-src with Nonces and strict-dynamic

The rule: 'Use nonce-based script-src: Content-Security-Policy: script-src 'nonce-{random}' 'strict-dynamic'. Generate a random nonce per request (crypto.randomBytes(16).toString('base64')). Add the nonce to every script tag: <script nonce="{random}">. Only scripts with the matching nonce run — injected scripts without the nonce are blocked. strict-dynamic allows nonced scripts to load additional scripts (needed for bundlers that dynamically create script elements).'

For why not unsafe-inline: 'script-src: unsafe-inline disables XSS protection entirely — any inline script runs, including injected ones. This defeats the purpose of CSP. Nonces are the replacement: your scripts have the nonce (they run), injected scripts do not (they are blocked). If your app uses inline event handlers (onclick="..."), refactor to addEventListener in a nonced script. Inline handlers are incompatible with CSP.'

AI generates: no CSP at all, or Content-Security-Policy: script-src 'self' 'unsafe-inline' — which allows all inline scripts. This CSP provides zero XSS protection. A nonce-based policy with strict-dynamic is the modern standard: strong protection, compatible with bundlers, and does not require listing every CDN origin.

  • script-src 'nonce-{random}' 'strict-dynamic' — nonce per request, never unsafe-inline
  • Generate nonce: crypto.randomBytes(16).toString('base64') — unique per response
  • Never 'unsafe-inline' in script-src — it disables XSS protection entirely
  • strict-dynamic: nonced scripts can load other scripts (bundles, dynamic imports)
  • Refactor onclick handlers to addEventListener in nonced scripts
💡 Nonces Replace unsafe-inline

unsafe-inline in script-src means any inline script runs — including injected ones. Nonces: your scripts have a per-request token (they run), injected scripts do not (they are blocked). Same developer experience, dramatically better security.

Rule 2: style-src, img-src, and font-src Policies

The rule: 'Restrict style-src to 'self' and trusted CDNs: style-src 'self' https://fonts.googleapis.com. For CSS-in-JS (styled-components, Emotion), use nonces on style tags or 'unsafe-inline' for style-src only (style injection is lower risk than script injection). Restrict img-src to your domain and image CDNs: img-src 'self' https://images.rulesync.com data: (data: for inline SVGs and base64 images). Restrict font-src: font-src 'self' https://fonts.gstatic.com.'

For each directive: 'default-src 'none' starts with everything blocked. Then explicitly allow each resource type: script-src (JavaScript), style-src (CSS), img-src (images), font-src (fonts), connect-src (fetch/XHR/WebSocket), media-src (audio/video), frame-src (iframes). This allowlist approach means: new resource types are blocked by default. You explicitly permit what your app needs — nothing more.'

AI includes no resource restrictions. Third-party scripts load from any CDN, images from any origin, fonts from anywhere. One compromised CDN serves malicious scripts to your users. CSP source restrictions limit the blast radius: even if an attacker injects a script tag, it can only load from origins you explicitly allowed.

Rule 3: connect-src for API and WebSocket Allowlists

The rule: 'Restrict connect-src to your API origins: connect-src 'self' https://api.rulesync.com wss://realtime.rulesync.com. This controls which origins JavaScript can reach via fetch, XMLHttpRequest, WebSocket, and EventSource. Without connect-src restrictions, an XSS attack can exfiltrate data to any server. With connect-src, even if an attacker runs JavaScript on your page, they cannot send data to their own server.'

For third-party services: 'If your app sends data to analytics (Mixpanel, Amplitude), error tracking (Sentry), or other services, add their origins to connect-src: connect-src 'self' https://api.rulesync.com https://api.mixpanel.com https://sentry.io. Review this list quarterly — remove services you no longer use. Each origin in connect-src is a data exfiltration channel if compromised.'

AI generates: no connect-src directive. JavaScript can send data anywhere. An XSS payload sends stolen cookies to the attacker server. With connect-src restricted to your API, this request is blocked by the browser before it leaves the page. One directive prevents data exfiltration from XSS attacks.

⚠️ Data Exfiltration Blocked

Without connect-src, an XSS payload sends cookies to the attacker server. With connect-src restricted to your API origins, the browser blocks the request before it leaves the page. One directive prevents the most damaging XSS outcome.

Rule 4: frame-ancestors for Clickjacking Prevention

The rule: 'Set frame-ancestors to prevent clickjacking: Content-Security-Policy: frame-ancestors 'self'. This means: your app can only be framed by pages on your own origin. No other site can embed your app in an iframe and trick users into clicking. For apps that should never be framed: frame-ancestors 'none'. For apps that need specific partners to frame them: frame-ancestors 'self' https://partner.com.'

For the clickjacking attack: 'Attacker creates a page with your app in a transparent iframe, positioned over a fake UI. User thinks they are clicking the attacker page — they are actually clicking buttons in your app (hidden iframe). This can trigger: account deletion, permission changes, fund transfers — any action the authenticated user can perform. frame-ancestors makes the iframe refuse to load on unauthorized origins.'

AI generates: no frame-ancestors, no X-Frame-Options. Your app is frameable by any website. The clickjacking attack requires zero technical skill — just an iframe and some CSS positioning. frame-ancestors: 'self' is one CSP directive that eliminates the entire attack class.

ℹ️ Clickjacking: Zero Skill Required

Transparent iframe over a fake UI — user clicks your app buttons thinking they click the attacker page. Account deletion, permission changes, fund transfers. frame-ancestors: 'self' — one directive, entire attack class eliminated.

Rule 5: CSP Reporting and Report-Only Mode

The rule: 'Deploy CSP in report-only mode first: Content-Security-Policy-Report-Only: [your policy]; report-uri /api/csp-reports. Report-only mode logs violations without blocking them — you see what would break before enforcing. Monitor reports for 1-2 weeks, fix legitimate violations (third-party scripts you forgot to allowlist), then switch to enforcement: Content-Security-Policy: [your policy]; report-uri /api/csp-reports.'

For the reporting endpoint: 'CSP reports are JSON containing: document-uri (which page triggered the violation), violated-directive (which CSP rule was broken), and blocked-uri (which resource was blocked). Log these to your monitoring system. Group by violated-directive and blocked-uri. High-volume violations from browser extensions (Chrome extensions inject scripts) are noise — filter by document-uri to focus on your pages.'

AI deploys CSP with no reporting. When the policy breaks a feature (third-party widget, analytics script, payment iframe), the first report comes from users — not monitoring. Report-only mode lets you catch every violation before users do. Enforcement with report-uri lets you monitor ongoing compliance after deployment.

Complete Content Security Policy Rules Template

Consolidated rules for Content Security Policy.

  • script-src 'nonce-{random}' 'strict-dynamic' — nonce per request, never unsafe-inline
  • default-src 'none' — block everything, then explicitly allow each resource type
  • style-src, img-src, font-src: 'self' + trusted CDN origins only
  • connect-src: 'self' + your API + approved third-party services
  • frame-ancestors 'self' — prevent clickjacking, block unauthorized embedding
  • report-only mode first (1-2 weeks) — then enforce with report-uri for monitoring
  • Review CSP origins quarterly — remove unused services, add new ones explicitly
  • Filter browser extension noise from CSP reports — focus on your document URIs
AI Rules for Content Security Policy — RuleSync Blog