audit-headers
Skill106 starsChanged 7 months agoMITView on GitHub
Audit security headers in a pracht app: `headers()` exports that weaken the defaults, static hosts that drop them, and HSTS and CSP choices. Use for "audit security headers", "check CSP", "harden headers", "set up HSTS".
In this repository33 other files it gives its agents.
- AGENTS.mdAGENTS.md
- add-authSkill
- add-capabilitiesSkill
- add-contentSkill
- add-dbSkill
- add-i18nSkill
- add-imagesSkill
- add-observabilitySkill
- add-openapiSkill
- audit-a11ySkill
- audit-agent-surfaceSkill
- audit-authSkill
- audit-bundlesSkill
- audit-csrfSkill
- audit-depsSkill
- audit-islandsSkill
- audit-loadersSkill
- audit-redirectsSkill
- audit-secretsSkill
- audit-seoSkill
- audit-shellsSkill
- configure-isgSkill
- migrate-nextjsSkill
- pracht-debugSkill
- pracht-deploySkill
- pracht-scaffoldSkill
- pracht-test-apiSkill
- pre-deploySkill
- scaffold-e2eSkill
- scaffold-testsSkill
- tune-render-modeSkill
- typed-routesSkill
- upgrade-prachtSkill
The file
We split this skill into the rules it gives an agent
9 rules. About 0 are common, 2 are distinctive, and 7 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈2
Distinctive · estimate
This author's own choices. Worth a look.
7
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 64
Only route modules and shells have a headers() export — API handlers do not (there is no headers in the API module shape.
- Very specific: line 83
Ask how dist/client is deployed.
- Very specific: line 97
Only recommend preload if the user confirms they want to commit to HTTPS permanently and submit to the preload list.
- Very specific: line 102
Start from the starter policy in <https://pracht.resynapse.dev/docs/recipes/csp>, then adjust it for speculation and add observed origins:
- Very specific: line 105
Check which routes use speculation: read the speculation field from pracht inspect routes --json (requires a current @pracht/cli.
- Very specific: line 153
If the user mentions SharedArrayBuffer, WASM threading, or high-precision timers, recommend:
- Very specific: line 183
Never recommend a CSP with 'unsafe-eval' unless the user has documented why they need it.
- Distinctive: line 190
Treat prerender header manifests as public artifacts.
- Distinctive: line 191
Do not auto-edit. Headers are policy.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Pracht Audit Headers
What the framework guarantees: four default security headers are applied
automatically on EVERY framework response path — SSR pages, API responses,
404s, and static/ISG output on the first-party adapters (adapter-node,
adapter-cloudflare) as well as the Vercel headers config generated at build
time:
permissions-policy(disables device sensors)referrer-policy: strict-origin-when-cross-originx-content-type-options: nosniffx-frame-options: SAMEORIGIN
The helper behind this (applyDefaultSecurityHeaders) only sets a header
when missing — so a route or shell headers() export always wins,
including when it weakens a default. The framework does NOT set
strict-transport-security, content-security-policy, or cross-origin-*
headers — those need a project decision.
One deliberate exception: protocol-switch responses (a 101 WebSocket
handshake, or any response carrying a webSocket handle) are returned exactly
as the handler produced them, with no headers applied. This is not a gap —
copying the response would destroy the socket, and a handshake has no body for
a sniffing or framing policy to protect. Do not report it as a finding.
The audit surface is therefore:
- (a)
dist/clientserved by a custom CDN/host outside the first-party adapters — nothing applies the defaults there. - (b)
headers()exports that override a default with a weaker value. - (c) HSTS and CSP, which genuinely need user action.
Prerequisites: pracht inspect requires a vite config that registers the
pracht plugin; pracht inspect build and
dist/server/headers-manifest.json require a prior pracht build. Every
serverful target publishes a client copy at
dist/client/_pracht/headers.json; only Cloudflare reads it there. Pure static
exports deliberately do not publish that copy.
Step 1: Inventory header sources
pracht inspect routes --json
pracht inspect api --jsonMCP: if pracht dev-mcp is registered, prefer its inspect_*, doctor, and verify tools to the CLI.
Only route modules and shells have a headers() export — API handlers do not
(there is no headers in the API module shape; API handlers set headers
inline on the Response they build, and the runtime applies the defaults on
top). Inventory:
headers()exports in route files, loader files (loaderFile), and shells (shellFile).- Hand-rolled
Responseconstructions in API handlers and middleware where security-relevant headers are set inline.
Step 2: Find weakened defaults and uncovered static hosting
Defaults are framework-applied, so do not build a "which route is covered" matrix — instead:
- For each
headers()source from Step 1, flag any of the four default header names set to a weaker value (e.g.x-frame-options: ALLOWALL, an over-permissivepermissions-policy,referrer-policy: unsafe-url). Because the defaults are set-when-missing, the route value wins. - Ask how
dist/clientis deployed. Onadapter-node,adapter-cloudflare, or the generated Vercel config, static/ISG responses get the defaults. If a custom CDN or host servesdist/clientdirectly, flag it (warn) and recommend replicating the four defaults in that host's header config.
Step 3: HSTS
Grep for strict-transport-security. If absent everywhere, recommend adding
it (e.g. via a shell headers() export or the host's config):
"strict-transport-security": "max-age=63072000; includeSubDomains; preload"Only recommend preload if the user confirms they want to commit to HTTPS
permanently and submit to the preload list.
Step 4: Content-Security-Policy
Start from the starter policy in https://pracht.resynapse.dev/docs/recipes/csp, then adjust it for speculation and add observed origins:
- Check which routes use
speculation: read thespeculationfield frompracht inspect routes --json(requires a current@pracht/cli; on older CLIs, grep the manifest forspeculation:instead). Those routes emit an inline<script type="speculationrules">, so add'inline-speculation-rules'toscript-srcunless you use a nonce. - Grep the app for fetch URLs, image URLs, CSS
@import,<script src>,<link href>, font URLs, iframe sources. - Group by directive:
default-src,script-src,style-src,img-src,font-src,connect-src,frame-src; always include'self'per directive, per the starter policy. - pracht injects hydration state in a non-executable
<script id="pracht-state" type="application/json">— it does not need'unsafe-inline'. Do not add'unsafe-inline'unless the app truly emits executable inline scripts and the tradeoff is documented. - Routes with
streaming: trueemit executable inline scripts for deferred data and boundary swaps. Under a nonce policy, return the request nonce asscriptNoncefrom the shellhead()and allow it inscript-src; it also covers the speculation rules script.
Output a draft CSP referencing https://pracht.resynapse.dev/docs/recipes/csp and explain what each origin is for. Do not hand the user a CSP that breaks their site — present as draft.
Step 5: Prerendered header manifest safety
For SSG/ISG output, the framework refuses to prerender any route whose
document headers include dangerous names (set-cookie, authorization,
proxy-authorization, www-authenticate, proxy-authenticate) or
secret-shaped custom x-* names (token/secret/key/credential patterns) — a
build containing them hard-fails. So do not hunt for those at runtime; audit
the headers() sources statically to catch them before a build failure,
and report each as error with the prerender failure it would cause.
The real target is the warn class the framework cannot catch: innocuously
named headers carrying user-specific values in
dist/server/headers-manifest.json, which serverful adapters may replay across
users on static responses. Every serverful build also copies that
manifest into public client output; Cloudflare reads it through the assets
binding, while the other adapters retain the public copy for compatibility. A
pure static export omits the client copy and has no runtime to replay the server
manifest; instead, verify that the deployment's host-header configuration
mirrors only safe, non-user-specific entries. Missing host configuration means
route headers() values are not applied at all.
Secret VALUES in headers are owned by audit-secrets; this skill owns policy
headers. Cross-reference audit-secrets for value-level findings.
Step 6: Cross-origin isolation (optional)
If the user mentions SharedArrayBuffer, WASM threading, or high-precision
timers, recommend:
cross-origin-opener-policy: same-origincross-origin-embedder-policy: require-corpcross-origin-resource-policy: same-originon assets
Otherwise leave these unset — they break embedded third-party content.
Step 7: Report
Two outputs:
- Findings table (source × header × severity).
- Recommendations, in priority order:
error:headers()values that would fail prerender (Step 5).error:headers()exports that weaken a default.warn:dist/clientserved by a host that does not apply the defaults.warn: Missing HSTS in production-grade apps.warn: User-specific headers in prerendered static output.info: CSP draft, COOP/COEP suggestions.
Show the exact code change required (e.g. the headers() export to fix, or
the CDN header config to add).
Rules
- The four defaults are framework-applied — do not recommend re-adding them in app code; audit for weakening and for hosting paths outside the first-party adapters.
- Never recommend a CSP with
'unsafe-eval'unless the user has documented why they need it. applyDefaultSecurityHeadersis set-when-missing, so per-routeheaders()wins over the defaults — weakened values are the primary finding, not missing ones.- Static/ISG responses already get the defaults on first-party adapters and the generated Vercel config; only flag static hosting that bypasses those.
- Treat prerender header manifests as public artifacts.
- Do not auto-edit. Headers are policy; surface gaps, propose patches.
$ARGUMENTS
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

