DEV Community

myougaTheAxo
myougaTheAxo

Posted on

Security Headers with Claude Code: Helmet, CSP, and HSTS Configuration [20260311_142040]

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'
Enter fullscreen mode Exit fullscreen mode

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,
  })
);
Enter fullscreen mode Exit fullscreen mode

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();
});
Enter fullscreen mode Exit fullscreen mode

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 });
});
Enter fullscreen mode Exit fullscreen mode

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:

  1. CLAUDE.md — define CSP whitelist, HSTS config, and header policy once
  2. Helmet — one middleware call sets all headers correctly
  3. CSP whitelist — start from default-src 'none', add only what's required
  4. Report-only in dev — catch policy violations before blocking legitimate traffic in production
  5. 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:

👉 Security Pack → prompt-works.jp

Top comments (0)