cspresso - Brew a Content Security Policy

3 min read Original article ↗

Brew a Content Security Policy

Turn real page loads into a CSP you can ship.

cspresso crawls up to N same‑origin pages with headless Chromium (Playwright), watches the assets that load, and emits a draft Content-Security-Policy header.

--json --evaluate --bypass-csp --include-sourcemaps

pipx install cspresso
cspresso https://example.com --max-pages 10

# visited: https://example.com/
Content-Security-Policy: default-src 'self'; script-src 'self' https://cdn.jsdelivr.net; ...;

Remember: it’s only a starting point: crawls may not hit every flow, and inline hashing/nonces require care.

Quickstart

                  
cspresso https://mig5.net \
  --ignore-non-html \
  --max-pages 10
                  
                

What you’ll get

A header line you can paste into your vhost, or parseable info with --json


Tip: if an existing CSP might block loads during analysis, add --bypass-csp.

# Evaluate a candidate CSP (Report-Only) and fail CI on violations
cspresso https://mig5.net \
  --bypass-csp \
  --evaluate "default-src 'self'; img-src 'none';" \
  --json

{
  [...]
  "violations": [
    {
      "console": true,
      "disposition": "report",
      "documentURI": "https://mig5.net/",
      "text": "Loading the image 'https://mig5.net/logo.svg' violates the following Content Security Policy directive: \"img-src 'none'\". The policy is report-only, so the violation has been logged but no further action has been taken.",
      "type": "info"
    },
    {
      "console": true,
      "disposition": "report",
      "documentURI": "https://mig5.net/static/mig5.asc",
      "text": "Applying inline style violates the following Content Security Policy directive 'default-src 'self''. Either the 'unsafe-inline' keyword, a hash ('sha256-4Su6mBWzEIFnH4pAGMOuaeBrstwJN4Z3pq/s1Kn4/KQ='), or a nonce ('nonce-...') is required to enable inline execution. Note that hashes do not apply to event handlers, style attributes and javascript: navigations unless the 'unsafe-hashes' keyword is present. Note also that 'style-src' was not explicitly set, so 'default-src' is used as a fallback. The policy is report-only, so the violation has been logged but no further action has been taken.",
      "type": "info"
    }
  ]
}

# exit code: 1 if violations detected

How it works

cspresso lets the browser do the hard part: execute the page, watch what it loads, and distill origins into directives.

Crawl

Visit up to --max-pages same-origin pages and let the app’s JS run.

Observe

Track scripts, styles, images, fonts, frames, and “connect-like” requests.

Draft a CSP

Emit a baseline policy plus observed origins per directive.

Evaluate

Inject a candidate as Report‑Only and capture violations with an exit code for CI.

Inline script/style is tricky: nonces must be generated per response, and hashes must match bytes exactly. cspresso reports what it sees, but you should review and tighten before enforcing.

Popular flags

A few options that tend to matter in real deployments.

--bypass-csp

Strip existing CSP response headers so they don’t block discovery or evaluation.

--evaluate

Inject a candidate policy as Report‑Only and exit 1 if any violations are detected.

--include-sourcemaps

Heuristically discover sourcemap origins and add them to connect-src.

--upgrade-insecure-requests

Emit upgrade-insecure-requests in the proposed policy.

--browsers-path

Control where Playwright installs Chromium (handy for AppImage/CI caches).

--json

Machine-readable output: CSP, visited URLs, notes, and evaluation violations.

Install

pipx, pip, Poetry, or a standalone AppImage from Releases.

# Recommended
pipx install cspresso

# Or plain pip (use a venv)
pip install cspresso

Playwright browsers

cspresso can auto-install Chromium for Playwright if it isn’t present. By default it installs into ./.pw-browsers for deterministic builds and easy CI caching.


Override with --browsers-path or PLAYWRIGHT_BROWSERS_PATH.

Linux deps

If Chromium won’t start due to missing libraries, try --with-deps (may require elevated privileges).

chmod +x cspresso.AppImage
./cspresso.AppImage https://example.com \
  --browsers-path "$HOME/.cache/cspresso/pw-browsers"

Tip

AppImages mount read-only - use --browsers-path to install browsers into a writable cache directory.


Verify releases with the mig5 GPG key (fingerprint 00AE817C24A10C2540461A9C1D7CDE0234DB458D).