secure-headers-csp-builder
Secure Headers & CSP Builder
Add security headers safely without breaking functionality.
Essential Security Headers
// middleware/security-headers.ts
import { Request, Response, NextFunction } from "express";
export function securityHeaders(
req: Request,
res: Response,
next: NextFunction
) {
// Prevent clickjacking
res.setHeader("X-Frame-Options", "DENY");
// Prevent MIME sniffing
res.setHeader("X-Content-Type-Options", "nosniff");
// XSS Protection (legacy browsers)
res.setHeader("X-XSS-Protection", "1; mode=block");
// Referrer Policy
res.setHeader("Referrer-Policy", "strict-origin-when-cross-origin");
// Permissions Policy (replaces Feature-Policy)
res.setHeader(
"Permissions-Policy",
"camera=(), microphone=(), geolocation=(self), payment=()"
);
// HSTS - Force HTTPS (only in production)
if (process.env.NODE_ENV === "production") {
res.setHeader(
"Strict-Transport-Security",
"max-age=31536000; includeSubDomains; preload"
);
}
next();
}
Content Security Policy (CSP)
Phase 1: Report-Only Mode
// config/csp-report-only.ts
export const cspReportOnly = {
"default-src": ["'self'"],
"script-src": [
"'self'",
"'report-sample'",
"https://cdn.jsdelivr.net",
"https://www.googletagmanager.com",
],
"style-src": ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com"],
"img-src": ["'self'", "data:", "https:"],
"font-src": ["'self'", "https://fonts.gstatic.com"],
"connect-src": ["'self'", "https://api.example.com"],
"frame-ancestors": ["'none'"],
"base-uri": ["'self'"],
"form-action": ["'self'"],
"report-uri": ["/api/csp-report"],
};
function formatCSP(policy: Record<string, string[]>): string {
return Object.entries(policy)
.map(([key, values]) => `${key} ${values.join(" ")}`)
.join("; ");
}
// Apply report-only header
app.use((req, res, next) => {
res.setHeader(
"Content-Security-Policy-Report-Only",
formatCSP(cspReportOnly)
);
next();
});
CSP Violation Reporter
// routes/csp-report.ts
app.post(
"/api/csp-report",
express.json({ type: "application/csp-report" }),
(req, res) => {
const violation = req.body["csp-report"];
console.error("CSP Violation:", {
documentUri: violation["document-uri"],
violatedDirective: violation["violated-directive"],
blockedUri: violation["blocked-uri"],
sourceFile: violation["source-file"],
lineNumber: violation["line-number"],
});
// Store in monitoring system
trackCSPViolation({
directive: violation["violated-directive"],
blockedUri: violation["blocked-uri"],
userAgent: req.headers["user-agent"],
timestamp: new Date(),
});
res.status(204).send();
}
);
Phase 2: Enforce Mode
// config/csp-enforce.ts
export const cspEnforce = {
"default-src": ["'self'"],
"script-src": [
"'self'",
// Add nonces for inline scripts
"'nonce-{NONCE}'",
"https://cdn.jsdelivr.net",
"https://www.googletagmanager.com",
],
"style-src": [
"'self'",
// Replace unsafe-inline with nonces
"'nonce-{NONCE}'",
"https://fonts.googleapis.com",
],
"img-src": ["'self'", "data:", "https:"],
"font-src": ["'self'", "https://fonts.gstatic.com"],
"connect-src": ["'self'", "https://api.example.com"],
"frame-ancestors": ["'none'"],
"base-uri": ["'self'"],
"form-action": ["'self'"],
"upgrade-insecure-requests": [],
};
// Generate nonce for each request
app.use((req, res, next) => {
const nonce = crypto.randomBytes(16).toString("base64");
res.locals.cspNonce = nonce;
const policy = formatCSP(cspEnforce).replace(/{NONCE}/g, nonce);
res.setHeader("Content-Security-Policy", policy);
next();
});
Nonce Implementation
// views/index.ejs
<!DOCTYPE html>
<html>
<head>
<!-- Inline script with nonce -->
<script nonce="<%= cspNonce %>">
console.log('This script is allowed by CSP');
</script>
<!-- Inline style with nonce -->
<style nonce="<%= cspNonce %>">
body { background: white; }
</style>
</head>
<body>
<h1>Secure Page</h1>
</body>
</html>
Helmet.js Integration
// Using Helmet for comprehensive security headers
import helmet from "helmet";
app.use(
helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "'nonce-{NONCE}'"],
styleSrc: ["'self'", "'nonce-{NONCE}'"],
imgSrc: ["'self'", "data:", "https:"],
connectSrc: ["'self'", "https://api.example.com"],
fontSrc: ["'self'", "https://fonts.gstatic.com"],
objectSrc: ["'none'"],
mediaSrc: ["'self'"],
frameSrc: ["'none'"],
},
},
hsts: {
maxAge: 31536000,
includeSubDomains: true,
preload: true,
},
frameguard: {
action: "deny",
},
xssFilter: true,
noSniff: true,
referrerPolicy: {
policy: "strict-origin-when-cross-origin",
},
})
);
Rollout Plan
# CSP Rollout Plan
## Week 1: Report-Only Mode
- [ ] Deploy CSP in report-only mode
- [ ] Monitor violation reports
- [ ] Identify problematic resources
- [ ] Whitelist legitimate sources
## Week 2: Analysis
- [ ] Analyze 1 week of violations
- [ ] Update CSP policy based on reports
- [ ] Fix inline scripts/styles
- [ ] Test on staging
## Week 3: Staged Rollout
- [ ] Enable enforcement for 10% of traffic
- [ ] Monitor error rates
- [ ] Check user reports
- [ ] Adjust policy if needed
## Week 4: Full Enforcement
- [ ] Enable for 50% of traffic
- [ ] Verify no issues
- [ ] Enable for 100% of traffic
- [ ] Keep report-only header for monitoring
Testing CSP
// tests/csp.test.ts
import { describe, it, expect } from "vitest";
import request from "supertest";
import { app } from "../src/app";
describe("Content Security Policy", () => {
it("should set CSP header", async () => {
const response = await request(app).get("/");
expect(response.headers["content-security-policy"]).toBeDefined();
expect(response.headers["content-security-policy"]).toContain(
"default-src 'self'"
);
});
it("should block inline scripts without nonce", async () => {
const html = `
<!DOCTYPE html>
<html>
<head>
<script>alert('blocked')</script>
</head>
</html>
`;
// This would be blocked by CSP
// Verify in browser console or automated tests
});
it("should allow scripts with valid nonce", async () => {
const response = await request(app).get("/");
// Extract nonce from response
const nonceMatch = response.text.match(/nonce="([^"]+)"/);
expect(nonceMatch).toBeDefined();
});
});
Common CSP Issues & Fixes
// Issue 1: Inline event handlers
// ❌ Bad
<button onclick="handleClick()">Click</button>
// ✅ Good
<button id="myButton">Click</button>
<script nonce="<%= cspNonce %>">
document.getElementById('myButton').addEventListener('click', handleClick);
</script>
// Issue 2: Inline styles
// ❌ Bad
<div style="color: red;">Text</div>
// ✅ Good
<style nonce="<%= cspNonce %>">
.red-text { color: red; }
</style>
<div class="red-text">Text</div>
// Issue 3: eval() usage
// ❌ Bad
eval('console.log("test")');
// ✅ Good
// Don't use eval - refactor code
// Issue 4: Third-party scripts
// ❌ Bad - no CSP entry
<script src="https://cdn.example.com/script.js"></script>
// ✅ Good - whitelisted in CSP
script-src: ['self', 'https://cdn.example.com']
Monitoring & Alerts
// monitoring/csp-violations.ts
import { CloudWatch } from "@aws-sdk/client-cloudwatch";
const cloudwatch = new CloudWatch();
export async function trackCSPViolation(violation: {
directive: string;
blockedUri: string;
userAgent: string;
timestamp: Date;
}) {
await cloudwatch.putMetricData({
Namespace: "Security/CSP",
MetricData: [
{
MetricName: "Violations",
Value: 1,
Unit: "Count",
Timestamp: violation.timestamp,
Dimensions: [
{
Name: "Directive",
Value: violation.directive,
},
{
Name: "BlockedUri",
Value: violation.blockedUri,
},
],
},
],
});
// Alert if violations spike
if (await isViolationSpike()) {
await sendAlert({
title: "CSP Violation Spike Detected",
message: `High number of violations for ${violation.directive}`,
});
}
}
Best Practices
- Start report-only: Don't break production
- Gradual rollout: 10% → 50% → 100%
- Use nonces: Better than unsafe-inline
- Monitor violations: Track and analyze
- Test thoroughly: All pages and features
- Document exceptions: Why resources whitelisted
- Regular audits: Quarterly CSP review
Output Checklist
- Security headers implemented
- CSP policy defined (report-only)
- CSP violation reporter endpoint
- Nonce generation for inline scripts
- Helmet.js configured
- Rollout plan documented
- Testing strategy implemented
- Monitoring and alerts configured
- Team trained on CSP
- Staged rollout completed
More from monkey1sai/openai-cli
multi-tenant-safety-checker
Ensures tenant isolation at query and policy level using Row Level Security, automated testing, and security audits. Prevents data leakage between tenants. Use for "multi-tenancy", "tenant isolation", "RLS", or "data security".
10modal-drawer-system
Implements accessible modals and drawers with focus trap, ESC to close, scroll lock, portal rendering, and ARIA attributes. Includes sample implementations for common use cases like edit forms, confirmations, and detail views. Use when building "modals", "dialogs", "drawers", "sidebars", or "overlays".
10eslint-prettier-config
Configures ESLint and Prettier for consistent code quality with TypeScript, React, and modern best practices. Use when users request "ESLint setup", "Prettier config", "linting configuration", "code formatting", or "lint rules".
9api-security-hardener
Hardens API security with rate limiting, input validation, authentication, and protection against common attacks. Use when users request "API security", "secure API", "rate limiting", "input validation", or "API protection".
9security-incident-playbook-generator
Creates response procedures for security incidents with containment steps, communication templates, and evidence collection. Use for "incident response", "security playbook", "breach response", or "IR plan".
9bruno-collection-generator
Generates Bruno collection files (.bru) from Express, Next.js, Fastify, or other API routes. Creates organized collections with environments, authentication, and folder structure for the open-source Bruno API client. Use when users request "generate bruno collection", "bruno api testing", "create bru files", or "bruno import".
9