Built from your build
Scans HTML, CSS and JS in dist/ for scripts, styles, images, fonts, media, frames, workers, manifests and form actions.
npm package · MIT · Node ≥ 18
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
$ 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)
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.
Scans HTML, CSS and JS in dist/ for scripts, styles, images, fonts, media, frames, workers, manifests and form actions.
sha256 hashes for static sites, or nonce + strict-dynamic with SSR helpers and Express-style middleware.
header, raw, meta, nginx, apache, netlify, vercel, express, helmet, json and reporting-endpoints.
Compare a policy string, file or live URL with what the build needs. Flags missing sources, unsafe keywords, wildcards and stale hashes.
--report-only with report-uri, or modern report-to + Reporting-Endpoints.
Run the scan automatically after each production build.
Inject the meta tag into every HTML file, or write dist/_headers for Netlify / Cloudflare Pages.
Node ≥ 18, dual ESM/CJS, JSON and SARIF output.
# 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# .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 }
| Flag | Description |
|---|---|
-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-dynamic | Nonce mode with 'strict-dynamic' (best for SSR) |
--report-only | Emit 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-wildcards | Allow collapsing 3+ subdomains into https://*.domain |
--no-upgrade | Omit upgrade-insecure-requests |
--write | meta: inject <meta> into every HTML file; netlify: write <dir>/_headers |
-q, --quiet | No 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) |
--json | Print 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
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