# CSP does not use unsafe-inline for scripts

- **Pattern:** `ab-000006` (`security-headers.headers.csp-no-unsafe-inline`)
- **Severity:** high
- **Lifecycle:** active
- **Last modified:** 2026-04-17
- **Canonical URL:** https://auditbuffet.com/patterns/ab-000006
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-000006)

## Why it matters

`'unsafe-inline'` in `script-src` means any `<script>` tag injected by an attacker — through XSS, a compromised third-party dependency, or a DOM mutation — runs with full page context. It effectively negates CSP's primary purpose: a policy exists but an XSS payload executes anyway. OWASP A03 (Injection) flags this as a common misconfiguration where developers add a CSP for compliance but inadvertently include `unsafe-inline` to silence warnings, creating a security theater scenario. CWE-79 (XSS) and CWE-1336 (improper neutralization) both apply. The correct fix — cryptographically random nonces per request — is supported natively by all major frameworks.

## Severity rationale

High because `unsafe-inline` in `script-src` defeats XSS protection even when a CSP is present, reducing it to security theater.

## Remediation

Replace `'unsafe-inline'` with per-request cryptographic nonces in `middleware.ts`. The nonce must be random on every request — static nonces provide zero protection.

```ts
// middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

export function middleware(request: NextRequest) {
  const nonce = Buffer.from(crypto.randomUUID()).toString('base64')
  const csp = `default-src 'self'; script-src 'self' 'nonce-${nonce}'`
  const response = NextResponse.next()
  response.headers.set('Content-Security-Policy', csp)
  response.headers.set('x-nonce', nonce)
  return response
}
```

Pass the nonce to `<Script nonce={nonce}>` components via the request headers. Move any remaining inline scripts to external files or use hash-based allowlisting if the inline content is truly static.

## Detection

- **ID:** `csp-no-unsafe-inline`
- **Severity:** `high`
- **What to look for:** If a CSP is present, enumerate all directives in the policy and examine the `script-src` directive for `'unsafe-inline'`. Note: Next.js uses nonce-based inline scripts by default, which is acceptable without `unsafe-inline`.
- **Pass criteria:** CSP `script-src` contains 0 occurrences of `'unsafe-inline'`, OR uses nonce-based (`'nonce-*'`) or hash-based (`'sha256-*'`) inline script allowlisting that overrides the unsafe-inline directive.
- **Fail criteria:** CSP includes at least 1 `'unsafe-inline'` in `script-src` without nonce or hash supplementation.
- **Skip (N/A) when:** Skip when `csp-present` result is `fail` or `skip` — there is no CSP to evaluate for unsafe-inline directives.
- **Detail on fail:** `"CSP script-src includes 'unsafe-inline' which defeats much of CSP's XSS protection"` or `"CSP uses 'unsafe-inline' and 'unsafe-eval' in script-src"`
- **Remediation:** `unsafe-inline` in `script-src` allows any inline script to run, which largely defeats CSP's purpose. Instead, use nonces or hashes. In Next.js, generate a cryptographically random nonce per-request in middleware:

  ```ts
  // middleware.ts — generate a nonce per request
  import { NextResponse } from 'next/server'
  import type { NextRequest } from 'next/server'

  export function middleware(request: NextRequest) {
    const nonce = Buffer.from(crypto.randomUUID()).toString('base64')
    const csp = `default-src 'self'; script-src 'self' 'nonce-${nonce}'`
    const response = NextResponse.next()
    response.headers.set('Content-Security-Policy', csp)
    response.headers.set('x-nonce', nonce) // pass to server components
    return response
  }
  ```

  The nonce value must be cryptographically random and unique per request — never use a static nonce value, as that provides no security benefit. See the Next.js CSP documentation for the complete `generateCspNonce()` pattern and how to pass the nonce to `<Script>` components.

  If you have inline scripts, either move them to external files or use the nonce approach your framework provides.

## External references

- cwe CWE-79
- cwe CWE-1336
- owasp A03
- owasp A05

Taxons: injection-and-input-trust

HTML version: https://auditbuffet.com/patterns/ab-000006
