-
Notifications
You must be signed in to change notification settings - Fork 615
[TESTING][SECURITY]: Security headers manual test plan (CSP, HSTS, CORS, clickjacking) #2396
Description
[TESTING][SECURITY]: Security headers manual test plan (CSP, HSTS, CORS, clickjacking)
Goal
Produce a comprehensive manual test plan for validating all security headers are correctly set and protect against common web attacks including XSS, clickjacking, MIME sniffing, and information disclosure.
Why Now?
Security testing is critical for GA release:
- Production Readiness: Security must be validated before release
- Compliance: Required by security standards and audits
- Defense in Depth: Validates multiple protection layers
- Attack Mitigation: Prevents common exploitation techniques
- User Trust: Security issues erode confidence
User Stories
Story 1: Content Security Policy Protection
As a security administrator
I want Content Security Policy headers to be enforced
So that XSS attacks are mitigated through browser-level controls
Acceptance Criteria
Feature: Content Security Policy headers
Background:
Given the gateway is running with SECURITY_HEADERS_ENABLED=true
And the admin UI is enabled
Scenario: CSP header is present on all responses
When I make a request to any endpoint
Then the response should include "Content-Security-Policy" header
And the CSP should include "default-src 'self'"
Scenario: CSP allows configured CDN sources for scripts
When I access the admin UI
Then the CSP script-src should include CDN allowlist
And inline scripts should be blocked unless nonce-protected
Scenario: CSP allows WebSocket connections
When I check the CSP connect-src directive
Then it should include "wss:" for WebSocket support
And it should include "ws:" for development environments
Scenario: CSP violation is reported
Given CSP reporting is configured
When a CSP violation occurs
Then the violation should be logged
And the violating resource should be blockedStory 2: HSTS Protection
As a security administrator
I want HSTS headers to enforce HTTPS connections
So that downgrade attacks and cookie hijacking are prevented
Acceptance Criteria
Feature: HTTP Strict Transport Security
Background:
Given the gateway is running with HTTPS enabled
And HSTS_ENABLED=true
Scenario: HSTS header is present on HTTPS responses
When I make an HTTPS request to the gateway
Then the response should include "Strict-Transport-Security" header
And the max-age should be at least 31536000 (1 year)
Scenario: HSTS includes subdomains when configured
Given HSTS_INCLUDE_SUBDOMAINS=true
When I make an HTTPS request
Then the HSTS header should include "includeSubDomains"
Scenario: HSTS preload directive when configured
Given HSTS_PRELOAD=true
When I make an HTTPS request
Then the HSTS header should include "preload"
Scenario: HSTS is not set on HTTP responses
When I make an HTTP request (non-HTTPS)
Then the response should NOT include "Strict-Transport-Security" headerStory 3: Clickjacking Protection
As a security administrator
I want X-Frame-Options headers to prevent clickjacking
So that the application cannot be embedded in malicious frames
Acceptance Criteria
Feature: Clickjacking protection headers
Background:
Given the gateway is running with SECURITY_HEADERS_ENABLED=true
Scenario: X-Frame-Options DENY is set by default
Given X_FRAME_OPTIONS is not configured
When I make a request to the gateway
Then the response should include "X-Frame-Options: DENY"
Scenario: X-Frame-Options SAMEORIGIN when configured
Given X_FRAME_OPTIONS=SAMEORIGIN
When I make a request to the gateway
Then the response should include "X-Frame-Options: SAMEORIGIN"
Scenario: CSP frame-ancestors aligns with X-Frame-Options
When I make a request to the gateway
Then the CSP should include "frame-ancestors 'none'" for DENY
Or the CSP should include "frame-ancestors 'self'" for SAMEORIGINStory 4: CORS Configuration
As a developer integrating with the gateway
I want CORS headers to be properly configured
So that cross-origin requests work securely
Acceptance Criteria
Feature: CORS header configuration
Background:
Given the gateway is running with CORS enabled
Scenario: Preflight OPTIONS request is handled
When I send an OPTIONS request with Origin header
Then the response should include "Access-Control-Allow-Origin"
And the response should include "Access-Control-Allow-Methods"
And the response should include "Access-Control-Allow-Headers"
And the response status should be 200 or 204
Scenario: Credentials are allowed when configured
Given CORS_ALLOW_CREDENTIALS=true
When I send a request with credentials
Then the response should include "Access-Control-Allow-Credentials: true"
And Access-Control-Allow-Origin should NOT be "*"
Scenario: Origin validation for non-wildcard CORS
Given CORS is configured with specific origins
When I send a request from an allowed origin
Then the request should succeed with proper CORS headers
When I send a request from a disallowed origin
Then the CORS headers should not include that origin
Scenario: Exposed headers are configurable
Given specific headers need to be exposed
When I configure Access-Control-Expose-Headers
Then those headers should be accessible to the clientStory 5: Additional Security Headers
As a security administrator
I want all recommended security headers to be set
So that the application is protected against various attacks
Acceptance Criteria
Feature: Additional security headers
Background:
Given the gateway is running with SECURITY_HEADERS_ENABLED=true
Scenario: X-Content-Type-Options prevents MIME sniffing
When I make a request to the gateway
Then the response should include "X-Content-Type-Options: nosniff"
Scenario: Referrer-Policy limits referrer information
When I make a request to the gateway
Then the response should include "Referrer-Policy"
And the value should be "strict-origin-when-cross-origin" or more restrictive
Scenario: X-Download-Options protects IE users
When I make a request to the gateway
Then the response should include "X-Download-Options: noopen"
Scenario: Server header is removed for information disclosure prevention
When I make a request to the gateway
Then the response should NOT include identifying server information
Or the Server header should be generic (e.g., just "uvicorn")
Scenario: X-XSS-Protection is set to 0 (rely on CSP)
When I make a request to the gateway
Then the response should include "X-XSS-Protection: 0"
And CSP should be the primary XSS protection mechanismArchitecture
┌─────────────────────────────────────────────────────────────────────┐
│ Client Browser │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Security Header Enforcement │ │
│ │ • CSP: Block inline scripts, restrict sources │ │
│ │ • HSTS: Force HTTPS, prevent downgrades │ │
│ │ • X-Frame-Options: Block framing attempts │ │
│ │ • CORS: Enforce same-origin policy exceptions │ │
│ └─────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ MCP Gateway │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Security Headers Middleware │ │
│ │ security_headers.py │ │
│ │ │ │
│ │ Request Flow: │ │
│ │ 1. Request arrives │ │
│ │ 2. Process request normally │ │
│ │ 3. Add security headers to response │ │
│ │ 4. Return response with headers │ │
│ │ │ │
│ │ Headers Added: │ │
│ │ ├── Content-Security-Policy │ │
│ │ ├── Strict-Transport-Security (HTTPS only) │ │
│ │ ├── X-Frame-Options │ │
│ │ ├── X-Content-Type-Options │ │
│ │ ├── X-XSS-Protection │ │
│ │ ├── X-Download-Options │ │
│ │ ├── Referrer-Policy │ │
│ │ └── Server (removed/sanitized) │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ CORS Middleware (FastAPI) │ │
│ │ • Origin validation │ │
│ │ • Preflight handling │ │
│ │ • Credential support │ │
│ │ • Exposed headers │ │
│ └─────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
Test Environment Setup
Environment Variables
# Security headers configuration
export SECURITY_HEADERS_ENABLED=true
# HSTS settings
export HSTS_ENABLED=true
export HSTS_MAX_AGE=31536000
export HSTS_INCLUDE_SUBDOMAINS=true
export HSTS_PRELOAD=false
# Frame options
export X_FRAME_OPTIONS=DENY
# CORS settings
export ALLOWED_ORIGINS="https://example.com,https://app.example.com"
export CORS_ALLOW_CREDENTIALS=true
export CORS_ALLOW_METHODS="GET,POST,PUT,DELETE,OPTIONS"
export CORS_ALLOW_HEADERS="Authorization,Content-Type,X-API-Key"
export CORS_EXPOSE_HEADERS="X-Request-ID,X-RateLimit-Remaining"
# CSP settings (if configurable)
export CSP_REPORT_URI="/csp-report"Test Setup Script
#!/bin/bash
# setup-security-headers-test.sh
# Start gateway with security headers enabled
export SECURITY_HEADERS_ENABLED=true
export HSTS_ENABLED=true
make dev &
sleep 5
# Verify gateway is running
curl -s http://localhost:4444/health | jq .Test Cases
TC-SH-001: CSP Header Present
Objective: Verify Content-Security-Policy header is present on all responses
Prerequisites:
- Gateway running with SECURITY_HEADERS_ENABLED=true
Steps:
# Step 1: Check CSP on API endpoint
curl -s -I http://localhost:4444/health | grep -i "content-security-policy"
# Step 2: Check CSP on admin UI
curl -s -I http://localhost:4444/ui/ | grep -i "content-security-policy"
# Step 3: Check CSP on static assets
curl -s -I http://localhost:4444/static/js/app.js | grep -i "content-security-policy"Expected Results:
- All responses include Content-Security-Policy header
- CSP includes
default-src 'self' - CSP includes appropriate script-src and style-src directives
TC-SH-002: CSP Script Source Restrictions
Objective: Verify CSP restricts script sources appropriately
Steps:
# Step 1: Extract CSP header
CSP=$(curl -s -I http://localhost:4444/ui/ | grep -i "content-security-policy" | cut -d: -f2-)
echo "CSP: $CSP"
# Step 2: Verify script-src directive
echo "$CSP" | grep -o "script-src[^;]*"
# Step 3: Test inline script blocking (browser test)
# Create HTML file that tries to execute inline script
cat > /tmp/csp-test.html << 'EOF'
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Security-Policy" content="script-src 'self'">
</head>
<body>
<script>alert('This should be blocked')</script>
</body>
</html>
EOFExpected Results:
- script-src restricts to 'self' and allowed CDNs
- Inline scripts are blocked unless using nonce
- Browser console shows CSP violation for blocked scripts
TC-SH-003: HSTS Header on HTTPS
Objective: Verify HSTS header is set correctly on HTTPS responses
Prerequisites:
- Gateway running with HTTPS enabled
- HSTS_ENABLED=true
Steps:
# Step 1: Make HTTPS request and check HSTS
curl -s -I --insecure https://localhost:4444/health | grep -i "strict-transport-security"
# Step 2: Verify max-age value
HSTS=$(curl -s -I --insecure https://localhost:4444/health | grep -i "strict-transport-security")
echo "$HSTS"
# Should show max-age=31536000 or configured value
# Step 3: Check for includeSubDomains
echo "$HSTS" | grep -i "includeSubDomains"
# Step 4: Verify HSTS NOT present on HTTP
curl -s -I http://localhost:4444/health | grep -i "strict-transport-security"
# Should return nothingExpected Results:
- HTTPS responses include Strict-Transport-Security header
- max-age is at least 31536000 (1 year)
- includeSubDomains present if configured
- HTTP responses do NOT include HSTS header
TC-SH-004: X-Frame-Options Header
Objective: Verify clickjacking protection via X-Frame-Options
Steps:
# Step 1: Check X-Frame-Options header
curl -s -I http://localhost:4444/ui/ | grep -i "x-frame-options"
# Step 2: Test with DENY setting
export X_FRAME_OPTIONS=DENY
# Restart gateway
curl -s -I http://localhost:4444/ui/ | grep -i "x-frame-options"
# Should show: X-Frame-Options: DENY
# Step 3: Test with SAMEORIGIN setting
export X_FRAME_OPTIONS=SAMEORIGIN
# Restart gateway
curl -s -I http://localhost:4444/ui/ | grep -i "x-frame-options"
# Should show: X-Frame-Options: SAMEORIGIN
# Step 4: Verify CSP frame-ancestors alignment
curl -s -I http://localhost:4444/ui/ | grep -i "content-security-policy" | grep -o "frame-ancestors[^;]*"Expected Results:
- X-Frame-Options header is present
- Value matches configuration (DENY or SAMEORIGIN)
- CSP frame-ancestors aligns with X-Frame-Options
TC-SH-005: CORS Preflight Handling
Objective: Verify CORS preflight (OPTIONS) requests are handled correctly
Steps:
# Step 1: Send preflight request
curl -s -I -X OPTIONS http://localhost:4444/api/tools \
-H "Origin: https://example.com" \
-H "Access-Control-Request-Method: POST" \
-H "Access-Control-Request-Headers: Authorization,Content-Type"
# Step 2: Verify CORS headers in response
# Should include:
# Access-Control-Allow-Origin: https://example.com (or *)
# Access-Control-Allow-Methods: POST (or list including POST)
# Access-Control-Allow-Headers: Authorization, Content-Type
# Step 3: Check response status
# Should be 200 or 204Expected Results:
- OPTIONS request returns 200 or 204
- Access-Control-Allow-Origin is present
- Access-Control-Allow-Methods includes requested method
- Access-Control-Allow-Headers includes requested headers
TC-SH-006: CORS Origin Validation
Objective: Verify CORS validates origins correctly
Prerequisites:
- CORS configured with specific origins (not wildcard)
Steps:
# Step 1: Request from allowed origin
curl -s -I http://localhost:4444/api/tools \
-H "Origin: https://example.com" | grep -i "access-control"
# Step 2: Request from disallowed origin
curl -s -I http://localhost:4444/api/tools \
-H "Origin: https://evil.com" | grep -i "access-control"
# Step 3: Request with credentials from allowed origin
curl -s -I http://localhost:4444/api/tools \
-H "Origin: https://example.com" \
-H "Cookie: session=abc123" | grep -i "access-control"Expected Results:
- Allowed origins receive Access-Control-Allow-Origin header
- Disallowed origins do NOT receive CORS headers
- Credentials work only with specific origin (not wildcard)
TC-SH-007: CORS Credentials Support
Objective: Verify CORS credentials are handled securely
Prerequisites:
- CORS_ALLOW_CREDENTIALS=true
- CORS configured with specific origins
Steps:
# Step 1: Check credentials header
curl -s -I http://localhost:4444/api/tools \
-H "Origin: https://example.com" | grep -i "access-control-allow-credentials"
# Step 2: Verify origin is NOT wildcard when credentials allowed
ORIGIN=$(curl -s -I http://localhost:4444/api/tools \
-H "Origin: https://example.com" | grep -i "access-control-allow-origin" | cut -d: -f2-)
echo "Origin: $ORIGIN"
# Should NOT be * when credentials are allowed
# Step 3: Test without Origin header
curl -s -I http://localhost:4444/api/tools | grep -i "access-control"Expected Results:
- Access-Control-Allow-Credentials: true is present
- Access-Control-Allow-Origin is specific origin, not wildcard
- Requests without Origin header don't get CORS headers
TC-SH-008: X-Content-Type-Options Header
Objective: Verify MIME sniffing prevention
Steps:
# Step 1: Check header on various content types
curl -s -I http://localhost:4444/health | grep -i "x-content-type-options"
curl -s -I http://localhost:4444/ui/ | grep -i "x-content-type-options"
curl -s -I http://localhost:4444/static/styles.css | grep -i "x-content-type-options"
# All should return: X-Content-Type-Options: nosniffExpected Results:
- All responses include X-Content-Type-Options: nosniff
- Content-Type header is accurate for each response
TC-SH-009: Referrer-Policy Header
Objective: Verify referrer information is controlled
Steps:
# Step 1: Check Referrer-Policy header
curl -s -I http://localhost:4444/ui/ | grep -i "referrer-policy"
# Step 2: Verify acceptable value
# Acceptable values (from most to least restrictive):
# - no-referrer
# - same-origin
# - strict-origin
# - strict-origin-when-cross-originExpected Results:
- Referrer-Policy header is present
- Value is "strict-origin-when-cross-origin" or more restrictive
TC-SH-010: Server Header Information Disclosure
Objective: Verify server header doesn't leak sensitive information
Steps:
# Step 1: Check Server header
curl -s -I http://localhost:4444/health | grep -i "^server:"
# Step 2: Verify no version information
# Should NOT show detailed version like "uvicorn 0.23.2" or "Python/3.11"
# Acceptable: "uvicorn" or no Server header at all
# Step 3: Check for other identifying headers
curl -s -I http://localhost:4444/health | grep -iE "^(x-powered-by|x-aspnet|x-runtime):"
# Should return nothingExpected Results:
- Server header is absent or contains minimal information
- No X-Powered-By or similar identifying headers
- No version numbers exposed
TC-SH-011: Security Headers Disabled
Objective: Verify behavior when security headers are disabled
Prerequisites:
- SECURITY_HEADERS_ENABLED=false
Steps:
# Step 1: Restart gateway with headers disabled
export SECURITY_HEADERS_ENABLED=false
# Restart gateway
# Step 2: Check for security headers
curl -s -I http://localhost:4444/health | grep -iE "(content-security-policy|x-frame-options|x-content-type-options)"
# Step 3: Verify CORS still works (separate middleware)
curl -s -I -X OPTIONS http://localhost:4444/api/tools \
-H "Origin: https://example.com" | grep -i "access-control"Expected Results:
- Security headers are NOT present when disabled
- CORS headers still work (separate configuration)
- Application still functions normally
TC-SH-012: Vary Header for Caching
Objective: Verify Vary header prevents cache poisoning
Steps:
# Step 1: Check Vary header on CORS responses
curl -s -I http://localhost:4444/api/tools \
-H "Origin: https://example.com" | grep -i "^vary:"
# Should include Origin in Vary header
# Step 2: Verify caching behavior difference
curl -s -I http://localhost:4444/api/tools \
-H "Origin: https://example.com" | grep -i "access-control-allow-origin"
curl -s -I http://localhost:4444/api/tools \
-H "Origin: https://other.com" | grep -i "access-control-allow-origin"Expected Results:
- Vary header includes "Origin" when CORS is active
- Different origins get different CORS responses
- Cache cannot serve wrong origin's CORS headers
TC-SH-013: MCP Endpoint Security Headers
Objective: Verify security headers on MCP-specific endpoints
Steps:
# Step 1: Check headers on SSE endpoint
curl -s -I http://localhost:4444/mcp/sse | head -20
# Step 2: Check headers on WebSocket upgrade
curl -s -I http://localhost:4444/mcp/ws \
-H "Upgrade: websocket" \
-H "Connection: Upgrade" | head -20
# Step 3: Check headers on streamable HTTP
curl -s -I http://localhost:4444/mcp/http | head -20Expected Results:
- SSE endpoint has appropriate security headers
- WebSocket endpoint handles upgrade correctly
- All MCP endpoints have consistent security header policy
TC-SH-014: CSP for WebSocket Connections
Objective: Verify CSP allows WebSocket connections
Steps:
# Step 1: Extract connect-src directive
CSP=$(curl -s -I http://localhost:4444/ui/ | grep -i "content-security-policy")
echo "$CSP" | grep -o "connect-src[^;]*"
# Step 2: Verify wss: and ws: are allowed
echo "$CSP" | grep -E "(wss:|ws:)"
# Step 3: Browser test - attempt WebSocket connection
# This requires browser testing to verify CSP doesn't block WebSocketExpected Results:
- connect-src includes wss: for secure WebSocket
- connect-src includes ws: for development
- WebSocket connections succeed from UI
Test Matrix
| Test Case | CSP | HSTS | X-Frame | CORS | X-Content-Type | Referrer | Server |
|---|---|---|---|---|---|---|---|
| TC-SH-001 | ✓ | ||||||
| TC-SH-002 | ✓ | ||||||
| TC-SH-003 | ✓ | ||||||
| TC-SH-004 | ✓ | ||||||
| TC-SH-005 | ✓ | ||||||
| TC-SH-006 | ✓ | ||||||
| TC-SH-007 | ✓ | ||||||
| TC-SH-008 | ✓ | ||||||
| TC-SH-009 | ✓ | ||||||
| TC-SH-010 | ✓ | ||||||
| TC-SH-011 | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | |
| TC-SH-012 | ✓ | ||||||
| TC-SH-013 | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| TC-SH-014 | ✓ |
Browser Testing Checklist
Some security header behaviors can only be verified in a browser:
- CSP blocks inline scripts (check console for violations)
- CSP blocks scripts from unauthorized sources
- HSTS redirects HTTP to HTTPS after first visit
- X-Frame-Options prevents embedding in iframe
- CORS preflight succeeds for cross-origin requests
- WebSocket connections work with CSP
Security Header Reference
| Header | Purpose | Recommended Value |
|---|---|---|
| Content-Security-Policy | XSS prevention | default-src 'self'; script-src 'self' [cdns]; ... |
| Strict-Transport-Security | Force HTTPS | max-age=31536000; includeSubDomains |
| X-Frame-Options | Clickjacking | DENY or SAMEORIGIN |
| X-Content-Type-Options | MIME sniffing | nosniff |
| X-XSS-Protection | Legacy XSS filter | 0 (rely on CSP) |
| X-Download-Options | IE file safety | noopen |
| Referrer-Policy | Referrer control | strict-origin-when-cross-origin |
| Access-Control-Allow-Origin | CORS | Specific origins (not * with credentials) |
Success Criteria
- All 14 test cases pass
- CSP header present and correctly configured
- HSTS header present on HTTPS responses only
- X-Frame-Options prevents clickjacking
- CORS correctly validates origins
- No sensitive information in Server header
- Browser testing confirms headers are enforced
Related Files
mcpgateway/middleware/security_headers.py- Security headers middlewaremcpgateway/config.py- Security header configurationmcpgateway/main.py- CORS middleware configuration
Related Issues
- [BUG][AUTH]: CORS preflight OPTIONS requests return 401 on /mcp endpoints #2152 - CORS handling for MCP endpoints