Security Headers with Claude Code: Helmet, CSP, and HSTS Configuration
Default HTTP headers are insecure out of the box. XSS payloads get through because there is no Content Security Policy. Clickjacking works because X-Frame-Options is not set. HTTP traffic is allowed because HSTS is missing. Claude Code generates a hardened Helmet configuration from your CLAUDE.md security policy.
What Goes in CLAUDE.md
Define your header security requirements once:
## HTTP Security Headers
### Required Headers
- **CSP**: inline scripts forbidden; whitelist approach (default-src: none)
- **HSTS**: max-age=31536000, includeSubDomains, preload
- **X-Frame-Options**: DENY
- **X-Content-Type-Options**: nosniff
- **Referrer-Policy**: strict-origin-when-cross-origin
### CSP Rules
- Whitelist approach: start from `default-src 'none'`, add only what's needed
- report-uri: /api/csp-report for violation logging
- Dev mode: report-only (violations logged, not blocked)
- Localhost: allow ws:// for HMR websocket in development
### Inline Scripts
- Use nonce pattern — no 'unsafe-inline'
Generated Helmet Configuration
Claude Code produces a production-ready Helmet setup with environment-aware CSP:
const helmet = require('helmet');
const crypto = require('crypto');
const isDev = process.env.NODE_ENV !== 'production';
// Nonce middleware — runs before helmet so nonce is available in CSP
app.use((req, res, next) => {
res.locals.nonce = crypto.randomBytes(16).toString('base64');
next();
});
app.use(
helmet({
contentSecurityPolicy: {
useDefaults: false,
directives: {
defaultSrc: ["'none'"],
scriptSrc: [
"'self'",
// Allow nonce-based inline scripts instead of 'unsafe-inline'
(req, res) => `'nonce-${res.locals.nonce}'`,
// Allow HMR websocket in dev
...(isDev ? ["'unsafe-eval'"] : []),
],
styleSrc: ["'self'", 'https://fonts.googleapis.com'],
imgSrc: ["'self'", 'data:', 'https:'],
connectSrc: [
"'self'",
// Allow webpack HMR websocket in development
...(isDev ? ['ws://localhost:*'] : []),
],
fontSrc: ["'self'", 'https://fonts.gstatic.com'],
objectSrc: ["'none'"],
frameSrc: ["'none'"],
formAction: ["'self'"],
// Force HTTPS for all subresource loads in production
...(isDev ? {} : { upgradeInsecureRequests: [] }),
reportUri: '/api/csp-report',
},
// Report-only in dev: violations are logged, not blocked
reportOnly: isDev,
},
strictTransportSecurity: {
maxAge: 31536000, // 1 year
includeSubDomains: true,
preload: true,
},
frameguard: { action: 'deny' },
noSniff: true,
referrerPolicy: { policy: 'strict-origin-when-cross-origin' },
hidePoweredBy: true,
})
);
CSP Violation Reporting Endpoint
Log every violation to catch misconfigured policies before tightening them:
app.post('/api/csp-report', express.json({ type: 'application/csp-report' }), (req, res) => {
const report = req.body['csp-report'];
if (report) {
console.warn('[CSP Violation]', {
violatedDirective: report['violated-directive'],
blockedUri: report['blocked-uri'],
documentUri: report['document-uri'],
sourceFile: report['source-file'],
lineNumber: report['line-number'],
});
// Forward to your monitoring system (Datadog, Sentry, etc.)
}
res.status(204).end();
});
Nonce Pattern for Inline Scripts
When you absolutely need an inline script, use the nonce generated per-request:
// In your template engine (e.g., EJS)
// <script nonce="<%= nonce %>">
// window.__CONFIG__ = { apiUrl: '...' };
// </script>
// In Express route
app.get('/', (req, res) => {
res.render('index', { nonce: res.locals.nonce });
});
The CSP scriptSrc lambda picks up res.locals.nonce automatically, so each page load gets a unique nonce — 'unsafe-inline' is never needed.
Summary
The workflow:
- CLAUDE.md — define CSP whitelist, HSTS config, and header policy once
- Helmet — one middleware call sets all headers correctly
-
CSP whitelist — start from
default-src 'none', add only what's required - Report-only in dev — catch policy violations before blocking legitimate traffic in production
-
Nonce for inline scripts — per-request randomness eliminates
'unsafe-inline'
Run curl -I https://yourapp.com and verify content-security-policy, strict-transport-security, and x-frame-options are present on every response.
Want a battle-tested set of Claude Code prompts for security header audits, OWASP Top 10 checks, and dependency scanning?
Check out the Security Pack (¥1,480) — systematic security review prompts covering CSP, injection, auth, and more:
Top comments (0)