diff --git a/.env.example b/.env.example
index 3d6d65a93..2e5fa5e4c 100644
--- a/.env.example
+++ b/.env.example
@@ -111,11 +111,9 @@ ADMIN_USERNAME=admin123456
# AETHER_BARK_ALLOW_HTTP=false
# AETHER_BARK_ALLOW_PRIVATE_TARGETS=false
-# 普通 Provider 反代(包括 Provider OAuth)默认不按 DNS 地址过滤上游,兼容任意
-# Fake-IP 域名及内网 DNS。仅信任管理员配置的上游;需要防 DNS 重绑定时设为 true。
-# 开启后使用内置及系统配置的 DNS 域名白名单;隧道中继、登录 OAuth 的校验不受影响。
-# 此开关不放宽 URL 协议、字面 IP 或 TLS 证书校验;修改后需重启网关。
-# AETHER_PROVIDER_DNS_ADDRESS_FILTER_ENABLED=false
+# 普通 Provider 反代(包括 Provider OAuth)不按 DNS 地址过滤上游,兼容任意
+# Fake-IP 域名及内网 DNS。仅信任管理员配置的上游;没有严格 DNS 过滤开关。
+# URL 协议、字面 IP、TLS 证书,以及隧道中继和登录 OAuth 的校验仍保留。
# 可选 Provider OAuth 客户端。Gemini CLI 授权及刷新必须配置 client secret。
# Antigravity 默认使用内置 native-app 客户端凭据;自定义 client ID 时必须同时配置
diff --git a/apps/aether-gateway/src/execution_runtime/transport.rs b/apps/aether-gateway/src/execution_runtime/transport.rs
index 0f099fbc7..d4eb01785 100644
--- a/apps/aether-gateway/src/execution_runtime/transport.rs
+++ b/apps/aether-gateway/src/execution_runtime/transport.rs
@@ -22,8 +22,8 @@ use aether_contracts::{
};
use aether_data::repository::proxy_nodes::ProxyNodeTrafficMutation;
use aether_http::{
- apply_http_client_config, is_https_or_loopback_http_url, is_ipv4_benchmarking_fake_ip,
- is_private_or_reserved_ip, HttpClientConfig,
+ apply_http_client_config, is_https_or_loopback_http_url, is_private_or_reserved_ip,
+ HttpClientConfig,
};
use aether_runtime::{MetricKind, MetricSample};
use axum::body::Bytes;
@@ -63,8 +63,6 @@ use crate::upstream_admission::UpstreamTargetAdmissionPermit;
use crate::{AppState, GatewayError};
const HUB_RELAY_CONTENT_TYPE: &str = "application/vnd.aether.tunnel-envelope";
-pub(crate) const EXECUTION_EXTRA_TRUSTED_DNS_HOSTS_CONFIG_KEY: &str =
- aether_admin::system::EXECUTION_EXTRA_TRUSTED_DNS_HOSTS_CONFIG_KEY;
const HUB_RELAY_ERROR_HEADER: &str = "x-aether-tunnel-error";
const MAX_SAFE_REDIRECTS: usize = 10;
const MAX_UPSTREAM_ERROR_DETAIL_BYTES: usize = 2_048;
@@ -448,199 +446,6 @@ struct ExecutionSafeDnsResolver;
#[derive(Debug, Clone, Copy, Default)]
struct ExecutionSafeHyperDnsResolver;
-static PROVIDER_DNS_ADDRESS_FILTER_ENABLED: LazyLock = LazyLock::new(|| {
- std::env::var("AETHER_PROVIDER_DNS_ADDRESS_FILTER_ENABLED")
- .ok()
- .is_some_and(|value| matches_truthy_env_value(&value))
-});
-
-// Local DNS interception tools may use RFC 2544's 198.18.0.0/15 range for
-// synthetic answers. This exception is deliberately an allowlist rather
-// than a property of the address range itself: a custom provider hostname
-// must not be able to turn a local synthetic mapping into an SSRF primitive.
-// Keep this list limited to origins that Aether constructs as built-in
-// provider/model-fetch targets. In particular, do not use a
-// suffix match for ordinary hosts (for example, `evil.chatgpt.com`).
-const TRUSTED_EXECUTION_BENCHMARKING_DNS_EXACT_HOSTS: &[&str] = &[
- "aiplatform.googleapis.com",
- "antigravity.googleapis.com",
- "api.openai.com",
- "api.anthropic.com",
- "api.deepseek.com",
- "chatgpt.com",
- "cloudcode-pa.googleapis.com",
- "daily-cloudcode-pa.googleapis.com",
- "daily-cloudcode-pa.sandbox.googleapis.com",
- "dashscope.aliyuncs.com",
- "generativelanguage.googleapis.com",
- "grok.com",
- "oauth2.googleapis.com",
- "open.bigmodel.cn",
- "q.us-iso-east-1.c2s.ic.gov",
- "q.us-isob-east-1.sc2s.sgov.gov",
- "q.us-isof-east-1.csp.hci.ic.gov",
- "q.us-isof-south-1.csp.hci.ic.gov",
- "server.codeium.com",
- "www.googleapis.com",
-];
-
-const TRUSTED_EXECUTION_VERTEX_DNS_REGIONS: &[&str] = &[
- "africa-south1",
- "asia-east1",
- "asia-east2",
- "asia-northeast1",
- "asia-northeast2",
- "asia-northeast3",
- "asia-south1",
- "asia-south2",
- "asia-southeast1",
- "asia-southeast2",
- "australia-southeast1",
- "australia-southeast2",
- "europe-central2",
- "europe-north1",
- "europe-southwest1",
- "europe-west1",
- "europe-west2",
- "europe-west3",
- "europe-west4",
- "europe-west6",
- "europe-west8",
- "europe-west9",
- "europe-west10",
- "europe-west12",
- "me-central1",
- "me-central2",
- "me-west1",
- "northamerica-northeast1",
- "northamerica-northeast2",
- "southamerica-east1",
- "southamerica-west1",
- "us-central1",
- "us-east1",
- "us-east4",
- "us-east5",
- "us-south1",
- "us-west1",
- "us-west2",
- "us-west3",
- "us-west4",
-];
-
-const TRUSTED_EXECUTION_AWS_DNS_REGIONS: &[&str] = &[
- "af-south-1",
- "ap-east-1",
- "ap-northeast-1",
- "ap-northeast-2",
- "ap-northeast-3",
- "ap-south-1",
- "ap-south-2",
- "ap-southeast-1",
- "ap-southeast-2",
- "ap-southeast-3",
- "ap-southeast-4",
- "ca-central-1",
- "ca-west-1",
- "eu-central-1",
- "eu-central-2",
- "eu-north-1",
- "eu-south-1",
- "eu-south-2",
- "eu-west-1",
- "eu-west-2",
- "eu-west-3",
- "il-central-1",
- "me-central-1",
- "me-south-1",
- "mx-central-1",
- "sa-east-1",
- "us-east-1",
- "us-east-2",
- "us-gov-east-1",
- "us-gov-west-1",
- "us-west-1",
- "us-west-2",
-];
-
-static EXECUTION_EXTRA_TRUSTED_DNS_HOSTS: LazyLock>> =
- LazyLock::new(|| StdRwLock::new(BTreeSet::new()));
-
-pub(crate) fn refresh_execution_extra_trusted_dns_hosts(value: Option<&Value>) {
- let hosts = value
- .cloned()
- .and_then(|value| {
- aether_admin::system::normalize_execution_extra_trusted_dns_hosts_config_value(value)
- .ok()
- })
- .and_then(|value| {
- value.as_array().map(|hosts| {
- hosts
- .iter()
- .filter_map(Value::as_str)
- .map(ToOwned::to_owned)
- .collect::>()
- })
- })
- .unwrap_or_default();
-
- if let Ok(mut current) = EXECUTION_EXTRA_TRUSTED_DNS_HOSTS.write() {
- *current = hosts;
- }
-}
-
-/// Return whether `host` is one of the fixed provider origins for which a
-/// local RFC-2544 synthetic answer can be accepted. The resolver receives only
-/// a hostname (not the URL scheme/path), so all policy that can be expressed
-/// here is intentionally host based. URL validation still requires HTTPS for
-/// non-loopback upstreams before this resolver is used.
-fn execution_host_allows_benchmarking_dns_answer(host: &str) -> bool {
- let extra_hosts = EXECUTION_EXTRA_TRUSTED_DNS_HOSTS
- .read()
- .map(|hosts| hosts.clone())
- .unwrap_or_default();
- execution_host_allows_benchmarking_dns_answer_with_extra_hosts(host, &extra_hosts)
-}
-
-fn execution_host_allows_benchmarking_dns_answer_with_extra_hosts(
- host: &str,
- extra_hosts: &BTreeSet,
-) -> bool {
- let host = host.trim().trim_end_matches('.').to_ascii_lowercase();
- if extra_hosts.contains(&host)
- || TRUSTED_EXECUTION_BENCHMARKING_DNS_EXACT_HOSTS
- .iter()
- .any(|trusted| *trusted == host)
- {
- return true;
- }
-
- // Vertex service-account requests use `-aiplatform.googleapis.com`.
- // Keep this compatibility exception limited to known provider regions.
- if let Some(region) = host.strip_suffix("-aiplatform.googleapis.com") {
- return TRUSTED_EXECUTION_VERTEX_DNS_REGIONS.contains(®ion);
- }
-
- // Kiro uses a small, fixed set of regional service origins. Match each
- // supported AWS partition explicitly; never use a broad suffix check that
- // could accept an attacker-controlled subdomain.
- matches_regional_service_host(&host, "q", ".amazonaws.com")
- || matches_regional_service_host(&host, "q-fips", ".amazonaws.com")
- || matches_regional_service_host(&host, "codewhisperer", ".amazonaws.com")
- || matches_regional_service_host(&host, "oidc", ".amazonaws.com")
- || matches_regional_service_host(&host, "prod", ".auth.desktop.kiro.dev")
-}
-
-fn matches_regional_service_host(host: &str, service: &str, suffix: &str) -> bool {
- let Some(region) = host
- .strip_prefix(service)
- .and_then(|value| value.strip_prefix('.'))
- .and_then(|value| value.strip_suffix(suffix))
- else {
- return false;
- };
- TRUSTED_EXECUTION_AWS_DNS_REGIONS.contains(®ion)
-}
-
fn dns_host_explicitly_allows_loopback(host: &str) -> bool {
let host = host.trim_end_matches('.');
host.eq_ignore_ascii_case("localhost")
@@ -650,17 +455,10 @@ fn dns_host_explicitly_allows_loopback(host: &str) -> bool {
.unwrap_or(false)
}
-fn validate_execution_dns_answers(
+fn validate_resolved_execution_addresses(
host: &str,
addresses: Vec,
-) -> Result, std::io::Error> {
- validate_execution_dns_answers_with_policy(host, addresses, true)
-}
-
-fn validate_execution_dns_answers_with_policy(
- host: &str,
- addresses: Vec,
- allow_trusted_benchmarking_dns_answer: bool,
+ provider_execution: bool,
) -> Result, std::io::Error> {
if addresses.is_empty() {
return Err(std::io::Error::new(
@@ -668,25 +466,22 @@ fn validate_execution_dns_answers_with_policy(
"upstream DNS resolution returned no addresses",
));
}
-
+ if provider_execution {
+ return Ok(addresses);
+ }
let allows_loopback = dns_host_explicitly_allows_loopback(host);
- let allows_benchmarking_dns_answer = allow_trusted_benchmarking_dns_answer
- && execution_host_allows_benchmarking_dns_answer(host);
- let unsafe_answer = addresses.iter().any(|address| {
+ if addresses.iter().any(|address| {
if allows_loopback {
!address.ip().is_loopback()
} else {
is_private_or_reserved_ip(address.ip())
- && !(allows_benchmarking_dns_answer && is_ipv4_benchmarking_fake_ip(address.ip()))
}
- });
- if unsafe_answer {
+ }) {
return Err(std::io::Error::new(
std::io::ErrorKind::PermissionDenied,
- "upstream DNS resolution returned a private or reserved address",
+ "tunnel relay DNS resolution returned a private or reserved address",
));
}
-
Ok(addresses)
}
@@ -697,7 +492,7 @@ async fn resolve_execution_dns_addresses(host: &str) -> Result,
async fn resolve_execution_target_addresses_with_policy(
host: &str,
port: u16,
- allow_trusted_benchmarking_dns_answer: bool,
+ provider_execution: bool,
) -> Result, std::io::Error> {
let addresses = if let Ok(ip) = host.parse::() {
vec![SocketAddr::new(ip, port)]
@@ -705,24 +500,7 @@ async fn resolve_execution_target_addresses_with_policy(
aether_http::lookup_host_with_limits(host, port, aether_http::DEFAULT_DNS_LOOKUP_TIMEOUT)
.await?
};
- validate_resolved_execution_addresses(
- host,
- addresses,
- allow_trusted_benchmarking_dns_answer,
- *PROVIDER_DNS_ADDRESS_FILTER_ENABLED,
- )
-}
-
-fn validate_resolved_execution_addresses(
- host: &str,
- addresses: Vec,
- provider_execution: bool,
- provider_dns_address_filter_enabled: bool,
-) -> Result, std::io::Error> {
- if provider_execution && !provider_dns_address_filter_enabled && !addresses.is_empty() {
- return Ok(addresses);
- }
- validate_execution_dns_answers_with_policy(host, addresses, provider_execution)
+ validate_resolved_execution_addresses(host, addresses, provider_execution)
}
impl reqwest::dns::Resolve for ExecutionSafeDnsResolver {
@@ -3448,10 +3226,6 @@ async fn resolve_relay_target_addresses(
let port = url.port_or_known_default().ok_or_else(|| {
ExecutionRuntimeTransportError::RelayError("tunnel relay URL has no port".to_string())
})?;
- // Relay destinations remain strict even when their hostname happens to be
- // an official provider origin. The RFC-2544 compatibility exception is
- // only for direct provider execution; allowing it here would weaken the
- // relay SSRF guard.
let addresses = resolve_execution_target_addresses_with_policy(host, port, false)
.await
.map_err(|error| match error.kind() {
@@ -5659,115 +5433,14 @@ mod tests {
}
#[test]
- fn execution_dns_answers_reject_private_addresses_and_allow_explicit_loopback() {
- let public = "93.184.216.34:443".parse().unwrap();
- let private = "10.0.0.8:443".parse().unwrap();
- let loopback_v4 = "127.0.0.1:8080".parse().unwrap();
- let loopback_v6 = "[::1]:8080".parse().unwrap();
-
- assert!(super::validate_execution_dns_answers("api.example.test", vec![public]).is_ok());
- assert!(super::validate_execution_dns_answers("api.example.test", vec![private]).is_err());
- assert!(
- super::validate_execution_dns_answers("localhost", vec![loopback_v4, loopback_v6])
- .is_ok()
- );
- assert!(super::validate_execution_dns_answers("localhost", vec![private]).is_err());
- assert!(super::validate_execution_dns_answers("api.example.test", Vec::new()).is_err());
- }
-
- #[test]
- fn execution_dns_answers_allow_benchmarking_range_only_for_fixed_provider_hosts() {
- let fake = "198.18.75.234:443".parse().unwrap();
- for host in [
- "api.openai.com",
- "CHATGPT.COM.",
- "us-central1-aiplatform.googleapis.com",
- "me-central2-aiplatform.googleapis.com",
- "q.us-east-1.amazonaws.com",
- "q-fips.us-gov-west-1.amazonaws.com",
- "codewhisperer.us-west-2.amazonaws.com",
- "oidc.us-east-1.amazonaws.com",
- "prod.us-east-1.auth.desktop.kiro.dev",
- "q.us-iso-east-1.c2s.ic.gov",
- "q.us-isob-east-1.sc2s.sgov.gov",
- "q.us-isof-east-1.csp.hci.ic.gov",
- ] {
- assert!(
- super::validate_execution_dns_answers(host, vec![fake]).is_ok(),
- "fixed provider host should accept a benchmarking DNS answer: {host}"
- );
- }
-
- for host in [
- "api.example.test",
- "evil.chatgpt.com",
- "api.openai.com.evil.test",
- "q.us-east-1.evil.amazonaws.com",
- "q.us-east-1.amazonaws.com.attacker.test",
- "q.localhost.amazonaws.com",
- "evil-1-aiplatform.googleapis.com",
- "q.evil-1.amazonaws.com",
- "q-fips.evil-1.amazonaws.com",
- "codewhisperer.evil-1.amazonaws.com",
- "prod.evil-1.auth.desktop.kiro.dev",
- "oidc.evil-1.amazonaws.com",
- "q.us-central1.amazonaws.com",
- "us-east-1-aiplatform.googleapis.com",
- "q.us-east-1.c2s.ic.gov",
- "q.us-iso-east-1.sc2s.sgov.gov",
- "q-fips.us-gov-west-1.evil.amazonaws.com",
- "codewhisperer.us-west-2.evil.amazonaws.com",
- "oidc.us-east-1.evil.amazonaws.com",
- "prod.us-east-1.auth.desktop.kiro.dev.attacker.test",
- "prod.us-east-1.evil.auth.desktop.kiro.dev",
- "q.us-iso-east-1.evil.c2s.ic.gov",
- "q.us-iso-east-1.c2s.ic.gov.attacker.test",
- "q.us-iso-east-1.c2s.ic.gov.evil",
- "198.18.75.234",
- ] {
- assert!(
- super::validate_execution_dns_answers(host, vec![fake]).is_err(),
- "untrusted or lookalike host must reject a benchmarking DNS answer: {host}"
- );
- }
- }
-
- #[test]
- fn google_oauth_execution_dns_allows_only_exact_hosts_and_fake_ip_answers() {
- let fake = "198.18.78.41:443".parse().unwrap();
- for host in ["oauth2.googleapis.com", "www.googleapis.com"] {
- assert!(super::validate_execution_dns_answers(host, vec![fake]).is_ok());
- assert!(
- super::validate_execution_dns_answers_with_policy(host, vec![fake], false).is_err()
- );
- for private in [
- "127.0.0.1:443",
- "10.0.0.1:443",
- "169.254.169.254:443",
- "[::1]:443",
- ] {
- let private = private.parse().unwrap();
- assert!(super::validate_execution_dns_answers(host, vec![private]).is_err());
- assert!(super::validate_execution_dns_answers(host, vec![fake, private]).is_err());
- }
- }
- for host in [
- "oauth2.googleapis.com.attacker.test",
- "www.googleapis.com.attacker.test",
- "evil.oauth2.googleapis.com",
- "evil.googleapis.com",
- ] {
- assert!(super::validate_execution_dns_answers(host, vec![fake]).is_err());
- }
- }
-
- #[test]
- fn execution_dns_address_filter_is_optional_only_for_provider_connections() {
+ fn execution_dns_answers_allow_all_provider_hosts_without_address_filtering() {
let addresses = vec![
"198.18.78.41:443".parse().unwrap(),
"10.0.0.8:443".parse().unwrap(),
"127.0.0.1:443".parse().unwrap(),
+ "169.254.169.254:443".parse().unwrap(),
"[fd00::1]:443".parse().unwrap(),
+ "93.184.216.34:443".parse().unwrap(),
];
for host in [
"oauth2.googleapis.com",
@@ -5775,72 +5448,60 @@ mod tests {
"custom.example.test",
] {
assert_eq!(
- super::validate_resolved_execution_addresses(host, addresses.clone(), true, false)
- .expect("provider DNS answers should pass through without filtering"),
- addresses,
+ super::validate_resolved_execution_addresses(host, addresses.clone(), true)
+ .expect("provider DNS answers should pass through"),
+ addresses
);
- assert!(super::validate_resolved_execution_addresses(
- host,
- addresses.clone(),
- true,
- true
- )
- .is_err());
- for filter_enabled in [false, true] {
- assert!(super::validate_resolved_execution_addresses(
- host,
- addresses.clone(),
- false,
- filter_enabled
- )
- .is_err());
- assert!(super::validate_resolved_execution_addresses(
- host,
- Vec::new(),
- true,
- filter_enabled
- )
- .is_err());
- }
}
}
#[test]
- fn execution_dns_answers_allow_benchmarking_range_for_configured_exact_hosts() {
- let fake = "198.18.75.234:443".parse().unwrap();
- super::refresh_execution_extra_trusted_dns_hosts(Some(&json!(["custom.example.com",])));
-
- assert!(super::validate_execution_dns_answers("custom.example.com", vec![fake]).is_ok());
- assert!(
- super::validate_execution_dns_answers("api.custom.example.com", vec![fake]).is_err()
- );
-
- super::refresh_execution_extra_trusted_dns_hosts(None);
- assert!(super::validate_execution_dns_answers("custom.example.com", vec![fake]).is_err());
- }
-
- #[test]
- fn execution_dns_answers_reject_mixed_private_results_and_strict_relay_policy() {
- let fake = "198.18.75.234:443".parse().unwrap();
+ fn execution_dns_answers_keep_relay_address_filtering() {
let public = "93.184.216.34:443".parse().unwrap();
- let private = "10.0.0.8:443".parse().unwrap();
-
- // A trusted host may have a synthetic answer alongside a genuine public
- // answer, but any real private answer still fails closed.
+ for host in ["oauth2.googleapis.com", "custom.example.test"] {
+ assert!(
+ super::validate_resolved_execution_addresses(host, vec![public], false).is_ok()
+ );
+ for blocked in [
+ "198.18.78.41:443",
+ "10.0.0.8:443",
+ "127.0.0.1:443",
+ "169.254.169.254:443",
+ "[fd00::1]:443",
+ ] {
+ let blocked = blocked.parse().unwrap();
+ assert!(
+ super::validate_resolved_execution_addresses(host, vec![blocked], false)
+ .is_err()
+ );
+ assert!(super::validate_resolved_execution_addresses(
+ host,
+ vec![public, blocked],
+ false
+ )
+ .is_err());
+ }
+ }
+ let loopback = vec![
+ "127.0.0.1:443".parse().unwrap(),
+ "[::1]:443".parse().unwrap(),
+ ];
+ assert!(super::validate_resolved_execution_addresses("localhost", loopback, false).is_ok());
assert!(
- super::validate_execution_dns_answers("api.openai.com", vec![fake, public]).is_ok()
+ super::validate_resolved_execution_addresses("localhost", vec![public], false).is_err()
);
- assert!(
- super::validate_execution_dns_answers("api.openai.com", vec![fake, private]).is_err()
- );
-
- // Tunnel relay resolution opts out of the compatibility exception.
- assert!(super::validate_execution_dns_answers_with_policy(
- "api.openai.com",
- vec![fake],
- false,
- )
- .is_err());
+ for provider_execution in [false, true] {
+ assert_eq!(
+ super::validate_resolved_execution_addresses(
+ "custom.example.test",
+ Vec::new(),
+ provider_execution
+ )
+ .expect_err("empty DNS answers must fail")
+ .kind(),
+ std::io::ErrorKind::NotFound
+ );
+ }
}
#[test]
diff --git a/apps/aether-gateway/src/main.rs b/apps/aether-gateway/src/main.rs
index 2341a493e..0721a9d5d 100644
--- a/apps/aether-gateway/src/main.rs
+++ b/apps/aether-gateway/src/main.rs
@@ -2408,10 +2408,6 @@ async fn run() -> Result<(), Box> {
);
}
}
- match state.prewarm_execution_extra_trusted_dns_hosts().await {
- Ok(_) => info!("prewarmed execution Fake-IP DNS allowlist"),
- Err(err) => warn!(error = %err, "failed to prewarm execution Fake-IP DNS allowlist"),
- }
match prewarm_direct_h2c_sender_cache_from_env_for_startup().await {
Ok(Some(report)) => {
if report.failed_targets > 0 {
diff --git a/apps/aether-gateway/src/state/core.rs b/apps/aether-gateway/src/state/core.rs
index 236073550..44731f40d 100644
--- a/apps/aether-gateway/src/state/core.rs
+++ b/apps/aether-gateway/src/state/core.rs
@@ -154,15 +154,6 @@ impl AppState {
.map_err(|err| format!("{err:?}"))
}
- pub async fn prewarm_execution_extra_trusted_dns_hosts(&self) -> Result<(), String> {
- self.read_system_config_json_value(
- aether_admin::system::EXECUTION_EXTRA_TRUSTED_DNS_HOSTS_CONFIG_KEY,
- )
- .await
- .map(|_| ())
- .map_err(|err| format!("{err:?}"))
- }
-
fn usage_worker_queue_for(
runtime_state: &Arc,
) -> Option> {
@@ -778,18 +769,6 @@ impl AppState {
.expect("admin monitoring error stats reset cache should lock")
}
- fn refresh_execution_extra_trusted_dns_hosts(
- &self,
- key: &str,
- value: Option<&serde_json::Value>,
- ) {
- if key.eq_ignore_ascii_case(
- aether_admin::system::EXECUTION_EXTRA_TRUSTED_DNS_HOSTS_CONFIG_KEY,
- ) {
- crate::execution_runtime::transport::refresh_execution_extra_trusted_dns_hosts(value);
- }
- }
-
pub(crate) fn mark_admin_monitoring_error_stats_reset(&self, now_unix_secs: u64) {
let mut reset_at = self
.admin_monitoring_error_stats_reset_at
@@ -809,7 +788,6 @@ impl AppState {
SYSTEM_CONFIG_CACHE_MAX_STALENESS,
)
.await?;
- self.refresh_execution_extra_trusted_dns_hosts(key, value.as_ref());
Ok(value)
}
@@ -822,7 +800,6 @@ impl AppState {
.find_system_config_value_strong(key)
.await
.map_err(|err| GatewayError::Internal(err.to_string()))?;
- self.refresh_execution_extra_trusted_dns_hosts(key, value.as_ref());
Ok(value)
}
@@ -961,7 +938,6 @@ impl AppState {
.map_err(|err| GatewayError::Internal(err.to_string()))?;
self.system_config_cache
.insert(key.to_string(), None, SYSTEM_CONFIG_CACHE_MAX_STALENESS);
- self.refresh_execution_extra_trusted_dns_hosts(key, None);
if deleted && system_config_key_affects_scheduler(key) {
self.invalidate_scheduler_affinity_cache();
}
@@ -1043,7 +1019,6 @@ impl AppState {
}
fn remember_system_config_write(&self, key: &str, value: Option) {
- self.refresh_execution_extra_trusted_dns_hosts(key, value.as_ref());
self.system_config_cache
.insert(key.to_string(), value, SYSTEM_CONFIG_CACHE_MAX_STALENESS);
if system_config_key_affects_scheduler(key) {
@@ -1091,7 +1066,6 @@ impl AppState {
| aether_data::repository::system::AdminSystemPurgeTarget::Stats
) {
self.system_config_cache.clear();
- crate::execution_runtime::transport::refresh_execution_extra_trusted_dns_hosts(None);
self.invalidate_provider_routing_caches();
}
Ok(summary)
diff --git a/crates/aether-admin/src/system.rs b/crates/aether-admin/src/system.rs
index 9cafb2975..3e276f34b 100644
--- a/crates/aether-admin/src/system.rs
+++ b/crates/aether-admin/src/system.rs
@@ -67,17 +67,6 @@ pub fn admin_email_template_html_is_valid(value: &str) -> bool {
pub const ADMIN_SYSTEM_CONFIG_EXPORT_VERSION: &str = "2.3";
pub const ADMIN_SYSTEM_CONFIG_SUPPORTED_VERSIONS: &[&str] =
&["2.0", "2.1", "2.2", ADMIN_SYSTEM_CONFIG_EXPORT_VERSION];
-pub const EXECUTION_EXTRA_TRUSTED_DNS_HOSTS_CONFIG_KEY: &str = "execution_extra_trusted_dns_hosts";
-pub const EXECUTION_EXTRA_TRUSTED_DNS_HOSTS_MAX_ENTRIES: usize = 128;
-pub const EXECUTION_EXTRA_TRUSTED_DNS_HOST_MAX_BYTES: usize = 253;
-
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
-pub enum ExecutionExtraTrustedDnsHostsConfigError {
- InvalidValue,
- TooManyEntries,
- InvalidHost,
-}
-
pub const ADMIN_SYSTEM_USERS_EXPORT_VERSION: &str = "1.6";
pub const ADMIN_SYSTEM_USERS_SUPPORTED_VERSIONS: &[&str] =
&["1.3", "1.4", "1.5", ADMIN_SYSTEM_USERS_EXPORT_VERSION];
@@ -2289,7 +2278,6 @@ pub fn admin_system_config_default_value(key: &str) -> Option
"email_suffix_mode" => Some(json!("none")),
"email_suffix_list" => Some(json!([])),
"enable_format_conversion" => Some(json!(false)),
- EXECUTION_EXTRA_TRUSTED_DNS_HOSTS_CONFIG_KEY => Some(json!([])),
"enable_model_directives" => Some(json!(false)),
// Failover after a provider-side Cyber policy refusal is an explicit
// opt-in. Keep the system-config fallback aligned with the routing
@@ -2329,58 +2317,6 @@ pub fn admin_system_config_default_value(key: &str) -> Option
}
}
-pub fn normalize_execution_extra_trusted_dns_hosts_config_value(
- value: serde_json::Value,
-) -> Result {
- let values = match value {
- Value::Null => Vec::new(),
- Value::Array(values) => values,
- _ => return Err(ExecutionExtraTrustedDnsHostsConfigError::InvalidValue),
- };
- if values.len() > EXECUTION_EXTRA_TRUSTED_DNS_HOSTS_MAX_ENTRIES {
- return Err(ExecutionExtraTrustedDnsHostsConfigError::TooManyEntries);
- }
-
- let mut hosts = BTreeSet::new();
- for value in values {
- let host = value
- .as_str()
- .map(str::trim)
- .ok_or(ExecutionExtraTrustedDnsHostsConfigError::InvalidHost)?;
- let host = host.trim_end_matches('.').to_ascii_lowercase();
- if !execution_extra_trusted_dns_host_is_valid(&host) {
- return Err(ExecutionExtraTrustedDnsHostsConfigError::InvalidHost);
- }
- hosts.insert(host);
- }
-
- Ok(Value::Array(hosts.into_iter().map(Value::String).collect()))
-}
-
-fn execution_extra_trusted_dns_host_is_valid(host: &str) -> bool {
- if host.is_empty()
- || host.len() > EXECUTION_EXTRA_TRUSTED_DNS_HOST_MAX_BYTES
- || !host.is_ascii()
- || host.parse::().is_ok()
- {
- return false;
- }
-
- let labels = host.split('.').collect::>();
- if labels.len() < 2 {
- return false;
- }
- labels.iter().all(|label| {
- !label.is_empty()
- && label.len() <= 63
- && !label.starts_with('-')
- && !label.ends_with('-')
- && label
- .bytes()
- .all(|byte| byte.is_ascii_alphanumeric() || byte == b'-')
- })
-}
-
pub fn build_admin_system_configs_payload(
entries: &[StoredSystemConfigEntry],
) -> serde_json::Value {
@@ -2872,15 +2808,6 @@ pub fn parse_admin_system_config_update(
)
})?;
}
- EXECUTION_EXTRA_TRUSTED_DNS_HOSTS_CONFIG_KEY => {
- value =
- normalize_execution_extra_trusted_dns_hosts_config_value(value).map_err(|_| {
- (
- http::StatusCode::BAD_REQUEST,
- json!({ "detail": "额外可信 Fake-IP 域名配置格式无效" }),
- )
- })?;
- }
"module.important_notification.default_channel" => {
value = normalize_notification_channel_value(value).map_err(|_| {
(
@@ -4621,36 +4548,6 @@ mod tests {
.is_err());
}
- #[test]
- fn extra_trusted_dns_hosts_update_normalizes_exact_hostnames() {
- let update = parse_admin_system_config_update(
- "execution_extra_trusted_dns_hosts",
- br#"{"value":[" API.Example.COM. ","api.example.com"]}"#,
- )
- .expect("valid extra trusted DNS hosts should parse");
- assert_eq!(update.value, json!(["api.example.com"]));
- }
-
- #[test]
- fn extra_trusted_dns_hosts_update_rejects_non_exact_hostnames() {
- for value in [
- r#"["*.example.com"]"#,
- r#"["example.com:443"]"#,
- r#"["https://example.com/path"]"#,
- r#"["10.0.0.1"]"#,
- r#"["example..com"]"#,
- r#"["localhost"]"#,
- ] {
- let body = format!(r#"{{"value":{value}}}"#);
- let error = parse_admin_system_config_update(
- "execution_extra_trusted_dns_hosts",
- body.as_bytes(),
- )
- .expect_err("non-exact hostname should be rejected");
- assert_eq!(error.0, http::StatusCode::BAD_REQUEST);
- }
- }
-
#[test]
fn legacy_notification_email_config_key_normalizes_to_important_notification() {
assert_eq!(
diff --git a/frontend/src/i18n/legacy-ui-messages.ts b/frontend/src/i18n/legacy-ui-messages.ts
index be7f97167..3edc6fffc 100644
--- a/frontend/src/i18n/legacy-ui-messages.ts
+++ b/frontend/src/i18n/legacy-ui-messages.ts
@@ -422,8 +422,6 @@ export const legacyUiEnglishMessages: Record = {
'至少选择一个清理范围': 'Select at least one cleanup scope',
'等待后台返回结果': 'Waiting for the server to return results',
'默认代理节点': 'Default proxy node',
- '额外可信 Fake-IP 域名': 'Additional trusted Fake-IP domains',
- '每行填写一个精确 hostname;不支持通配符、后缀、端口、路径或 IP。仅允许这些域名返回 198.18.0.0/15 Fake-IP,内置提供商域名不受影响。': 'Enter one exact hostname per line. Wildcards, suffixes, ports, paths, and IP addresses are not supported. Only these domains may return Fake-IP addresses in 198.18.0.0/15. Built-in provider domains are unaffected.',
'配置系统后台定时任务': 'Configure scheduled background tasks',
'显示在导航栏、登录页标题和邮件中': 'Shown in the navigation bar, sign-in page title, and emails',
'显示在导航栏品牌名称下方': 'Shown below the brand name in the navigation bar',
diff --git a/frontend/src/views/admin/SystemSettings.vue b/frontend/src/views/admin/SystemSettings.vue
index 515749539..4940022cb 100644
--- a/frontend/src/views/admin/SystemSettings.vue
+++ b/frontend/src/views/admin/SystemSettings.vue
@@ -50,14 +50,12 @@
@@ -353,7 +351,6 @@ const {
hasCleanupConfigChanges,
sensitiveHeadersStr,
turnstileAllowedHostnamesStr,
- extraTrustedDnsHostsStr,
loadSystemConfig,
loadSystemVersion,
saveSiteInfo,
diff --git a/frontend/src/views/admin/system-settings/ProxyConfigSection.vue b/frontend/src/views/admin/system-settings/ProxyConfigSection.vue
index 29eb6a625..df8e22e62 100644
--- a/frontend/src/views/admin/system-settings/ProxyConfigSection.vue
+++ b/frontend/src/views/admin/system-settings/ProxyConfigSection.vue
@@ -40,25 +40,6 @@
对未单独配置代理的提供商生效,覆盖大模型 API 请求、余额查询、OAuth 刷新等。不影响系统内部接口。
-
-
-
-
- 每行填写一个精确 hostname;不支持通配符、后缀、端口、路径或 IP。仅允许这些域名返回 198.18.0.0/15 Fake-IP,内置提供商域名不受影响。
-
-
@@ -66,7 +47,6 @@
import { computed } from 'vue'
import Button from '@/components/ui/button.vue'
import Label from '@/components/ui/label.vue'
-import Textarea from '@/components/ui/textarea.vue'
import Select from '@/components/ui/select.vue'
import SelectTrigger from '@/components/ui/select-trigger.vue'
import SelectValue from '@/components/ui/select-value.vue'
@@ -84,7 +64,6 @@ interface ProxyNode {
const props = defineProps<{
proxyNodeId: string | null
- extraTrustedDnsHostsStr: string
onlineNodes: ProxyNode[]
allNodes: ProxyNode[]
loading: boolean
@@ -94,7 +73,6 @@ const props = defineProps<{
defineEmits<{
save: []
'update:proxyNodeId': [value: string | null]
- 'update:extraTrustedDnsHostsStr': [value: string]
}>()
const selectableNodes = computed(() => {
diff --git a/frontend/src/views/admin/system-settings/__tests__/useSystemConfig.spec.ts b/frontend/src/views/admin/system-settings/__tests__/useSystemConfig.spec.ts
index 939fc00e2..2aa93e5d2 100644
--- a/frontend/src/views/admin/system-settings/__tests__/useSystemConfig.spec.ts
+++ b/frontend/src/views/admin/system-settings/__tests__/useSystemConfig.spec.ts
@@ -81,29 +81,21 @@ describe('useSystemConfig', () => {
expect(state.systemConfig.value).not.toHaveProperty('max_response_body_size')
})
- it('loads and saves the extra trusted Fake-IP DNS hosts with proxy settings', async () => {
+ it('saves only the proxy node and ignores retired DNS allowlist settings', async () => {
getAllSystemConfigsMock.mockResolvedValue([
{ key: 'system_proxy_node_id', value: 'node-1' },
{ key: 'execution_extra_trusted_dns_hosts', value: ['custom.example.com'] },
])
updateSystemConfigMock.mockResolvedValue(undefined)
-
const state = useSystemConfig()
await state.loadSystemConfig()
-
- expect(state.extraTrustedDnsHostsStr.value).toBe('custom.example.com')
- state.extraTrustedDnsHostsStr.value = 'api.example.com\nmodels.example.com'
- expect(state.systemConfig.value.execution_extra_trusted_dns_hosts).toEqual([
- 'api.example.com',
- 'models.example.com',
- ])
-
+ expect(state.systemConfig.value).not.toHaveProperty('execution_extra_trusted_dns_hosts')
+ state.systemConfig.value.system_proxy_node_id = 'node-2'
+ expect(state.hasProxyConfigChanges.value).toBe(true)
await state.saveProxyConfig()
-
+ expect(updateSystemConfigMock).toHaveBeenCalledTimes(1)
expect(updateSystemConfigMock).toHaveBeenCalledWith(
- 'execution_extra_trusted_dns_hosts',
- ['api.example.com', 'models.example.com'],
- '执行运行时额外可信 Fake-IP 域名'
+ 'system_proxy_node_id', 'node-2', '系统默认代理节点 ID'
)
expect(state.hasProxyConfigChanges.value).toBe(false)
})
diff --git a/frontend/src/views/admin/system-settings/composables/useSystemConfig.ts b/frontend/src/views/admin/system-settings/composables/useSystemConfig.ts
index d6f8f17e4..6c2c86bf9 100644
--- a/frontend/src/views/admin/system-settings/composables/useSystemConfig.ts
+++ b/frontend/src/views/admin/system-settings/composables/useSystemConfig.ts
@@ -10,7 +10,6 @@ export interface SystemConfig {
site_subtitle: string
// 网络代理
system_proxy_node_id: string | null
- execution_extra_trusted_dns_hosts: string[]
// 基础配置
default_user_initial_gift_usd: number
rate_limit_per_minute: number
@@ -62,7 +61,6 @@ const CONFIG_KEYS = [
'site_subtitle',
// 网络代理
'system_proxy_node_id',
- 'execution_extra_trusted_dns_hosts',
// 基础配置
'default_user_initial_gift_usd',
'rate_limit_per_minute',
@@ -114,7 +112,6 @@ function createDefaultConfig(): SystemConfig {
site_subtitle: 'AI Gateway',
// 网络代理
system_proxy_node_id: null,
- execution_extra_trusted_dns_hosts: [],
// 基础配置
default_user_initial_gift_usd: 10.0,
rate_limit_per_minute: 0,
@@ -191,8 +188,6 @@ export function useSystemConfig() {
if (systemConfigLoading.value) return false
if (!originalConfig.value) return false
return systemConfig.value.system_proxy_node_id !== originalConfig.value.system_proxy_node_id
- || JSON.stringify(systemConfig.value.execution_extra_trusted_dns_hosts) !==
- JSON.stringify(originalConfig.value.execution_extra_trusted_dns_hosts)
})
const hasBasicConfigChanges = computed(() => {
@@ -283,16 +278,6 @@ export function useSystemConfig() {
},
})
- const extraTrustedDnsHostsStr = computed({
- get: () => systemConfig.value.execution_extra_trusted_dns_hosts.join('\n'),
- set: (val: string) => {
- systemConfig.value.execution_extra_trusted_dns_hosts = val
- .split(/[\n,]/)
- .map((s) => s.trim().toLowerCase().replace(/\.$/, ''))
- .filter((s) => s.length > 0)
- },
- })
-
// 加载配置
async function loadSystemConfig() {
systemConfigLoading.value = true
@@ -370,23 +355,13 @@ export function useSystemConfig() {
async function saveProxyConfig() {
proxyConfigLoading.value = true
try {
- await Promise.all([
- adminApi.updateSystemConfig(
- 'system_proxy_node_id',
- systemConfig.value.system_proxy_node_id || null,
- '系统默认代理节点 ID'
- ),
- adminApi.updateSystemConfig(
- 'execution_extra_trusted_dns_hosts',
- systemConfig.value.execution_extra_trusted_dns_hosts,
- '执行运行时额外可信 Fake-IP 域名'
- ),
- ])
+ await adminApi.updateSystemConfig(
+ 'system_proxy_node_id',
+ systemConfig.value.system_proxy_node_id || null,
+ '系统默认代理节点 ID'
+ )
if (originalConfig.value) {
originalConfig.value.system_proxy_node_id = systemConfig.value.system_proxy_node_id
- originalConfig.value.execution_extra_trusted_dns_hosts = [
- ...systemConfig.value.execution_extra_trusted_dns_hosts,
- ]
}
success('网络代理配置已保存')
} catch (err) {
@@ -741,7 +716,6 @@ export function useSystemConfig() {
// 计算属性
sensitiveHeadersStr,
turnstileAllowedHostnamesStr,
- extraTrustedDnsHostsStr,
// 加载函数
loadSystemConfig,
loadSystemVersion,