# -*- coding: utf-8 -*-
"""Location: ./tests/security/test_configurable_headers.py
Copyright 2025
SPDX-License-Identifier: Apache-2.0
Authors: Mihai Criveti
Configurable Security Headers Testing.
This module tests the configurable security headers implementation for issue #533.
"""
# Standard
from unittest.mock import patch
# Third-Party
from fastapi import FastAPI
from fastapi.testclient import TestClient
# First-Party
from mcpgateway.config import settings
from mcpgateway.middleware.security_headers import SecurityHeadersMiddleware
def test_security_headers_can_be_disabled():
"""Test that security headers can be disabled via configuration."""
app = FastAPI()
app.add_middleware(SecurityHeadersMiddleware)
@app.get("/test")
def test_endpoint():
return {"message": "test"}
with patch.object(settings, "security_headers_enabled", False):
client = TestClient(app)
response = client.get("/test")
# When disabled, security headers should not be present
assert "X-Content-Type-Options" not in response.headers
assert "X-Frame-Options" not in response.headers
assert "X-XSS-Protection" not in response.headers
assert "X-Download-Options" not in response.headers
def test_individual_headers_configurable():
"""Test that individual security headers can be configured."""
app = FastAPI()
app.add_middleware(SecurityHeadersMiddleware)
@app.get("/test")
def test_endpoint():
return {"message": "test"}
# Test with some headers disabled
with patch.multiple(settings, security_headers_enabled=True, x_content_type_options_enabled=False, x_frame_options="SAMEORIGIN", x_xss_protection_enabled=False, x_download_options_enabled=True):
client = TestClient(app)
response = client.get("/test")
# Check configured headers
assert "X-Content-Type-Options" not in response.headers # Disabled
assert response.headers["X-Frame-Options"] == "SAMEORIGIN" # Custom value
assert "X-XSS-Protection" not in response.headers # Disabled
assert response.headers["X-Download-Options"] == "noopen" # Enabled
assert response.headers["Referrer-Policy"] == "strict-origin-when-cross-origin" # Always on
def test_hsts_configuration():
"""Test HSTS header configuration options."""
app = FastAPI()
app.add_middleware(SecurityHeadersMiddleware)
@app.get("/test")
def test_endpoint():
return {"message": "test"}
# Test with custom HSTS settings
with patch.multiple(
settings,
security_headers_enabled=True,
hsts_enabled=True,
hsts_max_age=7776000, # 90 days
hsts_include_subdomains=False,
):
client = TestClient(app)
response = client.get("/test", headers={"X-Forwarded-Proto": "https"})
# Check HSTS configuration
assert "Strict-Transport-Security" in response.headers
hsts_value = response.headers["Strict-Transport-Security"]
assert "max-age=7776000" in hsts_value
assert "includeSubDomains" not in hsts_value # Disabled
def test_hsts_can_be_disabled():
"""Test that HSTS can be disabled."""
app = FastAPI()
app.add_middleware(SecurityHeadersMiddleware)
@app.get("/test")
def test_endpoint():
return {"message": "test"}
with patch.multiple(settings, security_headers_enabled=True, hsts_enabled=False):
client = TestClient(app)
response = client.get("/test", headers={"X-Forwarded-Proto": "https"})
# HSTS should not be present when disabled
assert "Strict-Transport-Security" not in response.headers
def test_server_header_removal_configurable():
"""Test that server header removal is configurable."""
app = FastAPI()
app.add_middleware(SecurityHeadersMiddleware)
@app.get("/test")
def test_endpoint():
return {"message": "test"}
# Test with server header removal disabled
with patch.multiple(settings, security_headers_enabled=True, remove_server_headers=False):
client = TestClient(app)
response = client.get("/test")
# Server headers should not be removed when disabled
# Note: FastAPI/Starlette might not add these headers in test mode,
# but our middleware won't remove them if they exist
pass # This test mainly validates the configuration works
def test_all_headers_with_default_config():
"""Test all headers with default configuration."""
app = FastAPI()
app.add_middleware(SecurityHeadersMiddleware)
@app.get("/test")
def test_endpoint():
return {"message": "test"}
# Use default settings (all should be enabled)
client = TestClient(app)
response = client.get("/test")
# All default headers should be present
assert response.headers["X-Content-Type-Options"] == "nosniff"
assert response.headers["X-Frame-Options"] == "DENY"
assert response.headers["X-XSS-Protection"] == "0"
assert response.headers["X-Download-Options"] == "noopen"
assert response.headers["Referrer-Policy"] == "strict-origin-when-cross-origin"
assert "Content-Security-Policy" in response.headers