turbomcp_server/security_checks.rs
1//! Runtime Security Checks (Sprint 2.6)
2//!
3//! This module provides runtime security validation for server configuration.
4//!
5//! ## Security Checks
6//!
7//! - **0.0.0.0 Binding Safety**: Warns when binding to all interfaces without authentication
8
9use std::net::ToSocketAddrs;
10use tracing::warn;
11
12/// Check if binding address is 0.0.0.0 (all interfaces)
13///
14/// Binding to 0.0.0.0 exposes the server on all network interfaces, which can be
15/// a security risk if authentication is not enabled.
16///
17/// This function:
18/// - **Logs WARN** if binding to 0.0.0.0 (all interfaces)
19/// - **Silent** for localhost bindings (127.0.0.1, ::1) - safe by default
20///
21/// ## Security Guidance
22///
23/// ### Production Deployment (NEVER use 0.0.0.0 without these)
24///
25/// 1. **Enable Authentication**:
26/// ```rust,ignore
27/// use turbomcp_server::ServerBuilder;
28/// use turbomcp_server::middleware::{MiddlewareStack, AuthConfig};
29/// use secrecy::Secret;
30/// use jsonwebtoken::Algorithm;
31///
32/// // Configure middleware with authentication
33/// let auth_config = AuthConfig {
34/// secret: Secret::new("your-secret-key".to_string()),
35/// algorithm: Algorithm::HS256,
36/// issuer: None,
37/// audience: None,
38/// leeway: 60,
39/// validate_exp: true,
40/// validate_nbf: true,
41/// };
42/// let middleware = MiddlewareStack::new()
43/// .with_auth(auth_config); // ✅ Required for 0.0.0.0
44///
45/// let server = ServerBuilder::new()
46/// .name("MyServer")
47/// .build();
48/// ```
49///
50/// 2. **Use Specific Interface** (Better):
51/// ```bash
52/// # Bind to specific private IP
53/// server.run_http("10.0.1.5:8080").await?;
54/// ```
55///
56/// 3. **Use Reverse Proxy** (Best):
57/// ```bash
58/// # Bind to localhost, expose via nginx/traefik
59/// server.run_http("127.0.0.1:8080").await?;
60/// ```
61///
62/// ### Why 0.0.0.0 is Risky
63///
64/// - Exposes on **ALL** network interfaces (eth0, wlan0, docker0, etc.)
65/// - Accessible from any network the host is connected to
66/// - Docker containers can access if not firewalled
67/// - Vulnerable to network-level attacks if firewall misconfigured
68///
69/// ### When 0.0.0.0 is Acceptable
70///
71/// - Local development with authentication enabled
72/// - Behind a firewall or in isolated network
73/// - Using a reverse proxy for TLS termination
74///
75/// ## Example
76///
77/// ```rust,ignore
78/// use turbomcp_server::security_checks::check_binding_security;
79///
80/// // Safe: localhost binding
81/// check_binding_security("127.0.0.1:8080", true); // No warning
82///
83/// // Warning: all interfaces with auth
84/// check_binding_security("0.0.0.0:8080", true); // WARN log
85///
86/// // Error: all interfaces without auth
87/// check_binding_security("0.0.0.0:8080", false); // ERROR log
88/// ```
89pub fn check_binding_security<A: ToSocketAddrs + std::fmt::Debug>(addr: &A) {
90 // Try to resolve the address to check if it's 0.0.0.0
91 let addr_str = format!("{:?}", addr);
92
93 // Check if the address string contains 0.0.0.0
94 let is_all_interfaces = addr_str.contains("0.0.0.0") || addr_str.contains("[::]");
95
96 if !is_all_interfaces {
97 // Safe binding (localhost or specific interface)
98 return;
99 }
100
101 // Binding to all interfaces - log security warning
102 warn!(
103 "🔒 SECURITY NOTICE: Binding to all interfaces (0.0.0.0)\n\
104 \n\
105 ⚠️ Binding to 0.0.0.0 exposes your server on ALL network interfaces.\n\
106 \n\
107 Security Checklist:\n\
108 ✓ Is authentication enabled? (recommended for 0.0.0.0)\n\
109 ✓ Is this behind a reverse proxy? (nginx, traefik, cloudflare)\n\
110 ✓ Is firewall configured? (only allow intended sources)\n\
111 ✓ Is TLS/HTTPS enabled? (required for production)\n\
112 \n\
113 Best Practices:\n\
114 - Production: Bind to specific interface (10.0.1.5:8080)\n\
115 - Development: Bind to localhost (127.0.0.1:8080)\n\
116 - Cloud: Use reverse proxy for TLS termination\n\
117 \n\
118 Current binding: {:?}\n\
119 \n\
120 See: OWASP Top 10 - Broken Access Control (A01:2021)\n\
121 See: CWE-284 - Improper Access Control",
122 addr_str
123 );
124}
125
126#[cfg(test)]
127mod tests {
128 use super::*;
129
130 #[test]
131 fn test_localhost_binding_no_warning() {
132 // These should not trigger warnings (captured via tracing in actual tests)
133 check_binding_security(&"127.0.0.1:8080");
134 check_binding_security(&"localhost:8080");
135 }
136
137 #[test]
138 fn test_all_interfaces_warning() {
139 // This should trigger WARN (captured via tracing in actual tests)
140 check_binding_security(&"0.0.0.0:8080");
141 }
142
143 #[test]
144 fn test_ipv6_all_interfaces() {
145 // This should trigger warnings for IPv6 all-interfaces binding
146 check_binding_security(&"[::]:8080");
147 }
148}