-
Notifications
You must be signed in to change notification settings - Fork 341
Description
Add Additional Configurable Security Headers to APIs for Admin UI and Static Assets
Summary: The nodejsscan
static analysis identified 9 missing HTTP security headers in the MCP Gateway Admin UI and static assets. These headers are essential security best practices to mitigate client-side attacks (XSS, Clickjacking, MIME sniffing) and reduce information leakage.
Scope: Apply security headers for UI-related routes and static content:
- Admin templates:
mcpgateway/templates/admin.html
,mcpgateway/templates/version_info_partial.html
- Static files:
mcpgateway/static/*.js
,mcpgateway/static/*.css
- Admin routes:
/admin
,/version
(when format=html) - Static route:
/static/*
- Cookie security in:
mcpgateway/admin.py
Implementation Details:
1. Create Security Headers Middleware
Add a new middleware class in mcpgateway/main.py
after the existing middleware classes (around line 386):
class SecurityHeadersMiddleware(BaseHTTPMiddleware):
"""Add security headers to responses for admin UI and static assets."""
async def dispatch(self, request: Request, call_next):
response = await call_next(request)
# Only apply to admin UI routes and static files when UI is enabled
if settings.mcpgateway_ui_enabled and settings.security_headers_enabled:
path = request.url.path
if (path.startswith("/admin") or
path.startswith("/static") or
(path == "/version" and "html" in request.headers.get("accept", "")) or
(path == "/version" and request.url.query and "format=html" in request.url.query)):
# Content Security Policy
if settings.csp_enabled:
csp_header_name = "Content-Security-Policy-Report-Only" if settings.csp_report_only else "Content-Security-Policy"
response.headers[csp_header_name] = settings.csp_header_value
# X-Frame-Options - prevent clickjacking
if settings.x_frame_options:
response.headers["X-Frame-Options"] = settings.x_frame_options
# Strict-Transport-Security - enforce HTTPS (only in production)
if settings.hsts_enabled and not settings.debug:
hsts_value = settings.hsts_header_value
if hsts_value:
response.headers["Strict-Transport-Security"] = hsts_value
# X-XSS-Protection - legacy XSS protection
if settings.x_xss_protection_enabled:
response.headers["X-XSS-Protection"] = "1; mode=block"
# X-Content-Type-Options - prevent MIME sniffing
if settings.x_content_type_options_enabled:
response.headers["X-Content-Type-Options"] = "nosniff"
# X-Download-Options - prevent IE from executing downloads
if settings.x_download_options_enabled:
response.headers["X-Download-Options"] = "noopen"
# Remove server identification headers
if settings.remove_server_headers:
response.headers.pop("X-Powered-By", None)
response.headers.pop("Server", None)
return response
2. Register Middleware
Add the middleware registration in main.py
after the existing middleware setup (around line 430):
# Add security headers middleware (after CORS middleware)
app.add_middleware(SecurityHeadersMiddleware)
3. Update Cookie Settings in admin.py
Modify the JWT cookie setting in mcpgateway/admin.py
in the admin_ui()
function (around line 1730):
# BEFORE (line ~1730):
response.set_cookie(key="jwt_token", value=jwt_token, httponly=True, secure=False, samesite="Strict") # JavaScript CAN'T read it # only over HTTPS # or "Lax" per your needs
# AFTER:
response.set_cookie(
key="jwt_token",
value=jwt_token,
httponly=True, # Prevent JavaScript access
secure=not settings.debug, # Use secure cookies in production (using existing debug setting)
samesite="lax", # CSRF protection
max_age=settings.token_expiry * 60 # Convert minutes to seconds
)
Also check for any other cookie settings in the file (search for set_cookie
in admin.py) and apply the same security attributes.
4. CSP Considerations for HTMX and Alpine.js
The Admin UI uses:
- HTMX from unpkg.com CDN
- Alpine.js from cdn.jsdelivr.net CDN
- Inline scripts for Alpine.js initialization
- Inline styles for UI components
The CSP policy above allows these while maintaining security. In future iterations:
- Consider moving inline scripts to external files
- Use `nonces`` for necessary inline scripts
- Bundle dependencies locally to remove CDN requirements
Additional CDNs Found in Implementation:
- Tailwind CSS from cdn.tailwindcss.com
- CodeMirror themes and modes from cdnjs.cloudflare.com
- Chart.js from cdn.jsdelivr.net
Files to Modify:
mcpgateway/main.py
- Add SecurityHeadersMiddleware class and register it (after line 386)mcpgateway/admin.py
- Update cookie settings with security attributes (line ~1730)mcpgateway/config.py
- Add security headers configuration to Settings class (around line 270).env.example
- Add security headers environment variables
5. Environment-Based Configuration
Add environment variables for header configuration in config.py
. Add these to the Settings class (around line 270, after the existing security settings):
# Security Headers Configuration
security_headers_enabled: bool = True
csp_enabled: bool = True
csp_report_uri: Optional[str] = None
csp_report_only: bool = False # Set True to test CSP without blocking
hsts_enabled: bool = True
hsts_max_age: int = 31536000 # 1 year in seconds
hsts_include_subdomains: bool = True
hsts_preload: bool = False
x_frame_options: str = "DENY" # DENY, SAMEORIGIN, or ALLOW-FROM uri
x_content_type_options_enabled: bool = True
x_xss_protection_enabled: bool = True
x_download_options_enabled: bool = True
remove_server_headers: bool = True # Remove X-Powered-By and Server headers
# CSP Directives Configuration (can be customized via env)
csp_default_src: List[str] = ["'self'"]
csp_script_src: List[str] = [
"'self'",
"'unsafe-inline'",
"'unsafe-eval'",
"https://unpkg.com",
"https://cdn.jsdelivr.net",
"https://cdn.tailwindcss.com",
"https://cdnjs.cloudflare.com"
]
csp_style_src: List[str] = [
"'self'",
"'unsafe-inline'",
"https://unpkg.com",
"https://cdn.jsdelivr.net",
"https://cdnjs.cloudflare.com"
]
csp_img_src: List[str] = ["'self'", "data:", "https:"]
csp_font_src: List[str] = ["'self'", "data:", "https://cdnjs.cloudflare.com"]
csp_connect_src: List[str] = ["'self'"]
csp_frame_ancestors: List[str] = ["'none'"]
@property
def csp_header_value(self) -> str:
"""Generate the Content-Security-Policy header value from configuration."""
if not self.csp_enabled:
return ""
directives = []
# Build CSP directives from configuration
if self.csp_default_src:
directives.append(f"default-src {' '.join(self.csp_default_src)}")
if self.csp_script_src:
directives.append(f"script-src {' '.join(self.csp_script_src)}")
if self.csp_style_src:
directives.append(f"style-src {' '.join(self.csp_style_src)}")
if self.csp_img_src:
directives.append(f"img-src {' '.join(self.csp_img_src)}")
if self.csp_font_src:
directives.append(f"font-src {' '.join(self.csp_font_src)}")
if self.csp_connect_src:
directives.append(f"connect-src {' '.join(self.csp_connect_src)}")
if self.csp_frame_ancestors:
directives.append(f"frame-ancestors {' '.join(self.csp_frame_ancestors)}")
# Add report-uri if configured
if self.csp_report_uri:
directives.append(f"report-uri {self.csp_report_uri}")
return "; ".join(directives) + ";"
@property
def hsts_header_value(self) -> str:
"""Generate the Strict-Transport-Security header value from configuration."""
if not self.hsts_enabled or self.debug:
return ""
value = f"max-age={self.hsts_max_age}"
if self.hsts_include_subdomains:
value += "; includeSubDomains"
if self.hsts_preload:
value += "; preload"
return value
6. Update .env.example
Add these new configuration options to your .env.example
file (add after the existing Security and CORS section):
#####################################
# Security Headers Configuration
#####################################
# Enable security headers middleware (true/false)
SECURITY_HEADERS_ENABLED=true
# Content Security Policy settings
CSP_ENABLED=true
CSP_REPORT_URI=
CSP_REPORT_ONLY=false
# HSTS (HTTP Strict Transport Security) settings
HSTS_ENABLED=true
HSTS_MAX_AGE=31536000 # 1 year in seconds
HSTS_INCLUDE_SUBDOMAINS=true
HSTS_PRELOAD=false
# X-Frame-Options setting (DENY, SAMEORIGIN, or ALLOW-FROM uri)
X_FRAME_OPTIONS=DENY
# Other security headers (true/false)
X_CONTENT_TYPE_OPTIONS_ENABLED=true
X_XSS_PROTECTION_ENABLED=true
X_DOWNLOAD_OPTIONS_ENABLED=true
REMOVE_SERVER_HEADERS=true
# CSP directive customization (JSON arrays)
# Uncomment and modify these to customize CSP directives
# CSP_DEFAULT_SRC='["self"]'
# CSP_SCRIPT_SRC='["self", "unsafe-inline", "unsafe-eval", "https://unpkg.com", "https://cdn.jsdelivr.net", "https://cdn.tailwindcss.com", "https://cdnjs.cloudflare.com"]'
# CSP_STYLE_SRC='["self", "unsafe-inline", "https://unpkg.com", "https://cdn.jsdelivr.net", "https://cdnjs.cloudflare.com"]'
# CSP_IMG_SRC='["self", "data:", "https:"]'
# CSP_FONT_SRC='["self", "data:", "https://cdnjs.cloudflare.com"]'
# CSP_CONNECT_SRC='["self"]'
# CSP_FRAME_ANCESTORS='["none"]'
Action Items:
- Add Content-Security-Policy (CSP) header with appropriate directives for HTMX/Alpine.js
- Add X-Frame-Options (XFO) header set to DENY
- Add Strict-Transport-Security (HSTS) header (production only)
- Add X-XSS-Protection header with mode=block
- Add X-Content-Type-Options header set to nosniff
- Add X-Download-Options header set to noopen
- Set cookies with HttpOnly, Secure, and SameSite attributes
- Remove X-Powered-By header
- Skip Public-Key-Pins (HPKP) - deprecated and not recommended
Testing Checklist:
- Verify Admin UI still functions with CSP policy
- Test HTMX requests work properly
- Ensure Alpine.js components initialize
- Check static assets load correctly
- Validate headers using browser dev tools
- Test with security scanning tools
- Verify Tailwind CSS loads properly
- Check CodeMirror editors function
- Ensure Chart.js visualizations work
Notes:
- Headers only apply when
MCPGATEWAY_UI_ENABLED=true
- CSP policy allows CDN resources required by Admin UI (HTMX from unpkg.com, Alpine.js from cdn.jsdelivr.net)
- HSTS only enabled in production (when
DEBUG_MODE=false
orsettings.debug_mode=False
) - Cookie security enhanced with HttpOnly, Secure (production only), and SameSite=lax
- The existing cookie in admin.py already has
httponly=True
but needssecure
to be conditional on debug mode - Consider using
fastapi-csp
package for more advanced CSP management - Future enhancement: Add CSP nonces for inline scripts
References:
Priority: Medium — While the Admin UI is intended for development use, implementing these headers:
- Establishes security best practices
- Prepares for potential production exposure
- Reduces security scanner noise
- Protects against common web vulnerabilities
Suggested Milestone: v0.4.0
as part of general security hardening