npm package · MIT · Node ≥ 18

csp-forge

Generate a tight Content-Security-Policy from your built site.

Point it at dist/, build/ or out/ (and optionally a live URL). It works out exactly which sources the site loads and writes a policy with sha256 hashes, or nonce + strict-dynamic for SSR, ready to paste into Nginx, Apache, Netlify, Vercel, Express/Helmet or a meta tag. --check fails CI when a deployed CSP is missing sources or too loose.

npm i -D csp-forge
~/project — zsh
$ npx csp-forge dist --check "default-src 'self' 'unsafe-inline'; img-src *"policy string checked against 4 scanned file(s)  ✖ error script-src blocks sources the site uses [csp-missing-source]          Effective default-src does not allow https://cdn.jsdelivr.net (used in index.html).          fix: Example: Content-Security-Policy: script-src 'self' https://cdn.jsdelivr.net  ✖ error default-src allows 'unsafe-inline' [csp-unsafe-inline]          fix: Replace 'unsafe-inline' with the hashes from: npx csp-forge <dir> --format header  ✖ error img-src uses overly broad source * [csp-wildcard]          fix: List the explicit origins instead: img-src 'self' https://images.unsplash.com data:  ⚠ warn  base-uri missing [csp-missing-base-uri]          fix: Add "base-uri 'self'". 1 policy(ies) checked: 3 error(s), 1 warning(s)
Sample output from the csp-forge README.

The problem

Writing a CSP by hand means guessing every script, style, font, image and API origin your site touches. Guess too tight and the site breaks; too loose and 'unsafe-inline' or * quietly defeats the point.

csp-forge reads your actual build output, hashes every inline block, and emits the narrowest policy that still works. Then --check keeps it honest in CI as the site changes.

Features

Built from your build

Scans HTML, CSS and JS in dist/ for scripts, styles, images, fonts, media, frames, workers, manifests and form actions.

Hashes or nonces

sha256 hashes for static sites, or nonce + strict-dynamic with SSR helpers and Express-style middleware.

Paste-ready formats

header, raw, meta, nginx, apache, netlify, vercel, express, helmet, json and reporting-endpoints.

--check for CI

Compare a policy string, file or live URL with what the build needs. Flags missing sources, unsafe keywords, wildcards and stale hashes.

Safe rollouts

--report-only with report-uri, or modern report-to + Reporting-Endpoints.

Vite & webpack plugins

Run the scan automatically after each production build.

--write

Inject the meta tag into every HTML file, or write dist/_headers for Netlify / Cloudflare Pages.

Zero runtime deps

Node ≥ 18, dual ESM/CJS, JSON and SARIF output.

Usage

# Generate a header

 npx csp-forge dist -f header

# Write Netlify _headers

 npx csp-forge dist -f netlify --write

# Nonce + strict-dynamic (SSR)

 npx csp-forge dist --strict-dynamic -f header

# Check a live site against the build

 npx csp-forge dist --check https://example.com

CI / GitHub Action

# .github/workflows/csp.yml
name: CSP
on: [push, pull_request]
permissions: { contents: read, security-events: write }
jobs:
  csp:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: 20 }
      - run: npm ci && npm run build
      - run: npx csp-forge@1 dist --check dist/_headers --sarif csp.sarif
      - uses: github/codeql-action/upload-sarif@v3
        if: always()
        with: { sarif_file: csp.sarif }

Options & flags

FlagDescription
-u, --url <url>Also scan a live page (+ same-origin CSS/JS); its origin counts as 'self'
--origin <url>Treat this absolute origin as 'self' when scanning a directory
-c, --config <file>JSON config (default: .csp-forgerc.json / .cspbuilderrc.json / package.json "csp-forge")
-f, --format <fmt>header | raw | meta | nginx | apache | netlify | vercel | express | helmet | json | reporting-endpoints
--nonce[=value]'nonce-{{NONCE}}' (or 'nonce-<value>') instead of inline hashes
--strict-dynamicNonce mode with 'strict-dynamic' (best for SSR)
--report-onlyEmit Content-Security-Policy-Report-Only
--report-uri <url>Add report-uri (legacy reporting)
--report-to <name>Add report-to (use with --reporting-endpoint-url)
--reporting-endpoint-url <url>URL for the Reporting-Endpoints header
--frame-ancestors <v>Default 'none'; "off" omits it
--allow-wildcardsAllow collapsing 3+ subdomains into https://*.domain
--no-upgradeOmit upgrade-insecure-requests
--writemeta: inject <meta> into every HTML file; netlify: write <dir>/_headers
-q, --quietNo summary on stderr
--check <x>Policy string, file (HTML meta, _headers, nginx conf, vercel.json) or URL
--fail-on <level>error | warn | none (default: error)
--jsonPrint JSON results
--sarif [file]Print SARIF 2.1.0 (or write to file)
-t, --timeout <ms>Network timeout (default: 15000)

Full reference, config file format and API in the README.

Support this project

Support open-source security tools

I build free, open-source tools that help developers ship safer websites. Every coffee helps keep them maintained, documented and free for everyone.

Buy me a coffee (opens Stripe in a new tab)

Secure checkout by Stripe

Portrait of Louis Wright

About the author

Louis Wright

Cyber security researcher and software engineer based in the UK.

More tools by Louis Wright

58 tools

Sentinels security scanners & CI gates · 20