Skip to content

Commit 4ddb17f

Browse files
committed
fix: harden proxy redaction and add regression tests
1 parent f71eeda commit 4ddb17f

File tree

4 files changed

+65
-13
lines changed

4 files changed

+65
-13
lines changed

astrbot/core/provider/sources/openai_embedding_source.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
1-
import httpx
21
from openai import AsyncOpenAI
32

4-
from astrbot import logger
53
from astrbot.core.utils.network_utils import create_proxy_client
64

75
from ..entities import ProviderType

astrbot/core/provider/sources/openai_tts_api_source.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
import os
22
import uuid
33

4-
import httpx
54
from openai import NOT_GIVEN, AsyncOpenAI
65

7-
from astrbot import logger
86
from astrbot.core.utils.astrbot_path import get_astrbot_temp_path
97
from astrbot.core.utils.network_utils import create_proxy_client
108

astrbot/core/utils/network_utils.py

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -73,9 +73,12 @@ def log_connection_failure(
7373

7474
if effective_proxy:
7575
sanitized_proxy = _sanitize_proxy_url(effective_proxy)
76+
error_text = str(error)
77+
if effective_proxy:
78+
error_text = error_text.replace(effective_proxy, sanitized_proxy)
7679
logger.error(
7780
f"[{provider_label}] 网络/代理连接失败 ({error_type})。"
78-
f"代理地址: {sanitized_proxy},错误: {error}"
81+
f"代理地址: {sanitized_proxy},错误: {error_text}"
7982
)
8083
else:
8184
logger.error(f"[{provider_label}] 网络连接失败 ({error_type})。错误: {error}")
@@ -94,27 +97,36 @@ def _is_socks_proxy(proxy: str) -> bool:
9497

9598

9699
def _sanitize_proxy_url(proxy: str) -> str:
97-
"""Sanitize proxy URL by masking password for safe logging.
100+
"""Sanitize proxy URL by masking credentials for safe logging.
98101
99102
Args:
100103
proxy: The proxy URL string
101104
102105
Returns:
103-
Sanitized proxy URL with password masked (e.g., "http://user:****@host:port")
106+
Sanitized proxy URL with credentials masked (e.g., "http://****@host:port")
104107
"""
105108
try:
106109
from urllib.parse import urlparse, urlunparse
107110

108111
parsed = urlparse(proxy)
109-
if parsed.password:
110-
# Replace password with asterisks
111-
netloc = f"{parsed.username}:****@{parsed.hostname}"
112+
# Any userinfo in netloc should be masked to avoid leaking tokens/passwords.
113+
if "@" in parsed.netloc and parsed.hostname:
114+
host = parsed.hostname
115+
if ":" in host and not host.startswith("["):
116+
host = f"[{host}]"
117+
netloc = f"****@{host}"
112118
if parsed.port:
113119
netloc += f":{parsed.port}"
114-
sanitized = urlunparse(
115-
(parsed.scheme, netloc, parsed.path, "", "", "")
120+
return urlunparse(
121+
(
122+
parsed.scheme,
123+
netloc,
124+
parsed.path,
125+
parsed.params,
126+
parsed.query,
127+
parsed.fragment,
128+
)
116129
)
117-
return sanitized
118130
except Exception:
119131
pass
120132
return proxy

tests/unit/test_network_utils.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
"""Tests for network utility helpers."""
2+
3+
from astrbot.core.utils import network_utils
4+
5+
6+
def test_sanitize_proxy_url_masks_password_credentials():
7+
proxy = "http://user:secret@127.0.0.1:1080"
8+
assert network_utils._sanitize_proxy_url(proxy) == "http://****@127.0.0.1:1080"
9+
10+
11+
def test_sanitize_proxy_url_masks_username_only_credentials():
12+
proxy = "http://token@127.0.0.1:1080"
13+
assert network_utils._sanitize_proxy_url(proxy) == "http://****@127.0.0.1:1080"
14+
15+
16+
def test_sanitize_proxy_url_masks_empty_password_credentials():
17+
proxy = "http://user:@127.0.0.1:1080"
18+
assert network_utils._sanitize_proxy_url(proxy) == "http://****@127.0.0.1:1080"
19+
20+
21+
def test_is_socks_proxy_detects_supported_schemes():
22+
assert network_utils._is_socks_proxy("socks5://127.0.0.1:1080")
23+
assert network_utils._is_socks_proxy("socks4://127.0.0.1:1080")
24+
assert network_utils._is_socks_proxy("socks5h://127.0.0.1:1080")
25+
assert not network_utils._is_socks_proxy("http://127.0.0.1:1080")
26+
27+
28+
def test_log_connection_failure_redacts_proxy_in_error_text(monkeypatch):
29+
proxy = "http://token@127.0.0.1:1080"
30+
captured = {}
31+
32+
def fake_error(message: str):
33+
captured["message"] = message
34+
35+
monkeypatch.setattr(network_utils.logger, "error", fake_error)
36+
37+
network_utils.log_connection_failure(
38+
provider_label="OpenAI",
39+
error=RuntimeError(f"proxy connect failed: {proxy}"),
40+
proxy=proxy,
41+
)
42+
43+
assert "http://token@127.0.0.1:1080" not in captured["message"]
44+
assert "http://****@127.0.0.1:1080" in captured["message"]

0 commit comments

Comments
 (0)