Common Implementation Mistakes
Common Implementation Mistakes
Mistake 1: Implementing Headers Without Understanding Impact
// BAD: Blindly copying strict policies
app.use((req, res, next) => {
// This breaks many legitimate use cases
res.setHeader('Content-Security-Policy', "default-src 'none'");
res.setHeader('X-Frame-Options', 'DENY');
res.setHeader('Permissions-Policy', 'camera=(), microphone=(), geolocation=()');
next();
});
// GOOD: Gradual implementation with monitoring
const cspBuilder = {
development: "default-src 'self' 'unsafe-inline' 'unsafe-eval'; report-uri /csp-report",
staging: "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; report-uri /csp-report",
production: "default-src 'self'; script-src 'self' 'nonce-${nonce}'; style-src 'self' 'nonce-${nonce}'"
};
app.use((req, res, next) => {
const environment = process.env.NODE_ENV || 'development';
const nonce = generateNonce();
res.locals.nonce = nonce;
res.setHeader('Content-Security-Policy', cspBuilder[environment].replace('${nonce}', nonce));
// Start with less restrictive policies
res.setHeader('X-Frame-Options', 'SAMEORIGIN');
res.setHeader('Permissions-Policy', 'camera=(self), microphone=(self), geolocation=(self)');
next();
});
Mistake 2: Inconsistent Header Application
// BAD: Headers only on some routes
app.get('/', (req, res) => {
res.setHeader('X-Content-Type-Options', 'nosniff');
res.render('index');
});
app.get('/api/data', (req, res) => {
// Forgot security headers here!
res.json({ data: 'sensitive' });
});
// GOOD: Centralized security header middleware
class SecurityHeaders {
constructor(config = {}) {
this.config = {
enableHSTS: config.enableHSTS !== false,
enableCSP: config.enableCSP !== false,
enableFrameOptions: config.enableFrameOptions !== false,
...config
};
}
apply() {
return (req, res, next) => {
// Always apply base security headers
res.setHeader('X-Content-Type-Options', 'nosniff');
res.setHeader('X-XSS-Protection', '0'); // Modern browsers should use CSP
res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin');
// Conditional headers based on configuration
if (this.config.enableHSTS && req.secure) {
res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
}
if (this.config.enableCSP) {
res.setHeader('Content-Security-Policy', this.buildCSP(req));
}
if (this.config.enableFrameOptions) {
res.setHeader('X-Frame-Options', 'SAMEORIGIN');
}
// Apply route-specific overrides
res.setSecurityHeader = (name, value) => {
res.setHeader(name, value);
};
next();
};
}
buildCSP(req) {
// Dynamic CSP based on request context
return this.config.cspBuilder ? this.config.cspBuilder(req) : "default-src 'self'";
}
}
// Apply globally
app.use(new SecurityHeaders({
enableHSTS: process.env.NODE_ENV === 'production',
cspBuilder: (req) => {
// Customize CSP per route if needed
if (req.path.startsWith('/admin')) {
return "default-src 'self'; frame-ancestors 'none'";
}
return "default-src 'self'; frame-ancestors 'self'";
}
}).apply());
Mistake 3: Conflicting Security Policies
// BAD: Multiple conflicting CSP headers
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"]
}
}
}));
// Later in the code...
app.use((req, res, next) => {
// This overwrites the helmet CSP!
res.setHeader('Content-Security-Policy', "default-src *");
next();
});
// GOOD: Unified security header management
class UnifiedSecurityHeaders {
constructor() {
this.policies = new Map();
this.middleware = [];
}
use(middleware) {
this.middleware.push(middleware);
return this;
}
setPolicy(name, value) {
this.policies.set(name, value);
return this;
}
build() {
return async (req, res, next) => {
// Apply all middleware in order
for (const mw of this.middleware) {
await new Promise((resolve) => mw(req, res, resolve));
}
// Apply unified policies (overwrites any conflicts)
for (const [name, value] of this.policies) {
res.setHeader(name, typeof value === 'function' ? value(req, res) : value);
}
next();
};
}
}
const security = new UnifiedSecurityHeaders()
.use(helmet({ contentSecurityPolicy: false })) // Disable helmet's CSP
.setPolicy('Content-Security-Policy', (req) => {
// Single source of truth for CSP
return buildCSPPolicy(req);
})
.setPolicy('Permissions-Policy', 'geolocation=(), camera=(), microphone=()')
.build();
app.use(security);