From latestaiagents
HTTP security headers configuration guide. Use this skill when hardening web applications, configuring CSP, or setting up security headers. Activate when: security headers, CSP, Content-Security-Policy, HSTS, X-Frame-Options, CORS headers, clickjacking prevention, helmet.
npx claudepluginhub latestaiagents/agent-skills --plugin skills-authoringThis skill uses the workspace's default tool permissions.
**Configure HTTP security headers to protect against common web attacks.**
Configures HTTP security headers including CSP, HSTS, X-Frame-Options, XSS protection for Node.js/Express, Nginx, Flask, Apache. Hardens web apps against XSS, clickjacking, MIME sniffing.
Configures HTTP security headers like HSTS, CSP, X-Frame-Options, X-Content-Type-Options for Express, Nginx, Flask. Protects against XSS, clickjacking, MIME sniffing; useful for hardening web apps and passing audits.
Configures HTTP security headers like CSP, HSTS, X-Frame-Options using Helmet.js in Node.js/Express apps to block XSS, clickjacking, MIME sniffing, and info leaks. Use for new apps, hardening, or audits.
Share bugs, ideas, or general feedback.
Configure HTTP security headers to protect against common web attacks.
| Header | Purpose | Priority |
|---|---|---|
| Content-Security-Policy | XSS prevention | HIGH |
| Strict-Transport-Security | Force HTTPS | HIGH |
| X-Frame-Options | Clickjacking | HIGH |
| X-Content-Type-Options | MIME sniffing | MEDIUM |
| Referrer-Policy | Privacy | MEDIUM |
| Permissions-Policy | Feature control | MEDIUM |
const helmet = require('helmet');
app.use(helmet({
// Content Security Policy
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "https://cdn.example.com"],
styleSrc: ["'self'", "'unsafe-inline'"], // Consider removing unsafe-inline
imgSrc: ["'self'", "data:", "https:"],
connectSrc: ["'self'", "https://api.example.com"],
fontSrc: ["'self'", "https://fonts.gstatic.com"],
objectSrc: ["'none'"],
mediaSrc: ["'self'"],
frameSrc: ["'none'"],
frameAncestors: ["'none'"],
formAction: ["'self'"],
baseUri: ["'self'"],
upgradeInsecureRequests: []
}
},
// HTTP Strict Transport Security
hsts: {
maxAge: 31536000, // 1 year
includeSubDomains: true,
preload: true
},
// Prevent clickjacking
frameguard: { action: 'deny' },
// Prevent MIME type sniffing
noSniff: true,
// Referrer Policy
referrerPolicy: { policy: 'strict-origin-when-cross-origin' },
// Permissions Policy
permittedCrossDomainPolicies: { permittedPolicies: 'none' }
}));
// Additional headers
app.use((req, res, next) => {
// Permissions Policy (formerly Feature-Policy)
res.setHeader('Permissions-Policy',
'geolocation=(), microphone=(), camera=(), payment=()');
// Cross-Origin policies
res.setHeader('Cross-Origin-Embedder-Policy', 'require-corp');
res.setHeader('Cross-Origin-Opener-Policy', 'same-origin');
res.setHeader('Cross-Origin-Resource-Policy', 'same-origin');
next();
});
server {
listen 443 ssl http2;
server_name example.com;
# SSL configuration
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
# Security Headers
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self'; object-src 'none'; frame-ancestors 'none';" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
add_header X-Frame-Options "DENY" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
add_header Cross-Origin-Embedder-Policy "require-corp" always;
add_header Cross-Origin-Opener-Policy "same-origin" always;
add_header Cross-Origin-Resource-Policy "same-origin" always;
# Hide server version
server_tokens off;
}
<VirtualHost *:443>
ServerName example.com
# Security Headers
Header always set Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline';"
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
Header always set X-Frame-Options "DENY"
Header always set X-Content-Type-Options "nosniff"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
Header always set Permissions-Policy "geolocation=(), microphone=(), camera=()"
# Hide server version
ServerTokens Prod
ServerSignature Off
</VirtualHost>
// Start restrictive, then loosen as needed
const cspDirectives = {
// Default fallback for all resource types
defaultSrc: ["'self'"],
// JavaScript sources
scriptSrc: [
"'self'",
// "'unsafe-inline'", // Avoid if possible
// "'unsafe-eval'", // Never use
"'nonce-${nonce}'", // Better than unsafe-inline
"https://trusted-cdn.com"
],
// CSS sources
styleSrc: [
"'self'",
"'unsafe-inline'", // Often needed for frameworks
"https://fonts.googleapis.com"
],
// Image sources
imgSrc: [
"'self'",
"data:", // For inline images
"https:" // Any HTTPS image
],
// Font sources
fontSrc: [
"'self'",
"https://fonts.gstatic.com"
],
// AJAX/WebSocket/Fetch destinations
connectSrc: [
"'self'",
"https://api.example.com",
"wss://ws.example.com"
],
// <object>, <embed>, <applet>
objectSrc: ["'none'"],
// <frame>, <iframe>
frameSrc: ["'none'"],
// Who can frame this page
frameAncestors: ["'none'"],
// Form submission targets
formAction: ["'self'"],
// <base> tag restrictions
baseUri: ["'self'"],
// Upgrade HTTP to HTTPS
upgradeInsecureRequests: []
};
// Generate nonce per request
app.use((req, res, next) => {
res.locals.nonce = crypto.randomBytes(16).toString('base64');
next();
});
// Set CSP with nonce
app.use((req, res, next) => {
res.setHeader('Content-Security-Policy',
`default-src 'self'; script-src 'self' 'nonce-${res.locals.nonce}'`);
next();
});
<!-- In template -->
<script nonce="<%= nonce %>">
// Inline script that will execute
</script>
// Report-only mode (doesn't block, just reports)
const cspReportOnly = {
...cspDirectives,
reportUri: '/csp-report'
};
app.use(helmet.contentSecurityPolicy({
directives: cspReportOnly,
reportOnly: true // Test before enforcing
}));
// CSP report endpoint
app.post('/csp-report', express.json({ type: 'application/csp-report' }), (req, res) => {
console.log('CSP Violation:', req.body);
res.status(204).end();
});
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
max-age: Time in seconds browser should remember HTTPS-only
includeSubDomains: Apply to all subdomains
preload: Allow inclusion in browser preload list
X-Frame-Options: DENY # Never allow framing
X-Frame-Options: SAMEORIGIN # Only same origin can frame
Referrer-Policy: no-referrer # Never send referrer
Referrer-Policy: same-origin # Only same origin
Referrer-Policy: strict-origin-when-cross-origin # Recommended
Permissions-Policy: geolocation=(), microphone=(), camera=(), payment=(), usb=()
# Allow specific origins
Permissions-Policy: geolocation=(self "https://maps.example.com")
# Check headers with curl
curl -I https://example.com
# Online tools
# https://securityheaders.com
# https://observatory.mozilla.org
# Check CSP specifically
# https://csp-evaluator.withgoogle.com
// Issue: "Refused to execute inline script"
// Fix: Use nonce or move to external file
scriptSrc: ["'self'", `'nonce-${nonce}'`]
// Issue: "Refused to load stylesheet"
// Fix: Add the source
styleSrc: ["'self'", "https://cdn.example.com"]
// Issue: "Blocked frame with origin"
// Fix: Adjust frame-ancestors or X-Frame-Options
frameAncestors: ["'self'", "https://trusted-site.com"]
// Issue: Breaking third-party widgets
// Fix: Add specific sources (not *)
scriptSrc: ["'self'", "https://widget.example.com"]