mirror of
https://github.com/aiclientproxy/proxycast.git
synced 2026-09-24 23:10:56 +08:00
fix: allow 0.0.0.0 binding for LAN access (fixes #63)
Allow binding to 0.0.0.0 or :: to enable LAN access, with security requirements: 1. Add is_valid_bind_host() and is_non_local_bind() helper functions 2. Allow 0.0.0.0 and :: as valid bind addresses 3. Require non-default API key when binding to all interfaces 4. Update validation in: - save_config command - hot_reload validation - bootstrap validation Security: When using 0.0.0.0 or ::, users must set a custom API key (not the default "proxy_cast") to prevent unauthorized access. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
+1
-1
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "proxycast",
|
||||
"private": true,
|
||||
"version": "0.36.2",
|
||||
"version": "0.36.3",
|
||||
"type": "module",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "proxycast"
|
||||
version = "0.36.2"
|
||||
version = "0.36.3"
|
||||
description = "AI API Proxy Desktop App"
|
||||
authors = ["you"]
|
||||
edition = "2021"
|
||||
|
||||
@@ -40,7 +40,7 @@ use crate::services::token_cache_service::TokenCacheService;
|
||||
use crate::telemetry;
|
||||
|
||||
use super::types::{AppState, LogState, TokenCacheServiceState};
|
||||
use super::utils::{generate_api_key, is_loopback_host};
|
||||
use super::utils::{generate_api_key, is_non_local_bind, is_valid_bind_host};
|
||||
|
||||
/// 配置验证错误
|
||||
#[derive(Debug)]
|
||||
@@ -48,7 +48,7 @@ pub enum ConfigError {
|
||||
LoadFailed(String),
|
||||
SaveFailed(String),
|
||||
InvalidHost,
|
||||
DefaultApiKey,
|
||||
DefaultApiKeyWithNonLocalBind,
|
||||
TlsNotSupported,
|
||||
RemoteManagementNotSupported,
|
||||
}
|
||||
@@ -59,9 +59,15 @@ impl std::fmt::Display for ConfigError {
|
||||
ConfigError::LoadFailed(e) => write!(f, "配置加载失败: {}", e),
|
||||
ConfigError::SaveFailed(e) => write!(f, "配置保存失败: {}", e),
|
||||
ConfigError::InvalidHost => {
|
||||
write!(f, "当前版本仅支持本地监听,请使用 127.0.0.1/localhost/::1")
|
||||
write!(
|
||||
f,
|
||||
"无效的监听地址。允许的地址:127.0.0.1、localhost、::1、0.0.0.0、::"
|
||||
)
|
||||
}
|
||||
ConfigError::DefaultApiKey => write!(f, "检测到使用默认 API key,请配置强密钥"),
|
||||
ConfigError::DefaultApiKeyWithNonLocalBind => write!(
|
||||
f,
|
||||
"监听所有网络接口 (0.0.0.0 或 ::) 时,必须设置非默认的 API Key"
|
||||
),
|
||||
ConfigError::TlsNotSupported => write!(f, "当前版本尚未支持 TLS"),
|
||||
ConfigError::RemoteManagementNotSupported => {
|
||||
write!(f, "远程管理需要 TLS 支持,当前版本未启用")
|
||||
@@ -74,22 +80,24 @@ impl std::fmt::Display for ConfigError {
|
||||
pub fn load_and_validate_config() -> Result<Config, ConfigError> {
|
||||
let mut config = config::load_config().map_err(|e| ConfigError::LoadFailed(e.to_string()))?;
|
||||
|
||||
// 自动生成 API key(如果使用默认值)
|
||||
if config.server.api_key == config::DEFAULT_API_KEY {
|
||||
// 验证主机地址
|
||||
if !is_valid_bind_host(&config.server.host) {
|
||||
return Err(ConfigError::InvalidHost);
|
||||
}
|
||||
|
||||
let is_non_local = is_non_local_bind(&config.server.host);
|
||||
|
||||
// 如果是本地绑定且使用默认 API key,自动生成新密钥
|
||||
if !is_non_local && config.server.api_key == config::DEFAULT_API_KEY {
|
||||
let new_key = generate_api_key();
|
||||
config.server.api_key = new_key;
|
||||
config::save_config(&config).map_err(|e| ConfigError::SaveFailed(e.to_string()))?;
|
||||
tracing::info!("检测到默认 API key,已自动生成并保存新密钥");
|
||||
}
|
||||
|
||||
// 验证主机地址
|
||||
if !is_loopback_host(&config.server.host) {
|
||||
return Err(ConfigError::InvalidHost);
|
||||
}
|
||||
|
||||
// 再次检查 API key(防止保存失败后继续)
|
||||
if config.server.api_key == config::DEFAULT_API_KEY {
|
||||
return Err(ConfigError::DefaultApiKey);
|
||||
// 如果是非本地绑定,必须使用非默认 API key
|
||||
if is_non_local && config.server.api_key == config::DEFAULT_API_KEY {
|
||||
return Err(ConfigError::DefaultApiKeyWithNonLocalBind);
|
||||
}
|
||||
|
||||
// 检查 TLS 配置
|
||||
|
||||
@@ -3,10 +3,11 @@
|
||||
//! 包含配置读取、保存、Provider 设置等命令。
|
||||
|
||||
use crate::app::types::{AppState, LogState};
|
||||
use crate::app::utils::{is_non_local_bind, is_valid_bind_host};
|
||||
use crate::config::{
|
||||
self,
|
||||
observer::{ConfigChangeEvent, RoutingChangeEvent},
|
||||
ConfigChangeSource, GlobalConfigManagerState,
|
||||
ConfigChangeSource, GlobalConfigManagerState, DEFAULT_API_KEY,
|
||||
};
|
||||
|
||||
/// 获取配置
|
||||
@@ -22,12 +23,19 @@ pub async fn save_config(
|
||||
state: tauri::State<'_, AppState>,
|
||||
config: config::Config,
|
||||
) -> Result<(), String> {
|
||||
// P0 安全修复:禁止危险的网络配置
|
||||
let host = config.server.host.to_lowercase();
|
||||
if host == "0.0.0.0" || host == "::" {
|
||||
|
||||
// 验证绑定地址
|
||||
if !is_valid_bind_host(&host) {
|
||||
return Err(
|
||||
"安全限制:不允许监听所有网络接口 (0.0.0.0 或 ::)。请使用 127.0.0.1 或 localhost"
|
||||
.to_string(),
|
||||
"无效的监听地址。允许的地址:127.0.0.1、localhost、::1、0.0.0.0、::".to_string(),
|
||||
);
|
||||
}
|
||||
|
||||
// 如果监听所有接口,要求使用强 API Key
|
||||
if is_non_local_bind(&host) && config.server.api_key == DEFAULT_API_KEY {
|
||||
return Err(
|
||||
"安全限制:监听所有网络接口 (0.0.0.0 或 ::) 时,必须设置非默认的 API Key".to_string(),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,21 @@ pub fn is_loopback_host(host: &str) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
/// 检查是否为有效的绑定地址
|
||||
/// 允许回环地址和 0.0.0.0(监听所有接口)
|
||||
pub fn is_valid_bind_host(host: &str) -> bool {
|
||||
if is_loopback_host(host) {
|
||||
return true;
|
||||
}
|
||||
// 允许 0.0.0.0 和 :: (监听所有接口)
|
||||
host == "0.0.0.0" || host == "::"
|
||||
}
|
||||
|
||||
/// 检查是否为非本地绑定地址(需要强 API Key)
|
||||
pub fn is_non_local_bind(host: &str) -> bool {
|
||||
host == "0.0.0.0" || host == "::"
|
||||
}
|
||||
|
||||
/// 掩码敏感 Token
|
||||
pub fn mask_token(token: &str) -> String {
|
||||
let chars: Vec<char> = token.chars().collect();
|
||||
@@ -45,6 +60,28 @@ mod tests {
|
||||
assert!(!is_loopback_host("192.168.1.1"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_valid_bind_host() {
|
||||
// 回环地址
|
||||
assert!(is_valid_bind_host("localhost"));
|
||||
assert!(is_valid_bind_host("127.0.0.1"));
|
||||
assert!(is_valid_bind_host("::1"));
|
||||
// 监听所有接口
|
||||
assert!(is_valid_bind_host("0.0.0.0"));
|
||||
assert!(is_valid_bind_host("::"));
|
||||
// 其他地址不允许
|
||||
assert!(!is_valid_bind_host("192.168.1.1"));
|
||||
assert!(!is_valid_bind_host("10.0.0.1"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_non_local_bind() {
|
||||
assert!(is_non_local_bind("0.0.0.0"));
|
||||
assert!(is_non_local_bind("::"));
|
||||
assert!(!is_non_local_bind("127.0.0.1"));
|
||||
assert!(!is_non_local_bind("localhost"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mask_token() {
|
||||
assert_eq!(mask_token("short"), "****");
|
||||
|
||||
@@ -375,6 +375,8 @@ impl HotReloadManager {
|
||||
/// 验证配置
|
||||
fn validate_config(&self, config: &Config) -> Result<(), HotReloadError> {
|
||||
let is_localhost = is_localhost_host(&config.server.host);
|
||||
let is_valid_host = is_valid_bind_host(&config.server.host);
|
||||
let is_non_local = is_non_local_bind(&config.server.host);
|
||||
|
||||
// 验证端口范围
|
||||
if config.server.port == 0 {
|
||||
@@ -383,9 +385,10 @@ impl HotReloadManager {
|
||||
));
|
||||
}
|
||||
|
||||
if !is_localhost {
|
||||
// 验证绑定地址
|
||||
if !is_valid_host {
|
||||
return Err(HotReloadError::ValidationError(
|
||||
"当前版本仅支持本地监听,请使用 127.0.0.1/localhost/::1".to_string(),
|
||||
"无效的监听地址。允许的地址:127.0.0.1、localhost、::1、0.0.0.0、::".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
@@ -415,11 +418,12 @@ impl HotReloadManager {
|
||||
));
|
||||
}
|
||||
|
||||
if (!is_localhost || config.remote_management.allow_remote)
|
||||
// 非本地绑定或远程管理时,禁止使用默认 API Key
|
||||
if (is_non_local || !is_localhost || config.remote_management.allow_remote)
|
||||
&& is_default_api_key(&config.server.api_key)
|
||||
{
|
||||
return Err(HotReloadError::ValidationError(
|
||||
"非本地访问场景下禁止使用默认 API Key,请设置强口令".to_string(),
|
||||
"监听所有网络接口或开启远程管理时,禁止使用默认 API Key,请设置强口令".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
@@ -484,6 +488,21 @@ fn is_localhost_host(host: &str) -> bool {
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
/// 检查是否为有效的绑定地址
|
||||
/// 允许回环地址和 0.0.0.0(监听所有接口)
|
||||
fn is_valid_bind_host(host: &str) -> bool {
|
||||
if is_localhost_host(host) {
|
||||
return true;
|
||||
}
|
||||
// 允许 0.0.0.0 和 :: (监听所有接口)
|
||||
host == "0.0.0.0" || host == "::"
|
||||
}
|
||||
|
||||
/// 检查是否为非本地绑定地址(需要强 API Key)
|
||||
fn is_non_local_bind(host: &str) -> bool {
|
||||
host == "0.0.0.0" || host == "::"
|
||||
}
|
||||
|
||||
/// 热重载状态
|
||||
#[derive(Debug, Clone, serde::Serialize)]
|
||||
pub struct HotReloadStatus {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://schema.tauri.app/config/2",
|
||||
"productName": "ProxyCast",
|
||||
"version": "0.36.2",
|
||||
"version": "0.36.3",
|
||||
"identifier": "com.proxycast.app",
|
||||
"build": {
|
||||
"beforeDevCommand": "npm run dev",
|
||||
|
||||
Reference in New Issue
Block a user