vellaveto_http_proxy/proxy/mod.rs
1// Copyright 2026 Paolo Vella
2// SPDX-License-Identifier: BUSL-1.1
3//
4// Use of this software is governed by the Business Source License
5// included in the LICENSE-BSL-1.1 file at the root of this repository.
6//
7// Change Date: Three years from the date of publication of this version.
8// Change License: MPL-2.0
9
10//! MCP Streamable HTTP reverse proxy.
11//!
12//! Implements the Streamable HTTP transport (MCP spec 2025-11-25) as a
13//! reverse proxy that intercepts tool calls, evaluates policies, and
14//! forwards allowed requests to an upstream MCP server.
15
16mod auth;
17pub mod call_chain;
18pub mod discovery;
19mod fallback;
20pub mod gateway;
21#[cfg(feature = "grpc")]
22pub mod grpc;
23mod handlers;
24mod helpers;
25mod inspection;
26pub mod origin;
27pub mod smart_fallback;
28#[cfg(test)]
29mod tests;
30pub mod trace_propagation;
31pub mod transport_health;
32mod upstream;
33pub mod websocket;
34
35pub use call_chain::PrivilegeEscalationCheck;
36pub use discovery::handle_transport_discovery;
37pub use handlers::{
38 handle_mcp_delete, handle_mcp_get, handle_mcp_post, handle_protected_resource_metadata,
39};
40pub use websocket::{handle_ws_upgrade, WebSocketConfig};
41
42use hmac::Hmac;
43use sha2::Sha256;
44use std::net::SocketAddr;
45use std::sync::Arc;
46use vellaveto_approval::ApprovalStore;
47use vellaveto_audit::AuditLogger;
48use vellaveto_config::ManifestConfig;
49use vellaveto_engine::PolicyEngine;
50use vellaveto_engine::{circuit_breaker::CircuitBreakerManager, deputy::DeputyValidator};
51use vellaveto_mcp::extension_registry::ExtensionRegistry;
52use vellaveto_mcp::inspection::InjectionScanner;
53use vellaveto_mcp::output_validation::OutputSchemaRegistry;
54use vellaveto_mcp::{
55 auth_level::AuthLevelTracker, sampling_detector::SamplingDetector,
56 schema_poisoning::SchemaLineageTracker, shadow_agent::ShadowAgentDetector,
57};
58use vellaveto_types::Policy;
59
60use crate::oauth::OAuthValidator;
61use crate::session::SessionStore;
62
63/// HMAC-SHA256 type alias for call chain signing (FIND-015).
64pub type HmacSha256 = Hmac<Sha256>;
65
66/// Query parameters for POST /mcp.
67#[derive(Debug, serde::Deserialize, Default)]
68#[serde(deny_unknown_fields)]
69pub struct McpQueryParams {
70 /// When true, include evaluation trace in the response.
71 #[serde(default)]
72 pub trace: bool,
73}
74
75/// Shared state for the HTTP proxy handlers.
76#[derive(Clone)]
77pub struct ProxyState {
78 pub engine: Arc<PolicyEngine>,
79 pub policies: Arc<Vec<Policy>>,
80 pub audit: Arc<AuditLogger>,
81 pub sessions: Arc<SessionStore>,
82 pub upstream_url: String,
83 pub http_client: reqwest::Client,
84 /// OAuth 2.1 JWT validator. When `Some`, all MCP requests require a valid Bearer token.
85 pub oauth: Option<Arc<OAuthValidator>>,
86 /// Custom injection scanner. When `Some`, uses configured patterns instead of defaults.
87 pub injection_scanner: Option<Arc<InjectionScanner>>,
88 /// When true, injection scanning is completely disabled.
89 pub injection_disabled: bool,
90 /// When true, injection matches block the response instead of just logging (H4).
91 pub injection_blocking: bool,
92 /// API key for authenticating requests. None disables auth (--allow-anonymous).
93 pub api_key: Option<Arc<String>>,
94 /// Optional approval store for RequireApproval verdicts.
95 /// When set, creates pending approvals with approval_id in error response data.
96 pub approval_store: Option<Arc<ApprovalStore>>,
97 /// Optional manifest verification config. When set, tools/list responses
98 /// are verified against a pinned manifest per session.
99 pub manifest_config: Option<ManifestConfig>,
100 /// Allowed origins for CSRF / DNS rebinding protection. If non-empty,
101 /// Origin must be in the allowlist. If empty and the proxy is bound to a
102 /// loopback address, only localhost origins are accepted. If empty and
103 /// bound to a non-loopback address, falls back to same-origin check
104 /// (Origin host must match Host header).
105 /// Requests without an Origin header are always allowed (non-browser clients).
106 pub allowed_origins: Vec<String>,
107 /// The socket address the proxy is bound to. Used for automatic localhost
108 /// origin validation when `allowed_origins` is empty.
109 pub bind_addr: SocketAddr,
110 /// When true, re-serialize parsed JSON-RPC messages before forwarding to
111 /// upstream. This closes the TOCTOU gap where the proxy evaluates a parsed
112 /// representation but forwards original bytes that could differ (e.g., due to
113 /// duplicate keys or parser-specific handling). Duplicate keys are always
114 /// rejected regardless of this setting.
115 pub canonicalize: bool,
116 /// Output schema registry for structuredContent validation (MCP 2025-06-18).
117 pub output_schema_registry: Arc<OutputSchemaRegistry>,
118 /// When true, scan tool responses for secrets (DLP response scanning).
119 pub response_dlp_enabled: bool,
120 /// When true, block responses that contain detected secrets instead of just logging.
121 /// SECURITY (R18-DLP-BLOCK): Without this, DLP is log-only and secrets still reach the client.
122 pub response_dlp_blocking: bool,
123 /// Strict audit mode (FIND-CREATIVE-003): When true, audit logging failures
124 /// cause requests to be denied instead of proceeding without an audit trail.
125 /// This enforces non-repudiation guarantees — no unaudited security decisions
126 /// can occur. Default: false (backward compatible).
127 pub audit_strict_mode: bool,
128 /// Known legitimate tool names for squatting detection.
129 /// Built from DEFAULT_KNOWN_TOOLS + any config overrides.
130 pub known_tools: std::collections::HashSet<String>,
131 /// Elicitation interception configuration (MCP 2025-06-18).
132 /// Controls whether `elicitation/create` requests are allowed or blocked.
133 pub elicitation_config: vellaveto_config::ElicitationConfig,
134 /// Sampling request policy configuration.
135 /// Controls whether `sampling/createMessage` requests are allowed or blocked.
136 pub sampling_config: vellaveto_config::SamplingConfig,
137 /// Tool registry for tracking tool trust scores (P2.1).
138 /// None when tool registry is disabled.
139 pub tool_registry: Option<Arc<vellaveto_mcp::tool_registry::ToolRegistry>>,
140 /// HMAC-SHA256 key for signing and verifying X-Upstream-Agents call chain entries (FIND-015).
141 /// When `Some`, Vellaveto signs its own chain entries and verifies incoming ones.
142 /// When `None`, chain signing/verification is disabled (backward compatible).
143 pub call_chain_hmac_key: Option<[u8; 32]>,
144 /// When true, the `?trace=true` query parameter is honored and evaluation
145 /// traces are included in responses. When false (the default), trace output
146 /// is silently suppressed regardless of the client query parameter.
147 ///
148 /// SECURITY: Traces expose internal policy names, patterns, and constraint
149 /// configurations. Leaving this disabled prevents information leakage to
150 /// authenticated clients.
151 pub trace_enabled: bool,
152
153 // =========================================================================
154 // Phase 3.1 Security Managers
155 // =========================================================================
156 /// Circuit breaker for cascading failure prevention (OWASP ASI08).
157 /// When a tool fails repeatedly, the circuit opens and subsequent calls are rejected.
158 pub circuit_breaker: Option<Arc<CircuitBreakerManager>>,
159
160 /// Shadow agent detector for agent impersonation detection.
161 /// Tracks known agent fingerprints and alerts on impersonation attempts.
162 pub shadow_agent: Option<Arc<ShadowAgentDetector>>,
163
164 /// Deputy validator for confused deputy attack prevention (OWASP ASI02).
165 /// Tracks delegation chains and validates action permissions.
166 pub deputy: Option<Arc<DeputyValidator>>,
167
168 /// Schema lineage tracker for schema poisoning detection (OWASP ASI05).
169 /// Tracks tool schema changes and alerts on suspicious mutations.
170 pub schema_lineage: Option<Arc<SchemaLineageTracker>>,
171
172 /// Auth level tracker for step-up authentication.
173 /// Tracks session auth levels and enforces step-up requirements.
174 pub auth_level: Option<Arc<AuthLevelTracker>>,
175
176 /// Sampling detector for sampling attack prevention.
177 /// Tracks sampling request patterns and enforces rate limits.
178 pub sampling_detector: Option<Arc<SamplingDetector>>,
179
180 // =========================================================================
181 // Runtime Limits
182 // =========================================================================
183 /// Configurable runtime limits for memory bounds, timeouts, and chain lengths.
184 /// Provides operator control over previously hardcoded security constants.
185 pub limits: vellaveto_config::LimitsConfig,
186
187 // =========================================================================
188 // WebSocket Transport (Phase 17.1 — SEP-1288)
189 // =========================================================================
190 /// WebSocket transport configuration. When `Some`, the `/mcp/ws` endpoint
191 /// is active with the specified message size, idle timeout, and rate limit.
192 /// When `None`, WebSocket requests use default configuration.
193 pub ws_config: Option<WebSocketConfig>,
194
195 // =========================================================================
196 // Protocol Extensions (Phase 17.4)
197 // =========================================================================
198 /// Extension registry for `x-` prefixed protocol extensions.
199 /// When `Some`, extension method calls are routed to registered handlers
200 /// before falling back to upstream forwarding.
201 pub extension_registry: Option<Arc<ExtensionRegistry>>,
202
203 // =========================================================================
204 // Phase 18: Transport Discovery & Negotiation
205 // =========================================================================
206 /// Transport discovery and negotiation configuration.
207 pub transport_config: vellaveto_config::TransportConfig,
208
209 /// gRPC listen port, when gRPC transport is enabled.
210 /// Used by the discovery endpoint to advertise the gRPC endpoint.
211 pub grpc_port: Option<u16>,
212
213 // =========================================================================
214 // Phase 20: MCP Gateway Mode
215 // =========================================================================
216 /// Multi-backend gateway router. When `Some`, tool calls are routed to
217 /// different upstream MCP servers based on tool name prefix matching.
218 /// When `None`, all requests use `upstream_url` (single-server mode).
219 pub gateway: Option<Arc<gateway::GatewayRouter>>,
220
221 // =========================================================================
222 // Phase 21: Advanced Authorization (ABAC)
223 // =========================================================================
224 /// ABAC policy engine for Cedar-style permit/forbid evaluation.
225 /// When `Some`, refines Allow verdicts from the PolicyEngine.
226 /// When `None`, behavior is identical to pre-Phase 21.
227 pub abac_engine: Option<Arc<vellaveto_engine::abac::AbacEngine>>,
228 /// Least-agency tracker for permission usage monitoring.
229 /// When `Some`, records which permissions each agent actually uses.
230 pub least_agency: Option<Arc<vellaveto_engine::least_agency::LeastAgencyTracker>>,
231 /// Continuous authorization config for risk-based deny.
232 pub continuous_auth_config: Option<vellaveto_config::abac::ContinuousAuthConfig>,
233
234 // =========================================================================
235 // Phase 29: Cross-Transport Smart Fallback
236 // =========================================================================
237 /// Per-transport circuit breaker tracker. When `Some` and
238 /// `transport_config.cross_transport_fallback` is true, failed transports
239 /// trigger automatic fallback to the next transport in priority order.
240 pub transport_health: Option<Arc<transport_health::TransportHealthTracker>>,
241
242 // =========================================================================
243 // Phase 30: MCP 2025-11-25 Streamable HTTP
244 // =========================================================================
245 /// Streamable HTTP configuration for SSE resumability, strict tool name
246 /// validation, and retry directives.
247 pub streamable_http: vellaveto_config::StreamableHttpConfig,
248
249 // =========================================================================
250 // Phase 39: Agent Identity Federation
251 // =========================================================================
252 /// Federation resolver for cross-organization agent identity validation.
253 /// When `Some`, incoming JWTs are checked against federation trust anchors
254 /// before falling back to the local OAuth validator.
255 pub federation: Option<Arc<crate::federation::FederationResolver>>,
256
257 // =========================================================================
258 // Phase 34: Tool Discovery Service
259 // =========================================================================
260 /// Tool discovery engine for intent-based tool search.
261 /// When `Some`, the proxy intercepts `vv_discover` tool calls and returns
262 /// discovered tool schemas without forwarding to upstream.
263 #[cfg(feature = "discovery")]
264 pub discovery_engine: Option<std::sync::Arc<vellaveto_mcp::discovery::DiscoveryEngine>>,
265
266 // =========================================================================
267 // Phase 35.3: Model Projector
268 // =========================================================================
269 /// Model projector registry for cross-model tool schema translation.
270 /// When `Some`, tool schemas can be projected to model-specific formats.
271 #[cfg(feature = "projector")]
272 pub projector_registry: Option<std::sync::Arc<vellaveto_mcp::projector::ProjectorRegistry>>,
273}
274
275/// Per-request trust signal for forwarded-header handling.
276///
277/// FIND-R56-HTTP-002: `from_trusted_proxy` is `pub(crate)` to prevent external
278/// code from constructing a `TrustedProxyContext { from_trusted_proxy: true }`
279/// and injecting it as a spoofed trust signal. Use `is_from_trusted_proxy()`
280/// for read access from outside the crate.
281#[derive(Clone, Copy, Debug)]
282pub struct TrustedProxyContext {
283 pub(crate) from_trusted_proxy: bool,
284}
285
286impl TrustedProxyContext {
287 /// Create a new `TrustedProxyContext`.
288 pub fn new(from_trusted_proxy: bool) -> Self {
289 Self { from_trusted_proxy }
290 }
291
292 /// Returns whether the request came from a trusted reverse proxy.
293 pub fn is_from_trusted_proxy(&self) -> bool {
294 self.from_trusted_proxy
295 }
296}
297
298/// MCP Session ID header name.
299const MCP_SESSION_ID: &str = "mcp-session-id";
300
301/// MCP protocol version header (MCP 2025-06-18 spec requirement).
302const MCP_PROTOCOL_VERSION_HEADER: &str = "mcp-protocol-version";
303
304/// The protocol version value this proxy speaks.
305/// FIND-R56-HTTP-006: Renamed from `MCP_PROTOCOL_VERSION` to avoid confusion
306/// with `MCP_PROTOCOL_VERSION_HEADER` (the header name).
307const MCP_PROTOCOL_VERSION_VALUE: &str = "2025-11-25";
308
309/// Supported MCP protocol versions for incoming requests.
310/// The proxy accepts these versions for backwards compatibility.
311const SUPPORTED_PROTOCOL_VERSIONS: &[&str] = &["2025-11-25", "2025-06-18", "2025-03-26"];
312
313/// Header for client transport preference negotiation (MCP June 2026).
314/// Clients may send a comma-separated list of preferred transports.
315/// Used in request handling when transport-preference-aware routing is active.
316const MCP_TRANSPORT_PREFERENCE_HEADER: &str = "mcp-transport-preference";
317
318/// OWASP ASI08: Header for tracking upstream agents in multi-hop MCP scenarios.
319/// Contains a JSON-encoded array of CallChainEntry objects from previous hops.
320/// This header is added by Vellaveto when forwarding requests downstream
321/// and read when receiving requests from upstream.
322pub const X_UPSTREAM_AGENTS: &str = "x-upstream-agents";
323
324/// SECURITY (FIND-R41-001): Allowlist of headers forwarded to upstream.
325/// Only these headers are forwarded to prevent leaking internal/sensitive
326/// headers (e.g., authorization, cookies) to upstream backends.
327/// Shared by `fallback.rs` and `smart_fallback.rs`.
328pub(crate) const FORWARDED_HEADERS: &[&str] = &[
329 "content-type",
330 "accept",
331 "user-agent",
332 "traceparent",
333 "tracestate",
334 "x-request-id",
335];
336
337/// Maximum response body size from upstream (16 MB).
338/// SECURITY (FIND-R41-004, FIND-R42-020): Prevents unbounded memory
339/// allocation from malicious upstream responses.
340/// Shared by `fallback.rs` and `smart_fallback.rs`.
341pub(crate) const MAX_RESPONSE_BODY_BYTES: usize = 16 * 1024 * 1024;
342
343/// OWASP ASI07: Header for cryptographically attested agent identity.
344/// Contains a signed JWT with claims identifying the agent (issuer, subject, custom claims).
345/// Provides stronger identity guarantees than the simple agent_id string derived from OAuth.
346const X_AGENT_IDENTITY: &str = "x-agent-identity";