Files
Aether/crates/aether-provider/transport/src/windsurf.rs
T
elky 579f2c7cc1 feat(security): harden gateway boundaries and usage policies
Consolidate subscription usage policy enforcement, privacy-safe persistence, and gateway security hardening into one reviewable change.

Includes bounded HTTP and execution envelopes, header and protocol guards, DNS and relay validation, authentication and secret projection hardening, secure backup/install paths, and regression coverage.
2026-09-04 03:45:52 +08:00

588 lines
20 KiB
Rust

use std::collections::BTreeMap;
use serde_json::{json, Value};
use uuid::Uuid;
use super::headers::{declared_connection_header_names, remove_declared_connection_headers};
use crate::rules::{
apply_local_body_rules_with_request_headers, apply_local_header_rules_with_request_headers,
body_rules_are_locally_supported, header_rules_are_locally_supported,
};
use crate::snapshot::GatewayProviderTransportSnapshot;
use crate::url::build_passthrough_path_url;
use crate::{
resolve_transport_profile, should_skip_upstream_passthrough_header,
supports_local_oauth_request_auth_resolution, transport_profile_is_configured,
transport_proxy_is_locally_supported,
};
pub mod cascade;
pub mod models;
pub mod proto;
pub const PROVIDER_TYPE: &str = "windsurf";
pub const WINDSURF_ENVELOPE_NAME: &str = "windsurf:GetChatMessage";
pub const GET_CHAT_MESSAGE_PATH: &str = "/exa.api_server_pb.ApiServerService/GetChatMessage";
const DEFAULT_IDE_VERSION: &str = "1.9600.41";
const PLACEHOLDER_API_KEY: &str = "__placeholder__";
pub fn is_windsurf_provider_transport(transport: &GatewayProviderTransportSnapshot) -> bool {
transport
.provider
.provider_type
.trim()
.eq_ignore_ascii_case(PROVIDER_TYPE)
}
pub fn local_windsurf_request_transport_unsupported_reason_with_network(
transport: &GatewayProviderTransportSnapshot,
) -> Option<&'static str> {
if !transport.provider.is_active {
return Some("provider_inactive");
}
if !transport.endpoint.is_active {
return Some("endpoint_inactive");
}
if !transport.key.is_active {
return Some("key_inactive");
}
if !is_windsurf_provider_transport(transport) {
return Some("transport_provider_type_unsupported");
}
if !transport
.endpoint
.api_format
.trim()
.eq_ignore_ascii_case("openai:chat")
{
return Some("transport_api_format_mismatch");
}
if !header_rules_are_locally_supported(transport.endpoint.header_rules.as_ref()) {
return Some("transport_header_rules_unsupported");
}
if !body_rules_are_locally_supported(transport.endpoint.body_rules.as_ref()) {
return Some("transport_body_rules_unsupported");
}
if transport.key.decrypted_auth_config.is_some()
&& !supports_local_oauth_request_auth_resolution(transport)
&& !supports_local_windsurf_request_auth_resolution(transport)
{
return Some("transport_oauth_resolution_unsupported");
}
if !transport_proxy_is_locally_supported(transport) {
return Some("transport_proxy_unsupported");
}
if transport_profile_is_configured(transport) && resolve_transport_profile(transport).is_none()
{
return Some("transport_profile_unsupported");
}
None
}
pub fn supports_local_windsurf_request_auth_resolution(
transport: &GatewayProviderTransportSnapshot,
) -> bool {
resolve_windsurf_cascade_auth(transport).is_some()
}
pub fn resolve_windsurf_cascade_auth(
transport: &GatewayProviderTransportSnapshot,
) -> Option<(String, String)> {
if !is_windsurf_provider_transport(transport) {
return None;
}
let auth_type = transport.key.auth_type.trim().to_ascii_lowercase();
if !matches!(auth_type.as_str(), "oauth" | "api_key" | "bearer") {
return None;
}
let secret = transport.key.decrypted_api_key.trim();
if secret.is_empty() || secret == PLACEHOLDER_API_KEY {
return None;
}
Some(("authorization".to_string(), format!("Bearer {secret}")))
}
pub fn build_windsurf_cascade_upstream_url(
upstream_base_url: &str,
query: Option<&str>,
) -> Option<String> {
build_passthrough_path_url(upstream_base_url, GET_CHAT_MESSAGE_PATH, query, &[])
}
pub fn build_windsurf_cascade_request_body(
body_json: &Value,
mapped_model: &str,
auth_value: &str,
body_rules: Option<&Value>,
request_headers: Option<&http::HeaderMap>,
upstream_is_stream: bool,
) -> Option<Value> {
let mapped_model = mapped_model.trim();
if mapped_model.is_empty() {
return None;
}
let messages = body_json.get("messages")?.as_array()?.clone();
if messages.is_empty() {
return None;
}
let conversation_id =
extract_conversation_id(body_json).unwrap_or_else(|| Uuid::new_v4().to_string());
let message_text =
latest_message_snapshot_text(&messages).unwrap_or_else(|| "Continue.".to_string());
let mut provider_request_body = json!({
"metadata": windsurf_metadata_from_auth(auth_value),
"model": mapped_model,
"modelName": mapped_model,
"stream": upstream_is_stream,
"conversationId": conversation_id,
"message": message_text,
"messages": messages,
});
if let Some(max_tokens) = body_json
.get("max_tokens")
.or_else(|| body_json.get("maxTokens"))
{
provider_request_body
.as_object_mut()?
.insert("maxTokens".to_string(), max_tokens.clone());
}
if let Some(temperature) = body_json.get("temperature") {
provider_request_body
.as_object_mut()?
.insert("temperature".to_string(), temperature.clone());
}
if let Some(top_p) = body_json.get("top_p").or_else(|| body_json.get("topP")) {
provider_request_body
.as_object_mut()?
.insert("topP".to_string(), top_p.clone());
}
for field in [
"tools",
"tool_choice",
"toolChoice",
"parallel_tool_calls",
"response_format",
] {
if let Some(value) = body_json.get(field) {
provider_request_body
.as_object_mut()?
.insert(field.to_string(), value.clone());
}
}
if !apply_local_body_rules_with_request_headers(
&mut provider_request_body,
body_rules,
Some(body_json),
request_headers,
) {
return None;
}
Some(provider_request_body)
}
pub fn build_windsurf_cascade_headers(
headers: &http::HeaderMap,
provider_request_body: &Value,
original_request_body: &Value,
header_rules: Option<&Value>,
auth_header: &str,
auth_value: &str,
_upstream_is_stream: bool,
) -> Option<BTreeMap<String, String>> {
let declared_connection_headers = declared_connection_header_names(headers, &BTreeMap::new());
let mut out = BTreeMap::new();
for (name, value) in headers {
let Ok(value) = value.to_str() else {
continue;
};
let key = name.as_str().to_ascii_lowercase();
if should_skip_upstream_passthrough_header(&key)
|| declared_connection_headers.contains(&key)
{
continue;
}
let value = value.trim();
if !value.is_empty() {
out.insert(key, value.to_string());
}
}
let auth_header = auth_header.trim().to_ascii_lowercase();
if !apply_local_header_rules_with_request_headers(
&mut out,
header_rules,
&[
auth_header.as_str(),
"content-type",
"connect-protocol-version",
],
provider_request_body,
Some(original_request_body),
Some(headers),
) {
return None;
}
out.insert(
"content-type".to_string(),
"application/connect+json".to_string(),
);
out.insert("connect-protocol-version".to_string(), "1".to_string());
out.insert(
"user-agent".to_string(),
format!("windsurf/{DEFAULT_IDE_VERSION}"),
);
out.insert("accept".to_string(), "application/connect+json".to_string());
if !auth_header.is_empty() {
out.insert(auth_header, auth_value.trim().to_string());
}
remove_declared_connection_headers(&mut out, &declared_connection_headers);
out.remove("content-length");
Some(out)
}
fn windsurf_metadata_from_auth(auth_value: &str) -> Value {
json!({
"apiKey": auth_secret_from_header_value(auth_value),
"ideName": "windsurf",
"ideVersion": DEFAULT_IDE_VERSION,
"extensionName": "windsurf",
"extensionVersion": DEFAULT_IDE_VERSION,
"locale": "en",
})
}
fn auth_secret_from_header_value(auth_value: &str) -> String {
let value = auth_value.trim();
value
.strip_prefix("Bearer ")
.or_else(|| value.strip_prefix("bearer "))
.unwrap_or(value)
.trim()
.to_string()
}
fn extract_conversation_id(body_json: &Value) -> Option<String> {
let object = body_json.as_object()?;
string_value(object.get("conversation_id"))
.or_else(|| string_value(object.get("conversationId")))
.or_else(|| string_value(object.get("session_id")))
.or_else(|| string_value(object.get("sessionId")))
.or_else(|| {
object
.get("metadata")
.and_then(Value::as_object)
.and_then(|metadata| {
string_value(metadata.get("conversation_id"))
.or_else(|| string_value(metadata.get("conversationId")))
.or_else(|| string_value(metadata.get("session_id")))
.or_else(|| string_value(metadata.get("sessionId")))
})
})
}
fn string_value(value: Option<&Value>) -> Option<String> {
value
.and_then(Value::as_str)
.map(str::trim)
.filter(|value| !value.is_empty())
.map(ToOwned::to_owned)
}
fn latest_message_snapshot_text(messages: &[Value]) -> Option<String> {
messages
.iter()
.rev()
.filter_map(Value::as_object)
.find_map(|message| {
let role = message.get("role").and_then(Value::as_str)?;
match role {
"user" => openai_content_to_text(message.get("content"))
.map(|value| value.trim().to_string())
.filter(|value| !value.is_empty()),
"tool" => {
let content = openai_content_to_text(message.get("content"))
.map(|value| value.trim().to_string())
.filter(|value| !value.is_empty())?;
let tool_call_id = message
.get("tool_call_id")
.and_then(Value::as_str)
.unwrap_or("unknown");
Some(format!(
"<tool_result tool_call_id=\"{}\">\n{content}\n</tool_result>",
escape_xml_attr(tool_call_id)
))
}
"assistant" => openai_content_to_text(message.get("content"))
.map(|value| value.trim().to_string())
.filter(|value| !value.is_empty()),
_ => None,
}
})
}
fn openai_content_to_text(value: Option<&Value>) -> Option<String> {
match value? {
Value::String(text) => Some(text.clone()),
Value::Array(items) => {
let parts = items
.iter()
.filter_map(|item| {
item.as_object()
.and_then(|object| object.get("text"))
.and_then(Value::as_str)
})
.map(str::trim)
.filter(|value| !value.is_empty())
.collect::<Vec<_>>();
(!parts.is_empty()).then(|| parts.join("\n"))
}
_ => None,
}
}
fn escape_xml_attr(value: &str) -> String {
value
.replace('&', "&amp;")
.replace('"', "&quot;")
.replace('<', "&lt;")
.replace('>', "&gt;")
}
#[cfg(test)]
mod tests {
use http::HeaderMap;
use serde_json::json;
use crate::snapshot::{
GatewayProviderTransportEndpoint, GatewayProviderTransportKey,
GatewayProviderTransportProvider, GatewayProviderTransportSnapshot,
};
use super::{
build_windsurf_cascade_headers, build_windsurf_cascade_request_body,
build_windsurf_cascade_upstream_url,
local_windsurf_request_transport_unsupported_reason_with_network,
resolve_windsurf_cascade_auth, GET_CHAT_MESSAGE_PATH,
};
fn sample_windsurf_transport(auth_type: &str) -> GatewayProviderTransportSnapshot {
GatewayProviderTransportSnapshot {
provider: GatewayProviderTransportProvider {
id: "provider-windsurf".to_string(),
name: "Windsurf".to_string(),
provider_type: "windsurf".to_string(),
website: None,
is_active: true,
keep_priority_on_conversion: false,
enable_format_conversion: true,
concurrent_limit: None,
max_retries: None,
proxy: None,
request_timeout_secs: None,
stream_first_byte_timeout_secs: None,
config: None,
},
endpoint: GatewayProviderTransportEndpoint {
id: "endpoint-windsurf-chat".to_string(),
provider_id: "provider-windsurf".to_string(),
api_format: "openai:chat".to_string(),
api_family: Some("openai".to_string()),
endpoint_kind: Some("chat".to_string()),
is_active: true,
base_url: "https://server.codeium.com".to_string(),
header_rules: None,
body_rules: None,
max_retries: None,
custom_path: None,
config: None,
format_acceptance_config: None,
proxy: None,
},
key: GatewayProviderTransportKey {
id: "key-windsurf".to_string(),
provider_id: "provider-windsurf".to_string(),
name: "[email protected]".to_string(),
auth_type: auth_type.to_string(),
is_active: true,
api_formats: None,
auth_type_by_format: None,
allow_auth_channel_mismatch_formats: None,
allowed_models: None,
capabilities: None,
rate_multipliers: None,
global_priority_by_format: None,
expires_at_unix_secs: None,
proxy: None,
fingerprint: None,
decrypted_api_key: "devin-session-token$abc".to_string(),
decrypted_auth_config: Some(r#"{"provider_type":"windsurf"}"#.to_string()),
upstream_metadata: None,
},
}
}
#[test]
fn builds_windsurf_cascade_url() {
assert_eq!(
build_windsurf_cascade_upstream_url("https://server.codeium.com", Some("debug=1"))
.as_deref(),
Some(
"https://server.codeium.com/exa.api_server_pb.ApiServerService/GetChatMessage?debug=1"
)
);
assert!(GET_CHAT_MESSAGE_PATH.ends_with("/GetChatMessage"));
}
#[test]
fn builds_cascade_request_body_with_metadata_and_messages() {
let body = build_windsurf_cascade_request_body(
&json!({
"model": "gpt-5",
"conversation_id": "conv-1",
"messages": [
{"role": "system", "content": "brief"},
{"role": "user", "content": [{"type": "text", "text": "hello"}]}
],
"max_tokens": 128
}),
"windsurf-model",
"Bearer devin-session-token$abc",
None,
None,
true,
)
.expect("body should build");
assert_eq!(body["metadata"]["apiKey"], json!("devin-session-token$abc"));
assert_eq!(body["modelName"], json!("windsurf-model"));
assert_eq!(body["stream"], json!(true));
assert_eq!(body["conversationId"], json!("conv-1"));
assert_eq!(body["message"], json!("hello"));
assert_eq!(body["maxTokens"], json!(128));
}
#[test]
fn preserves_openai_tool_fields_for_native_windsurf_runtime() {
let body = build_windsurf_cascade_request_body(
&json!({
"model": "gpt-5-5-low",
"messages": [
{"role": "user", "content": "read Cargo.toml"}
],
"tools": [{
"type": "function",
"function": {
"name": "Read",
"parameters": {
"type": "object",
"properties": {"file_path": {"type": "string"}}
}
}
}],
"tool_choice": "required",
"parallel_tool_calls": false,
"response_format": {"type": "json_object"}
}),
"gpt-5-5-low",
"Bearer devin-session-token$abc",
None,
None,
false,
)
.expect("body should build");
assert_eq!(body["tools"][0]["function"]["name"], json!("Read"));
assert_eq!(body["tool_choice"], json!("required"));
assert_eq!(body["parallel_tool_calls"], json!(false));
assert_eq!(body["response_format"]["type"], json!("json_object"));
}
#[test]
fn builds_cascade_request_body_message_snapshot_from_latest_tool_result() {
let body = build_windsurf_cascade_request_body(
&json!({
"model": "gpt-5-5-low",
"messages": [
{"role": "user", "content": "read Cargo.toml"},
{
"role": "assistant",
"content": null,
"tool_calls": [{
"id": "call_1",
"type": "function",
"function": {"name": "Read", "arguments": "{\"file_path\":\"Cargo.toml\"}"}
}]
},
{"role": "tool", "tool_call_id": "call_1", "content": "workspace Cargo.toml content"}
]
}),
"gpt-5-5-low",
"Bearer devin-session-token$abc",
None,
None,
false,
)
.expect("body should build");
assert!(body["message"]
.as_str()
.expect("message should be a string")
.contains(r#"<tool_result tool_call_id="call_1">"#));
assert!(body["message"]
.as_str()
.expect("message should be a string")
.contains("workspace Cargo.toml content"));
}
#[test]
fn builds_cascade_headers_with_connect_protocol_and_auth() {
let headers = build_windsurf_cascade_headers(
&HeaderMap::new(),
&json!({"metadata": {"apiKey": "secret"}}),
&json!({"messages": []}),
None,
"authorization",
"Bearer secret",
false,
)
.expect("headers should build");
assert_eq!(
headers.get("connect-protocol-version").map(String::as_str),
Some("1")
);
assert_eq!(
headers.get("authorization").map(String::as_str),
Some("Bearer secret")
);
assert_eq!(
headers.get("content-type").map(String::as_str),
Some("application/connect+json")
);
assert_eq!(
headers.get("accept").map(String::as_str),
Some("application/connect+json")
);
}
#[test]
fn oauth_windsurf_transport_resolves_direct_bearer_auth() {
let transport = sample_windsurf_transport("oauth");
assert_eq!(
local_windsurf_request_transport_unsupported_reason_with_network(&transport),
None
);
assert_eq!(
resolve_windsurf_cascade_auth(&transport),
Some((
"authorization".to_string(),
"Bearer devin-session-token$abc".to_string()
))
);
}
}