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);