# Helmet.js: Secure Express Apps

# Helmet.js: Secure Express Apps - HTTP Headers Security

## Problem

Express applications are vulnerable to various HTTP header-based attacks by default. Without proper security headers, applications expose themselves to:

- **XSS (Cross-Site Scripting)**: Attackers inject malicious scripts into web pages
- **Clickjacking**: Malicious sites frame your application to trick users
- **MIME-type sniffing**: Browsers misinterpret file types, executing harmful content
- **Man-in-the-Middle attacks**: Unencrypted connections allow interception
- **Information disclosure**: Server details leak through headers
- **Insecure dependencies**: Vulnerable libraries compromise security

These vulnerabilities exist because Express doesn't set security headers by default, leaving applications exposed to common web attacks.

## Solution

**Helmet.js** is a comprehensive middleware suite that automatically sets 15+ HTTP security headers, following OWASP best practices. It provides:

- **Content Security Policy (CSP)**: Controls resource loading
- **X-Frame-Options**: Prevents clickjacking
- **X-Content-Type-Options**: Prevents MIME sniffing
- **Strict-Transport-Security (HSTS)**: Enforces HTTPS
- **X-XSS-Protection**: Legacy XSS protection
- **Referrer-Policy**: Controls referrer information
- **Permissions-Policy**: Restricts browser features
- **Dependency vulnerability scanning**: Identifies unsafe packages

Helmet.js provides defense-in-depth security with minimal configuration overhead.

## Code Implementation

### Basic Setup

```javascript
// app.js
const express = require('express');
const helmet = require('helmet');

const app = express();

// Apply Helmet middleware - sets all default security headers
app.use(helmet());

app.get('/', (req, res) => {
  res.send('Secure Express App');
});

app.listen(3000, () => {
  console.log('Server running on port 3000');
});
```

### Advanced Configuration

```javascript
const express = require('express');
const helmet = require('helmet');

const app = express();

// Custom Helmet configuration
app.use(helmet({
  // Content Security Policy
  contentSecurityPolicy: {
    directives: {
      defaultSrc: ["'self'"],
      scriptSrc: ["'self'", "'unsafe-inline'", 'trusted-cdn.com'],
      styleSrc: ["'self'", "'unsafe-inline'"],
      imgSrc: ["'self'", 'data:', 'https:'],
      connectSrc: ["'self'", 'api.example.com'],
      fontSrc: ["'self'", 'fonts.googleapis.com'],
      objectSrc: ["'none'"],
      mediaSrc: ["'self'"],
      frameSrc: ["'none'"],
    },
  },
  
  // Clickjacking protection
  frameguard: {
    action: 'deny', // or 'sameorigin'
  },
  
  // MIME-type sniffing prevention
  noSniff: true,
  
  // XSS protection (legacy)
  xssFilter: true,
  
  // HTTPS enforcement
  hsts: {
    maxAge: 31536000, // 1 year in seconds
    includeSubDomains: true,
    preload: true,
  },
  
  // Referrer policy
  referrerPolicy: {
    policy: 'strict-origin-when-cross-origin',
  },
  
  // Permissions policy (formerly Feature-Policy)
  permissionsPolicy: {
    features: {
      geolocation: ["'none'"],
      microphone: ["'none'"],
      camera: ["'none'"],
      payment: ["'self'"],
    },
  },
  
  // Remove X-Powered-By header
  hidePoweredBy: true,
}));

// Additional security middleware
app.use(express.json({ limit: '10kb' })); // Limit payload size
app.use(express.urlencoded({ limit: '10kb', extended: true }));

// CORS configuration
const cors = require('cors');
app.use(cors({
  origin: ['https://trusted-domain.com'],
  credentials: true,
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
}));

// Rate limiting
const rateLimit = require('express-rate-limit');
const limiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100, // Limit each IP to 100 requests per windowMs
  message: 'Too many requests from this IP',
});
app.use('/api/', limiter);

// Routes
app.get('/', (req, res) => {
  res.json({ message: 'Secure API' });
});

app.post('/api/data', (req, res) => {
  // Validate and sanitize input
  const { data } = req.body;
  res.json({ received: data });
});

// Error handling
app.use((err, req, res, next) => {
  console.error(err.stack);
  res.status(500).json({ 
    error: 'Internal Server Error',
    // Don't expose stack traces in production
  });
});

app.listen(3000, () => {
  console.log('Secure server running on port 3000');
});
```

### Selective Header Configuration

```javascript
const helmet = require('helmet');
const express = require('express');

const app = express();

// Disable specific headers if needed
app.use(helmet({
  contentSecurityPolicy: false, // Disable CSP
  frameguard: true,
  hsts: true,
}));

// Or apply individual middleware
app.use(helmet.frameguard({ action: 'deny' }));
app.use(helmet.hsts({ maxAge: 31536000 }));
app.use(helmet.noSniff());
app.use(helmet.xssFilter());
app.use(helmet.referrerPolicy({ policy: 'strict-origin-when-cross-origin' }));

app.listen(3000);
```

### Production-Ready Setup

```javascript
const express = require('express');
const helmet = require('helmet');
const mongoSanitize = require('express-mongo-sanitize');
const rateLimit = require('express-rate-limit');
const cors = require('cors');

const app = express();

// Trust proxy
app.set('trust proxy', 1);

// Security headers
app.use(helmet({
  contentSecurityPolicy: {
    directives: {
      defaultSrc: ["'self'"],
      scriptSrc: ["'self'"],
      styleSrc: ["'self'", "'unsafe-inline'"],
    },
  },
  hsts: {
    maxAge: 31536000,
    includeSubDomains: true,
    preload: true,
  },
}));

// CORS
app.use(cors({
  origin: process.env.ALLOWED_ORIGINS?.split(','),
  credentials: true,
}));

// Body parser with size limits
app.use(express.json({ limit: '10kb' }));
app.use(express.urlencoded({ limit: '10kb', extended: true }));

// Data sanitization against NoSQL injection
app.use(mongoSanitize());

// Rate limiting
const limiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 100,
  standardHeaders: true,
  legacyHeaders: false,
});
app.use('/api/', limiter);

// Routes
app.get('/health', (req, res) => {
  res.json({ status: 'OK' });
});

// 404 handler
app.use((req, res) => {
  res.status(404).json({ error: 'Not Found' });
});

// Error handler
app.use((err, req, res, next) => {
  res.status(err.status || 500).json({
    error: process.env.NODE_ENV === 'production' 
      ? 'Internal Server Error' 
      : err.message,
  });
});

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`);
});
```

## Security Headers Explained

| Header | Purpose | Default Value |
|--------|---------|----------------|
| **Content-Security-Policy** | Controls resource loading | Restrictive |
| **X-Frame-Options** | Prevents clickjacking | DENY |
| **X-Content-Type-Options** | Prevents MIME sniffing | nosniff |
| **Strict-Transport-Security** | Enforces HTTPS | 180 days |
| **X-XSS-Protection** | Legacy XSS protection | 1; mode=block |
| **Referrer-Policy** | Controls referrer info | no-referrer |
| **Permissions-Policy** | Restricts browser features | Restrictive |

## Installation

```bash
npm install helmet
npm install express-rate-limit express-mongo-sanitize cors
```

## Key Takeaways

✅ **Always use Helmet.js** in production Express applications  
✅ **Customize CSP** based on your application's resource needs  
✅ **Enable HSTS preload** for maximum HTTPS enforcement  
✅ **Combine with rate limiting** and input validation  
✅ **Test headers** using security scanning tools  
✅ **Keep dependencies updated** for latest security patches

Helmet.js provides essential HTTP header security with minimal configuration, making it a must-have for any Express application handling sensitive data.
