hash-static
Generate a CSP policy by scanning static HTML files for inline content — no browser or crawl required.
Use this as a post-build step when you ship a static site: it hashes every inline <script>, <style>, style="..." attribute, and on*="..." event handler across the built HTML, dedupes across files, and either prints the policy or writes it straight into each <head> as a <meta> tag.
When to use this vs crawl
| Scenario | Use |
|---|---|
| You have a static build on disk and want deterministic hashes | hash-static |
| You need to capture inline content injected by JS at runtime | crawl (dynamic) |
| You want both: static build + framework-injected inline content | hash-static with --extra-* flags seeded from a one-off crawl |
hash-static is fast (no Playwright, no network) and ideal for CI. It only sees what is in the HTML on disk — content added by JavaScript at runtime (e.g. some framework hydration scripts) is invisible to it. For those cases, run crawl once against a preview server, capture the missing hashes, and feed them back via --extra-*.
Usage
csp-analyser hash-static <path>... [options]<path> may be a file or a directory; directories are walked recursively for *.html files. Multiple paths are allowed.
Options
| Option | Default | Description |
|---|---|---|
--inject | false | Rewrite each scanned HTML in place to include the generated CSP as a <meta http-equiv="Content-Security-Policy"> immediately after <head>. Any existing CSP <meta> is replaced. |
--format <fmt> | meta | Output format when not using --inject (see export formats). |
--report-only | false | Emit Content-Security-Policy-Report-Only instead of the enforcing header. |
--extra-script-elem <src> | — | Extra source expression for script-src-elem. Repeatable. Use for runtime-injected inline scripts. |
--extra-style-elem <src> | — | Extra source expression for style-src-elem. Repeatable. |
--extra-style-attr <src> | — | Extra source expression for style-src-attr. Repeatable. |
--extra-script-attr <src> | — | Extra source expression for script-src-attr. Repeatable. |
What it captures
For each HTML file under the given paths, hash-static extracts:
<script>...</script>blocks without asrcattribute →script-src-elem<style>...</style>blocks →style-src-elem- Every
style="..."attribute value (including empty strings) →style-src-attr - Every
on*="..."event handler attribute value (including empty strings) →script-src-attr
Empty-string values are included deliberately: browsers evaluate CSP against <div style=""> and require the empty-string SHA-256 (sha256-47DEQpj8...) to be listed for the attribute to apply.
When any hashes end up under style-src-attr or script-src-attr, 'unsafe-hashes' is added automatically — without it, the browser silently ignores attribute-context hashes (CSP3 §2.3.2).
Output
The generated directive map always includes a secure baseline:
default-src 'self'base-uri 'self'form-action 'self'font-src 'self'img-src 'self' data:object-src 'none'
Plus script-src-elem / style-src-elem / style-src-attr / script-src-attr populated from the scan.
Examples
Generate and print the meta tag
csp-analyser hash-static dist/Inject into every HTML file in the build output
csp-analyser hash-static dist/ --injectAs a post-build step in package.json
{
"scripts": {
"build": "vitepress build docs && csp-analyser hash-static docs/.vitepress/dist --inject"
}
}Include runtime-injected hashes captured by a prior crawl
csp-analyser hash-static dist/ --inject \
--extra-style-elem "'sha256-skqujXORqzxt1aE0NNXxujEanPTX6raoqSscTV/Ww/Y='" \
--extra-script-elem "'sha256-someRuntimeInjectedScriptHash='"Export as Cloudflare Pages _headers
csp-analyser hash-static dist/ --format cloudflare-pages > dist/_headersNotes
- HTML parsing is regex-based, tuned for compliant machine-generated output (VitePress, Next.js, Astro, etc.). Hand-written HTML with unusual quoting may not parse cleanly.
- Runs without a database — it does not create a session and cannot be compared, scored, or diffed via the session-based commands.
- If you want to both hash static content and capture runtime-injected hashes automatically, run
crawlagainst a local preview instead.