European Parliament MCP Server API - v1.1.26
    Preparing search index...

    Hack23 AB Logo

    πŸ›‘οΈ Hack23 AB β€” Security Headers Implementation

    πŸ”’ Technical Security Controls
    🎯 Implementing defense-in-depth for API services

    Owner Version Effective Date Review Cycle

    πŸ“‹ Document Owner: CEO | πŸ“„ Version: 1.0 | πŸ“… Last Updated: 2025-02-16 (UTC)
    πŸ”„ Review Cycle: Quarterly | ⏰ Next Review: 2025-05-16


    Security Headers Implementation

    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.

    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:

    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

    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

    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

    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.

    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.

    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

    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

    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

    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

    HTTP Header:

    Cross-Origin-Embedder-Policy: require-corp
    

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

    Implementation (Express.js):

    app.use(helmet.crossOriginEmbedderPolicy());
    

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

    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

    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

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

    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);
    });
    });
    # 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
    • βœ… No high-severity vulnerabilities
    • βœ… All security headers present
    • βœ… No CORS misconfigurations
    • βœ… No sensitive data in error messages
    • βœ… Rate limiting functional

    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.

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

    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

    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

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


    πŸ“‹ Document Control:
    βœ… Approved by: James Pether SΓΆrling, CEO
    πŸ“€ Distribution: Public
    🏷️ Classification: Confidentiality: Public
    πŸ“… Effective Date: 2025-02-16
    ⏰ Next Review: 2025-05-16
    🎯 Framework Compliance: ISO 27001 NIST CSF 2.0 CIS Controls OWASP GDPR Compliant