# 🛡️ Hack23 AB — Security Headers Implementation

🔒 Technical Security Controls
🎯 Implementing defense-in-depth for API services

📋 Document Owner: CEO | 📄 Version: 1.0 | 📅 Last Updated: 2025-02-16 (UTC)
🔄 Review Cycle: Quarterly | ⏰ Next Review: 2025-05-16

# Security Headers Implementation

## Overview

This document explains the security headers implemented to protect the European Parliament MCP Server API, in compliance with Hack23 AB's ISMS Secure Development Policy.

## Background

As a Node.js/TypeScript API server implementing the Model Context Protocol (MCP), this service requires comprehensive HTTP security headers to protect against common web vulnerabilities and ensure secure communication with MCP clients accessing European Parliament data.

This implementation aligns with:

- Secure Development Policy - Security testing and DAST requirements
- Information Security Policy - Security controls implementation
- Privacy Policy - GDPR and data protection requirements

## Implemented Security Headers

### 1. Content Security Policy (CSP)

HTTP Header:

    Content-Security-Policy: default-src 'none'; frame-ancestors 'none'; base-uri 'none'

Purpose: Restricts the sources from which content can be loaded. For an API server, we use a highly restrictive policy.

Directives Explained:

- `default-src 'none'` - Deny all resource loading by default (API doesn't serve client-side content)
- `frame-ancestors 'none'` - Prevent API responses from being embedded in frames (clickjacking protection)
- `base-uri 'none'` - Prevent base tag injection attacks

Implementation (Express.js):

    import helmet from 'helmet';

    app.use(helmet.contentSecurityPolicy({
      directives: {
        defaultSrc: ["'none'"],
        frameAncestors: ["'none'"],
        baseUri: ["'none'"]
      }
    }));

ZAP/OWASP Issues Addressed:

- ✅ Content Security Policy (CSP) Header Not Set
- ✅ Clickjacking protection
- ✅ Base tag injection prevention

### 2. X-Frame-Options

HTTP Header:

    X-Frame-Options: DENY

Purpose: Prevents API responses from being displayed in frames, iframes, or embedded objects.

Implementation (Express.js):

    app.use(helmet.frameguard({ action: 'deny' }));

ZAP/OWASP Issues Addressed:

- ✅ Missing Anti-clickjacking Header

### 3. X-Content-Type-Options

HTTP Header:

    X-Content-Type-Options: nosniff

Purpose: Prevents browsers from MIME-sniffing responses, ensuring the declared Content-Type is respected.

Implementation (Express.js):

    app.use(helmet.noSniff());

ZAP/OWASP Issues Addressed:

- ✅ X-Content-Type-Options Header Missing
- ✅ MIME-sniffing attacks

### 4. Strict-Transport-Security (HSTS)

HTTP Header:

    Strict-Transport-Security: max-age=31536000; includeSubDomains; preload

Purpose: Forces all connections to use HTTPS, preventing protocol downgrade attacks.

Configuration:

- `max-age=31536000` - 1 year validity
- `includeSubDomains` - Apply to all subdomains
- `preload` - Eligible for browser HSTS preload lists

Implementation (Express.js):

    app.use(helmet.hsts({
      maxAge: 31536000,
      includeSubDomains: true,
      preload: true
    }));

ZAP/OWASP Issues Addressed:

- ✅ Strict-Transport-Security Header Not Set
- ✅ Protocol downgrade attacks

Note: Only enable HSTS if your API is served exclusively over HTTPS.

### 5. X-XSS-Protection

HTTP Header:

    X-XSS-Protection: 0

Purpose: Disables legacy XSS filters that can introduce vulnerabilities. Modern protection comes from CSP.

Implementation (Express.js):

    app.use(helmet.xssFilter());

Rationale: Modern browsers have deprecated XSS filters due to security issues. CSP provides better protection.

### 6. Referrer-Policy

HTTP Header:

    Referrer-Policy: no-referrer

Purpose: Controls referrer information sent with requests. For API security, no referrer is sent.

Implementation (Express.js):

    app.use(helmet.referrerPolicy({ policy: 'no-referrer' }));

Benefits:

- Prevents leaking sensitive API endpoint information
- Protects European Parliament data access patterns
- GDPR privacy enhancement

### 7. Permissions-Policy

HTTP Header:

    Permissions-Policy: geolocation=(), microphone=(), camera=(), payment=(), usb=()

Purpose: Controls which browser features and APIs can be used.

Implementation (Express.js):

    app.use(helmet.permissionsPolicy({
      features: {
        geolocation: [],
        microphone: [],
        camera: [],
        payment: [],
        usb: []
      }
    }));

Features Disabled:

- Geolocation API
- Microphone access
- Camera access
- Payment Request API
- USB device access

### 8. Cross-Origin-Opener-Policy (COOP)

HTTP Header:

    Cross-Origin-Opener-Policy: same-origin

Purpose: Isolates the browsing context from cross-origin documents.

Implementation (Express.js):

    app.use(helmet.crossOriginOpenerPolicy({ policy: 'same-origin' }));

ZAP/OWASP Issues Addressed:

- ✅ Spectre-style attacks mitigation

### 9. Cross-Origin-Resource-Policy (CORP)

HTTP Header:

    Cross-Origin-Resource-Policy: same-origin

Purpose: Prevents other origins from reading the resource.

Implementation (Express.js):

    app.use(helmet.crossOriginResourcePolicy({ policy: 'same-origin' }));

Benefits:

- Protects European Parliament data from unauthorized cross-origin access
- Prevents information leakage to third-party sites

### 10. Cross-Origin-Embedder-Policy (COEP)

HTTP Header:

    Cross-Origin-Embedder-Policy: require-corp

Purpose: Prevents loading cross-origin resources without explicit permission.

Implementation (Express.js):

    app.use(helmet.crossOriginEmbedderPolicy());

## API-Specific Security Headers

### 11. X-API-Version

HTTP Header:

    X-API-Version: 1.0.0

Purpose: Informs clients of the API version for compatibility and debugging.

Implementation (Express.js):

    app.use((req, res, next) => {
      res.setHeader('X-API-Version', '1.0.0');
      next();
    });

### 12. X-RateLimit-* Headers

HTTP Headers:

    X-RateLimit-Limit: 100
    X-RateLimit-Remaining: 95
    X-RateLimit-Reset: 1644841200

Purpose: Informs clients of rate limiting status to prevent abuse.

Implementation (Express.js):

    import rateLimit from 'express-rate-limit';

    const limiter = rateLimit({
      windowMs: 15 * 60 * 1000, // 15 minutes
      max: 100, // limit each IP to 100 requests per windowMs
      standardHeaders: true, // Return rate limit info in headers
      legacyHeaders: false
    });

    app.use('/api/', limiter);

Benefits:

- DoS protection
- Fair resource allocation
- Client self-regulation

### 13. Cache-Control (API Responses)

HTTP Header:

    Cache-Control: no-store, no-cache, must-revalidate, private

Purpose: Prevents caching of sensitive European Parliament data.

Implementation (Express.js):

    app.use((req, res, next) => {
      res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, private');
      res.setHeader('Pragma', 'no-cache');
      res.setHeader('Expires', '0');
      next();
    });

GDPR Alignment:

- Prevents unauthorized data retention
- Supports right to erasure
- Minimizes data exposure window

## CORS Configuration for MCP Server

### Secure CORS Headers

HTTP Headers:

    Access-Control-Allow-Origin: https://trusted-mcp-client.example.com
    Access-Control-Allow-Methods: GET, POST, OPTIONS
    Access-Control-Allow-Headers: Content-Type, Authorization
    Access-Control-Max-Age: 86400
    Access-Control-Allow-Credentials: true

Implementation (Express.js):

    import cors from 'cors';

    const corsOptions = {
      origin: process.env.ALLOWED_ORIGINS?.split(',') || [],
      methods: ['GET', 'POST', 'OPTIONS'],
      allowedHeaders: ['Content-Type', 'Authorization'],
      credentials: true,
      maxAge: 86400
    };

    app.use(cors(corsOptions));

Security Considerations:

- Never use `Access-Control-Allow-Origin: *` for authenticated APIs
- Whitelist specific MCP client origins
- Validate Origin header server-side
- Use credentials only when necessary

## Complete Security Middleware Setup

### Recommended Express.js Configuration

    import express from 'express';
    import helmet from 'helmet';
    import cors from 'cors';
    import rateLimit from 'express-rate-limit';

    const app = express();

    // 1. Helmet - Core security headers
    app.use(helmet({
      contentSecurityPolicy: {
        directives: {
          defaultSrc: ["'none'"],
          frameAncestors: ["'none'"],
          baseUri: ["'none'"]
        }
      },
      hsts: {
        maxAge: 31536000,
        includeSubDomains: true,
        preload: true
      },
      referrerPolicy: {
        policy: 'no-referrer'
      },
      permissionsPolicy: {
        features: {
          geolocation: [],
          microphone: [],
          camera: [],
          payment: [],
          usb: []
        }
      }
    }));

    // 2. CORS configuration
    const corsOptions = {
      origin: process.env.ALLOWED_ORIGINS?.split(',') || [],
      methods: ['GET', 'POST', 'OPTIONS'],
      allowedHeaders: ['Content-Type', 'Authorization'],
      credentials: true,
      maxAge: 86400
    };
    app.use(cors(corsOptions));

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

    // 4. Custom security headers
    app.use((req, res, next) => {
      res.setHeader('X-API-Version', '1.0.0');
      res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, private');
      res.setHeader('Pragma', 'no-cache');
      res.setHeader('Expires', '0');
      next();
    });

    // Your API routes here
    app.use('/api', apiRoutes);

    export default app;

## Testing

### Unit Tests for Security Headers

Create comprehensive tests to validate all security headers:

    import request from 'supertest';
    import app from './app';

    describe('Security Headers', () => {
      it('should set CSP header', async () => {
        const response = await request(app).get('/api/health');
        expect(response.headers['content-security-policy']).toContain("default-src 'none'");
      });

      it('should set X-Frame-Options header', async () => {
        const response = await request(app).get('/api/health');
        expect(response.headers['x-frame-options']).toBe('DENY');
      });

      it('should set X-Content-Type-Options header', async () => {
        const response = await request(app).get('/api/health');
        expect(response.headers['x-content-type-options']).toBe('nosniff');
      });

      it('should set Strict-Transport-Security header', async () => {
        const response = await request(app).get('/api/health');
        expect(response.headers['strict-transport-security']).toContain('max-age=31536000');
      });

      it('should set Referrer-Policy header', async () => {
        const response = await request(app).get('/api/health');
        expect(response.headers['referrer-policy']).toBe('no-referrer');
      });

      it('should set COOP header', async () => {
        const response = await request(app).get('/api/health');
        expect(response.headers['cross-origin-opener-policy']).toBe('same-origin');
      });

      it('should set CORP header', async () => {
        const response = await request(app).get('/api/health');
        expect(response.headers['cross-origin-resource-policy']).toBe('same-origin');
      });

      it('should set Cache-Control for API responses', async () => {
        const response = await request(app).get('/api/parliament/members');
        expect(response.headers['cache-control']).toContain('no-store');
      });

      it('should enforce rate limiting', async () => {
        const requests = Array(101).fill(null).map(() => request(app).get('/api/health'));
        const responses = await Promise.all(requests);
        const tooManyRequests = responses.filter(r => r.status === 429);
        expect(tooManyRequests.length).toBeGreaterThan(0);
      });
    });

## DAST Verification with OWASP ZAP

### Running ZAP Against the API

    # Start the API server
    npm start

    # Run ZAP baseline scan
    docker run -t owasp/zap2docker-stable zap-baseline.py \
      -t http://localhost:3000/api \
      -r zap-report.html

    # Run ZAP full scan (for comprehensive testing)
    docker run -t owasp/zap2docker-stable zap-full-scan.py \
      -t http://localhost:3000/api \
      -r zap-full-report.html

### Expected ZAP Results

- ✅ No high-severity vulnerabilities
- ✅ All security headers present
- ✅ No CORS misconfigurations
- ✅ No sensitive data in error messages
- ✅ Rate limiting functional

## GDPR Compliance Headers

### Data Protection Headers

For European Parliament data handling, consider additional headers:

    // Indicate data processing purpose
    res.setHeader('X-Data-Processing-Purpose', 'european-parliament-api-access');

    // Indicate data retention policy
    res.setHeader('X-Data-Retention-Days', '90');

    // Indicate GDPR compliance
    res.setHeader('X-GDPR-Compliant', 'true');

Note: These are custom headers for internal documentation and should not replace proper GDPR compliance measures.

## Monitoring and Logging

### Security Header Monitoring

Implement logging for security header violations:

    app.use((req, res, next) => {
      const originalSend = res.send;
      res.send = function(data) {
        // Log if security headers are missing
        const requiredHeaders = [
          'content-security-policy',
          'x-frame-options',
          'x-content-type-options',
          'strict-transport-security'
        ];

        requiredHeaders.forEach(header => {
          if (!res.getHeader(header)) {
            console.error(`Security header missing: ${header}`, {
              path: req.path,
              method: req.method,
              timestamp: new Date().toISOString()
            });
          }
        });

        return originalSend.call(this, data);
      };
      next();
    });

## Browser Compatibility

HTTP security headers are supported by:

- ✅ Chrome/Edge (all modern versions)
- ✅ Firefox (all modern versions)
- ✅ Safari (all modern versions)
- ✅ Node.js HTTP clients (fetch, axios, etc.)
- ✅ MCP client libraries

## Deployment Considerations

### Production Checklist

Before deploying to production:

- [ ] All security headers implemented and tested
- [ ] HTTPS enforced with valid TLS certificate
- [ ] HSTS header enabled (after HTTPS validation)
- [ ] Rate limiting configured appropriately
- [ ] CORS origins whitelisted (never use `*`)
- [ ] Error messages sanitized (no stack traces)
- [ ] Audit logging enabled for all API access
- [ ] GDPR compliance verified
- [ ] DAST scan passed with no high-severity issues

### Infrastructure-Level Headers

If using a reverse proxy (nginx, Cloudflare, etc.), configure headers at that level:

Nginx example:

    add_header Content-Security-Policy "default-src 'none'; frame-ancestors 'none';" always;
    add_header X-Frame-Options "DENY" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
    add_header Referrer-Policy "no-referrer" always;

## Future Improvements

1. Certificate Transparency - Monitor TLS certificate issuance
2. Subresource Integrity (SRI) - If serving any client-side resources
3. Report-URI / report-to - CSP violation reporting
4. NEL (Network Error Logging) - Network-level error tracking
5. HTTPS Certificate Pinning - Advanced certificate validation

## 📚 Related Documents

### Internal Documentation

- 🔒 SECURITY.md - Security policy and vulnerability reporting
- 📖 README.md - Project overview
- 🤖 Copilot Agents - Security-focused development agents

### ISMS-PUBLIC Policies

- 🔐 Information Security Policy - Overall security governance
- 🛠️ Secure Development Policy - SDLC and security testing requirements
- 🔍 Vulnerability Management - Security vulnerability handling
- 🔒 Privacy Policy - GDPR compliance
- 🏷️ Classification Framework - CIA triad and impact levels

### External References

- MDN: HTTP Headers
- OWASP: Security Headers
- Helmet.js Documentation
- SecurityHeaders.com - Test your API's security headers
- ZAP Scanning Rules
- NIST: API Security

📋 Document Control:
✅ Approved by: James Pether Sörling, CEO
📤 Distribution: Public
🏷️ Classification:
📅 Effective Date: 2025-02-16
⏰ Next Review: 2025-05-16
🎯 Framework Compliance: