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.
This commit is contained in:
elky
2026-09-04 03:45:52 +08:00
parent ddcbeb3ae9
commit 579f2c7cc1
1019 changed files with 190437 additions and 26080 deletions
@@ -1,16 +1,27 @@
use super::super::snapshot::GatewayProviderTransportSnapshot;
use super::credentials::{generate_machine_id, KiroAuthConfig};
use std::fmt;
pub const PROVIDER_TYPE: &str = "kiro";
pub const KIRO_AUTH_HEADER: &str = "authorization";
#[derive(Debug, Clone, PartialEq, Eq)]
#[derive(Clone, PartialEq, Eq)]
pub struct KiroBearerAuth {
pub name: &'static str,
pub value: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
impl fmt::Debug for KiroBearerAuth {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("KiroBearerAuth")
.field("name", &self.name)
.field("value", &"[REDACTED]")
.finish()
}
}
#[derive(Clone, PartialEq, Eq)]
pub struct KiroRequestAuth {
pub name: &'static str,
pub value: String,
@@ -18,6 +29,18 @@ pub struct KiroRequestAuth {
pub machine_id: String,
}
impl fmt::Debug for KiroRequestAuth {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("KiroRequestAuth")
.field("name", &self.name)
.field("value", &"[REDACTED]")
.field("auth_config", &self.auth_config)
.field("machine_id", &"[REDACTED]")
.finish()
}
}
pub fn is_kiro_provider_transport(transport: &GatewayProviderTransportSnapshot) -> bool {
transport
.provider
@@ -224,6 +247,41 @@ mod tests {
assert!(supports_local_kiro_auth_prerequisites(&sample_transport()));
}
#[test]
fn kiro_request_auth_debug_output_redacts_credentials_and_machine_identity() {
let bearer = resolve_local_kiro_bearer_auth(&sample_transport())
.expect("kiro bearer auth should resolve");
let bearer_debug = format!("{bearer:?}");
assert!(!bearer_debug.contains("upstream-key"));
assert!(bearer_debug.contains("[REDACTED]"));
let mut transport = sample_transport();
transport.key.decrypted_api_key = "__placeholder__".to_string();
transport.key.decrypted_auth_config = Some(
r#"{
"access_token":"kiro-request-access-canary",
"expires_at":4102444800,
"refresh_token":"kiro-request-refresh-canary................................................................................................",
"machine_id":"kiro-request-machine-canary",
"profile_arn":"kiro-request-profile-canary",
"api_region":"us-west-2"
}"#
.to_string(),
);
let request_auth =
resolve_local_kiro_request_auth(&transport).expect("kiro request auth should resolve");
let request_debug = format!("{request_auth:?}");
for secret in [
"kiro-request-access-canary",
"kiro-request-refresh-canary",
"kiro-request-machine-canary",
"kiro-request-profile-canary",
] {
assert!(!request_debug.contains(secret), "debug leaked {secret}");
}
assert!(request_debug.contains("[REDACTED]"));
}
#[test]
fn rejects_auth_config_subset() {
let mut transport = sample_transport();
@@ -2,6 +2,7 @@ pub use aether_oauth::provider::providers::{
generate_kiro_machine_id as generate_machine_id,
normalize_kiro_machine_id as normalize_machine_id, KiroAuthConfig, DEFAULT_REGION,
};
pub use aether_oauth::provider::providers::{is_valid_kiro_region, normalize_kiro_region};
#[cfg(test)]
mod tests {
@@ -2,7 +2,7 @@ use std::collections::BTreeMap;
use uuid::Uuid;
use super::credentials::KiroAuthConfig;
use super::credentials::{normalize_kiro_region, KiroAuthConfig};
pub const AWS_EVENTSTREAM_CONTENT_TYPE: &str = "application/vnd.amazon.eventstream";
pub const KIRO_PROFILE_ARN_HEADER: &str = "x-amzn-kiro-profile-arn";
@@ -47,7 +47,7 @@ pub fn build_generate_assistant_headers(
let kiro_version = auth_config.effective_kiro_version();
let system_version = auth_config.effective_system_version();
let node_version = auth_config.effective_node_version();
let region = auth_config.effective_api_region();
let region = normalize_kiro_region(auth_config.effective_api_region());
let host = format!("q.{region}.amazonaws.com");
BTreeMap::from([
@@ -92,7 +92,7 @@ pub fn build_mcp_headers(
let kiro_version = auth_config.effective_kiro_version();
let system_version = auth_config.effective_system_version();
let node_version = auth_config.effective_node_version();
let region = auth_config.effective_api_region();
let region = normalize_kiro_region(auth_config.effective_api_region());
let host = format!("q.{region}.amazonaws.com");
let mut headers = BTreeMap::from([
@@ -134,7 +134,7 @@ pub fn build_list_available_models_headers(
let kiro_version = auth_config.effective_kiro_version();
let system_version = auth_config.effective_system_version();
let node_version = auth_config.effective_node_version();
let region = auth_config.effective_api_region();
let region = normalize_kiro_region(auth_config.effective_api_region());
let host = format!("q.{region}.amazonaws.com");
let ide_tag = build_kiro_ide_tag(kiro_version, machine_id);
@@ -331,4 +331,30 @@ mod tests {
);
assert!(!headers.contains_key(KIRO_TOKEN_TYPE_HEADER));
}
#[test]
fn malicious_api_region_cannot_inject_host_header() {
let auth_config = KiroAuthConfig {
auth_method: Some("social".to_string()),
refresh_token: None,
expires_at: None,
profile_arn: None,
region: None,
auth_region: None,
api_region: Some("attacker.example/".to_string()),
client_id: None,
client_secret: None,
machine_id: None,
kiro_version: None,
system_version: None,
node_version: None,
access_token: None,
};
let headers = build_list_available_models_headers(&auth_config, "machine");
assert_eq!(
headers.get("host").map(String::as_str),
Some("q.us-east-1.amazonaws.com")
);
}
}
@@ -30,7 +30,10 @@ pub use auth::{
KiroBearerAuth, KiroRequestAuth, KIRO_AUTH_HEADER, PROVIDER_TYPE,
};
pub use converter::convert_claude_messages_to_conversation_state;
pub use credentials::{generate_machine_id, normalize_machine_id, KiroAuthConfig};
pub use credentials::{
generate_machine_id, is_valid_kiro_region, normalize_kiro_region, normalize_machine_id,
KiroAuthConfig,
};
pub use headers::{
build_generate_assistant_headers, build_list_available_models_headers, build_mcp_headers,
AWS_EVENTSTREAM_CONTENT_TYPE, KIRO_EXTERNAL_IDP_TOKEN_TYPE, KIRO_PROFILE_ARN_HEADER,
@@ -1,7 +1,9 @@
use std::collections::BTreeMap;
use std::fmt;
use serde_json::{json, Value};
use super::super::headers::{declared_connection_header_names, remove_declared_connection_headers};
pub use super::super::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,
@@ -83,7 +85,7 @@ pub fn build_kiro_provider_request_body(
Some(provider_request_body)
}
#[derive(Clone, Copy, Debug)]
#[derive(Clone, Copy)]
pub struct KiroProviderHeadersInput<'a> {
pub headers: &'a http::HeaderMap,
pub provider_request_body: &'a Value,
@@ -95,6 +97,39 @@ pub struct KiroProviderHeadersInput<'a> {
pub machine_id: &'a str,
}
impl fmt::Debug for KiroProviderHeadersInput<'_> {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("KiroProviderHeadersInput")
.field(
"request_header_names",
&self
.headers
.keys()
.map(|name| name.as_str())
.collect::<Vec<_>>(),
)
.field(
"provider_request_body_bytes",
&serde_json::to_vec(self.provider_request_body)
.ok()
.map(|bytes| bytes.len()),
)
.field(
"original_request_body_bytes",
&serde_json::to_vec(self.original_request_body)
.ok()
.map(|bytes| bytes.len()),
)
.field("has_header_rules", &self.header_rules.is_some())
.field("auth_header", &self.auth_header)
.field("has_auth_value", &(!self.auth_value.is_empty()))
.field("auth_config", &self.auth_config)
.field("has_machine_id", &(!self.machine_id.is_empty()))
.finish()
}
}
pub fn build_kiro_provider_headers(
input: KiroProviderHeadersInput<'_>,
) -> Option<BTreeMap<String, String>> {
@@ -109,13 +144,16 @@ pub fn build_kiro_provider_headers(
machine_id,
} = input;
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) {
if should_skip_upstream_passthrough_header(&key)
|| declared_connection_headers.contains(&key)
{
continue;
}
let value = value.trim();
@@ -143,8 +181,10 @@ pub fn build_kiro_provider_headers(
auth_header.trim().to_ascii_lowercase(),
auth_value.trim().to_string(),
);
remove_declared_connection_headers(&mut out, &declared_connection_headers);
out.entry("content-type".to_string())
.or_insert_with(|| "application/json".to_string());
remove_declared_connection_headers(&mut out, &declared_connection_headers);
out.remove("content-length");
Some(out)
}
@@ -1,5 +1,5 @@
use super::super::url::build_passthrough_path_url;
use super::credentials::DEFAULT_REGION;
use super::credentials::{normalize_kiro_region, DEFAULT_REGION};
pub const GENERATE_ASSISTANT_RESPONSE_PATH: &str = "/generateAssistantResponse";
pub const LIST_AVAILABLE_MODELS_PATH: &str = "/ListAvailableModels";
@@ -9,8 +9,7 @@ pub const KIRO_ENVELOPE_NAME: &str = "kiro:generateAssistantResponse";
pub fn resolve_kiro_base_url(upstream_base_url: &str, api_region: Option<&str>) -> String {
let region = api_region
.map(str::trim)
.filter(|value| !value.is_empty())
.map(normalize_kiro_region)
.unwrap_or(DEFAULT_REGION);
upstream_base_url
.trim()
@@ -105,6 +104,29 @@ mod tests {
);
}
#[test]
fn malicious_region_cannot_escape_configured_origin() {
for region in [
"attacker.example/",
"attacker.example?x=1",
"attacker.example#fragment",
"attacker@example",
] {
assert_eq!(
resolve_kiro_base_url("https://q.{region}.amazonaws.com", Some(region)),
"https://q.us-east-1.amazonaws.com"
);
}
assert_eq!(
build_kiro_list_available_models_url(
"https://q.{region}.amazonaws.com",
Some("attacker.example/"),
)
.as_deref(),
Some("https://q.us-east-1.amazonaws.com/ListAvailableModels?origin=AI_EDITOR")
);
}
#[test]
fn builds_mcp_url_for_latest_kiro_endpoint() {
assert_eq!(