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
27/// use turbomcp_server::ServerBuilder;
28///
29/// let server = ServerBuilder::new()
30/// .name("MyServer")
31/// .with_auth(auth_provider) // ✅ Required for 0.0.0.0
32/// .build();
33/// ```
34///
35/// 2. **Use Specific Interface** (Better):
36/// ```bash
37/// # Bind to specific private IP
38/// server.run_http("10.0.1.5:8080").await?;
39/// ```
40///
41/// 3. **Use Reverse Proxy** (Best):
42/// ```bash
43/// # Bind to localhost, expose via nginx/traefik
44/// server.run_http("127.0.0.1:8080").await?;
45/// ```
46///
47/// ### Why 0.0.0.0 is Risky
48///
49/// - Exposes on **ALL** network interfaces (eth0, wlan0, docker0, etc.)
50/// - Accessible from any network the host is connected to
51/// - Docker containers can access if not firewalled
52/// - Vulnerable to network-level attacks if firewall misconfigured
53///
54/// ### When 0.0.0.0 is Acceptable
55///
56/// - Local development with authentication enabled
57/// - Behind a firewall or in isolated network
58/// - Using a reverse proxy for TLS termination
59///
60/// ## Example
61///
62/// ```rust,ignore
63/// use turbomcp_server::security_checks::check_binding_security;
64///
65/// // Safe: localhost binding
66/// check_binding_security("127.0.0.1:8080", true); // No warning
67///
68/// // Warning: all interfaces with auth
69/// check_binding_security("0.0.0.0:8080", true); // WARN log
70///
71/// // Error: all interfaces without auth
72/// check_binding_security("0.0.0.0:8080", false); // ERROR log
73/// ```
74pub fn check_binding_security<A: ToSocketAddrs + std::fmt::Debug>(addr: &A) {
75 // Try to resolve the address to check if it's 0.0.0.0
76 let addr_str = format!("{:?}", addr);
77
78 // Check if the address string contains 0.0.0.0
79 let is_all_interfaces = addr_str.contains("0.0.0.0") || addr_str.contains("[::]");
80
81 if !is_all_interfaces {
82 // Safe binding (localhost or specific interface)
83 return;
84 }
85
86 // Binding to all interfaces - log security warning
87 warn!(
88 "🔒 SECURITY NOTICE: Binding to all interfaces (0.0.0.0)\n\
89 \n\
90 ⚠️ Binding to 0.0.0.0 exposes your server on ALL network interfaces.\n\
91 \n\
92 Security Checklist:\n\
93 ✓ Is authentication enabled? (recommended for 0.0.0.0)\n\
94 ✓ Is this behind a reverse proxy? (nginx, traefik, cloudflare)\n\
95 ✓ Is firewall configured? (only allow intended sources)\n\
96 ✓ Is TLS/HTTPS enabled? (required for production)\n\
97 \n\
98 Best Practices:\n\
99 - Production: Bind to specific interface (10.0.1.5:8080)\n\
100 - Development: Bind to localhost (127.0.0.1:8080)\n\
101 - Cloud: Use reverse proxy for TLS termination\n\
102 \n\
103 Current binding: {:?}\n\
104 \n\
105 See: OWASP Top 10 - Broken Access Control (A01:2021)\n\
106 See: CWE-284 - Improper Access Control",
107 addr_str
108 );
109}
110
111#[cfg(test)]
112mod tests {
113 use super::*;
114
115 #[test]
116 fn test_localhost_binding_no_warning() {
117 // These should not trigger warnings (captured via tracing in actual tests)
118 check_binding_security(&"127.0.0.1:8080");
119 check_binding_security(&"localhost:8080");
120 }
121
122 #[test]
123 fn test_all_interfaces_warning() {
124 // This should trigger WARN (captured via tracing in actual tests)
125 check_binding_security(&"0.0.0.0:8080");
126 }
127
128 #[test]
129 fn test_ipv6_all_interfaces() {
130 // This should trigger warnings for IPv6 all-interfaces binding
131 check_binding_security(&"[::]:8080");
132 }
133}