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}