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}