Skip to content

fix: 添加 SOCKS 代理支持并脱敏代理凭据#5766

Open
whatevertogo wants to merge 4 commits intoAstrBotDevs:masterfrom
whatevertogo:fix/5757-socks-proxy-support
Open

fix: 添加 SOCKS 代理支持并脱敏代理凭据#5766
whatevertogo wants to merge 4 commits intoAstrBotDevs:masterfrom
whatevertogo:fix/5757-socks-proxy-support

Conversation

@whatevertogo
Copy link
Contributor

@whatevertogo whatevertogo commented Mar 5, 2026

Fixes #5757

Modifications / 改动点

SOCKS 代理支持

  • 添加 socksio 依赖以支持 SOCKS 代理
  • create_proxy_client 中检测 SOCKS 代理,缺少依赖时提供清晰的安装指引
  • 统一 OpenAI embedding 和 TTS 源使用 create_proxy_client

代理凭据脱敏

  • 添加 _sanitize_proxy_url 函数脱敏代理 URL
  • 在日志和错误信息中隐藏代理凭据
  • 处理各种代理 URL 格式(带用户名、密码或两者)
  • 增强 fallback 逻辑确保异常情况下也能安全脱敏

修改的文件:

  • astrbot/core/utils/network.py

  • astrbot/core/provider/sources/openai_embedding_source.py

  • astrbot/core/provider/sources/openai_tts_source.py

  • requirements.txt

  • tests/unit/test_network_utils.py

  • This is NOT a breaking change. / 这不是一个破坏性变更。

Screenshots or Test Results / 运行截图或测试结果

单元测试通过:

tests/unit/test_network_utils.py PASSED

Checklist / 检查清单

  • 😊 如果 PR 中有新加入的功能,已经通过 Issue / 邮件等方式和作者讨论过。/ If there are new features added in the PR, I have discussed it with the authors through issues/emails, etc.
  • 👀 我的更改经过了良好的测试,并已在上方提供了"验证步骤"和"运行截图"。/ My changes have been well-tested, and "Verification Steps" and "Screenshots" have been provided above.
  • 🤓 我确保没有引入新依赖库,或者引入了新依赖库的同时将其添加到了 requirements.txtpyproject.toml 文件相应位置。/ I have ensured that no new dependencies are introduced, OR if new dependencies are introduced, they have been added to the appropriate locations in requirements.txt and pyproject.toml.
  • 😮 我的更改没有引入恶意代码。/ My changes do not introduce malicious code.

- 在 pyproject.toml 中添加 socksio 依赖以支持 SOCKS 代理
- 在 create_proxy_client 中检测 SOCKS 代理并在缺少 socksio 时提供清晰的安装指引
- 统一 openai_embedding_source 和 openai_tts_api_source 使用 create_proxy_client

Fixes AstrBotDevs#5757

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

此拉取请求旨在通过引入集中化的代理客户端创建逻辑来增强系统的网络连接能力,特别是增加了对 SOCKS 代理的支持。它通过提供明确的安装指导来改善用户体验,以防缺少 SOCKS 代理所需的依赖。此外,它还重构了 OpenAI 服务的现有集成,使其能够利用这一统一的代理处理机制,从而提升了代码的健壮性和一致性。

Highlights

  • SOCKS 代理支持: 为 HTTP 客户端添加了 SOCKS 代理支持,允许通过 SOCKS4、SOCKS5 和 SOCKS5h 协议进行连接。
  • 友好的错误提示: 当用户尝试使用 SOCKS 代理但未安装必要的 socksio 库时,会提供清晰的错误消息和安装指引,避免静默失败。
  • 代理客户端创建集中化: 将代理客户端的创建逻辑抽象并集中到 create_proxy_client 工具函数中,提高了代码的可重用性和维护性。
  • OpenAI 服务集成: OpenAI 的嵌入(embedding)和 TTS(Text-to-Speech)提供方已更新,以使用新的集中式代理客户端创建逻辑。
  • 依赖更新: 在 pyproject.toml 中添加了 socksio 依赖,以支持 SOCKS 代理功能。
Changelog
  • astrbot/core/provider/sources/openai_embedding_source.py
    • 引入 create_proxy_client 工具函数。
    • 将代理客户端的初始化逻辑替换为调用 create_proxy_client
  • astrbot/core/provider/sources/openai_tts_api_source.py
    • 引入 create_proxy_client 工具函数。
    • 将代理客户端的初始化逻辑替换为调用 create_proxy_client
  • astrbot/core/utils/network_utils.py
    • 新增 _is_socks_proxy 私有函数,用于判断代理 URL 是否为 SOCKS 协议。
    • 修改 create_proxy_client 函数,使其在检测到 SOCKS 代理时检查 socksio 库是否安装,并在未安装时抛出 ImportError 并提供安装指引。
    • 更新 create_proxy_client 的文档字符串,增加 Raises 部分说明 ImportError
  • pyproject.toml
    • [project.dependencies] 部分添加 socksio>=1.0.0 依赖。
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This PR unifies proxy client creation logic using the create_proxy_client helper function, adding SOCKS proxy support and improving error handling for proxy-related issues. A security concern was identified regarding the potential leakage of proxy credentials in logs and error messages; the create_proxy_client function should be updated to sanitize proxy URLs by masking any embedded passwords. The refactoring of openai_embedding_source and openai_tts_api_source is well-done, and the clear error messages for missing socksio dependency enhance user experience. A small optimization is suggested for the _is_socks_proxy function to improve conciseness.

whatevertogo and others added 2 commits March 5, 2026 18:57
- 添加 _sanitize_proxy_url 函数,在日志和错误消息中隐藏代理密码
- 简化 _is_socks_proxy 函数,使用元组参数
- 修复 Gemini Code Assist 指出的安全问题

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@whatevertogo whatevertogo marked this pull request as ready for review March 5, 2026 12:22
Copilot AI review requested due to automatic review settings March 5, 2026 12:22
@dosubot dosubot bot added the size:L This PR changes 100-499 lines, ignoring generated files. label Mar 5, 2026
@dosubot dosubot bot added the area:core The bug / feature is about astrbot's core, backend label Mar 5, 2026
Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - 我发现了 3 个问题

给 AI 助手的提示
Please address the comments from this code review:

## 单条评论

### 评论 1
<location path="astrbot/core/utils/network_utils.py" line_range="108-117" />
<code_context>
+    Returns:
+        Sanitized proxy URL with credentials masked (e.g., "http://****@host:port")
+    """
+    try:
+        from urllib.parse import urlparse, urlunparse
+
+        parsed = urlparse(proxy)
+        # Any userinfo in netloc should be masked to avoid leaking tokens/passwords.
+        if "@" in parsed.netloc and parsed.hostname:
+            host = parsed.hostname
+            if ":" in host and not host.startswith("["):
+                host = f"[{host}]"
+            netloc = f"****@{host}"
+            if parsed.port:
+                netloc += f":{parsed.port}"
+            return urlunparse(
+                (
+                    parsed.scheme,
+                    netloc,
+                    parsed.path,
+                    parsed.params,
+                    parsed.query,
+                    parsed.fragment,
+                )
+            )
+    except Exception:
+        pass
+    return proxy
</code_context>
<issue_to_address>
**🚨 issue (security):** 在解析出错时回退到原始代理地址,仍然可能在日志中泄露凭据。

由于 `_sanitize_proxy_url` 中的任何异常都会导致返回原始 `proxy`,如果解析失败(例如 URL 格式错误、奇怪的编码),凭据仍然可能暴露出来。因为这个辅助函数是用于安全日志记录的,在异常情况下返回一个完全打码的占位值(例如 `"****"` 或者信息极少的值)会比直接返回原始输入更安全。
</issue_to_address>

### 评论 2
<location path="tests/unit/test_network_utils.py" line_range="6-8" />
<code_context>
+from astrbot.core.utils import network_utils
+
+
+def test_sanitize_proxy_url_masks_password_credentials():
+    proxy = "http://user:secret@127.0.0.1:1080"
+    assert network_utils._sanitize_proxy_url(proxy) == "http://****@127.0.0.1:1080"
+
+
</code_context>
<issue_to_address>
**suggestion (testing):** 为 sanitize_proxy_url 增加无凭据 URL、格式错误 URL 和 IPv6 主机的测试用例。

当前测试只覆盖了基础的凭据打码场景。请同时补充以下情况的覆盖:

1. 无用户信息的 URL(例如 `http://127.0.0.1:1080``https://example.com`),以确认它们会原样返回。
2. 明显无效或格式错误的值(例如 `"not a url"`、空字符串),以确认在宽泛的 `try/except` 下它们会原样返回。
3. 带凭据的 IPv6 主机(例如 `http://user:secret@[::1]:1080`),以确保对方括号/冒号的处理不会破坏 `netloc`。

这些用例有助于在后续重构中守住行为不变。
</issue_to_address>

### 评论 3
<location path="tests/unit/test_network_utils.py" line_range="28-37" />
<code_context>
+    assert not network_utils._is_socks_proxy("http://127.0.0.1:1080")
+
+
+def test_log_connection_failure_redacts_proxy_in_error_text(monkeypatch):
+    proxy = "http://token@127.0.0.1:1080"
+    captured = {}
+
+    def fake_error(message: str):
+        captured["message"] = message
+
+    monkeypatch.setattr(network_utils.logger, "error", fake_error)
+
+    network_utils.log_connection_failure(
+        provider_label="OpenAI",
+        error=RuntimeError(f"proxy connect failed: {proxy}"),
+        proxy=proxy,
+    )
+
+    assert "http://token@127.0.0.1:1080" not in captured["message"]
+    assert "http://****@127.0.0.1:1080" in captured["message"]
</code_context>
<issue_to_address>
**suggestion (testing):** 为 log_connection_failure 增加在未提供代理以及错误信息中不包含代理字符串时的测试用例。

当前测试覆盖了 `proxy` 参数与异常消息中都包含相同 URL 的情况。为了更全面地测试 `log_connection_failure`,请另外添加以下场景:

1. `proxy=None`(或为空):确认在这种情况下日志中不会包含代理信息,并且在做打码处理时不会导致崩溃。
2. 错误消息本身不包含代理字符串:确认日志在 "代理地址" 字段中仍然使用打码后的代理值,同时保持错误文本本身不变。
</issue_to_address>

Sourcery 对开源项目免费 —— 如果你觉得我们的代码审查有帮助,欢迎分享给更多人 ✨
帮我变得更有用!请在每条评论上点 👍 或 👎,我会根据你的反馈改进后续的审查。
Original comment in English

Hey - I've found 3 issues

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="astrbot/core/utils/network_utils.py" line_range="108-117" />
<code_context>
+    Returns:
+        Sanitized proxy URL with credentials masked (e.g., "http://****@host:port")
+    """
+    try:
+        from urllib.parse import urlparse, urlunparse
+
+        parsed = urlparse(proxy)
+        # Any userinfo in netloc should be masked to avoid leaking tokens/passwords.
+        if "@" in parsed.netloc and parsed.hostname:
+            host = parsed.hostname
+            if ":" in host and not host.startswith("["):
+                host = f"[{host}]"
+            netloc = f"****@{host}"
+            if parsed.port:
+                netloc += f":{parsed.port}"
+            return urlunparse(
+                (
+                    parsed.scheme,
+                    netloc,
+                    parsed.path,
+                    parsed.params,
+                    parsed.query,
+                    parsed.fragment,
+                )
+            )
+    except Exception:
+        pass
+    return proxy
</code_context>
<issue_to_address>
**🚨 issue (security):** Falling back to the original proxy on parsing errors can still leak credentials in logs.

Because any exception in `_sanitize_proxy_url` causes it to return the original `proxy`, credentials can still be exposed if parsing fails (e.g., malformed URL, odd encodings). Since this helper is intended for safe logging, it would be safer to return a fully redacted placeholder (e.g., `"****"` or a minimally informative value) on exceptions instead of the raw input.
</issue_to_address>

### Comment 2
<location path="tests/unit/test_network_utils.py" line_range="6-8" />
<code_context>
+from astrbot.core.utils import network_utils
+
+
+def test_sanitize_proxy_url_masks_password_credentials():
+    proxy = "http://user:secret@127.0.0.1:1080"
+    assert network_utils._sanitize_proxy_url(proxy) == "http://****@127.0.0.1:1080"
+
+
</code_context>
<issue_to_address>
**suggestion (testing):** Add tests for sanitize_proxy_url on URLs without credentials, malformed URLs, and IPv6 hosts.

The current tests only cover the basic credential redaction case. Please also add coverage for:

1. URLs without userinfo (e.g. `http://127.0.0.1:1080`, `https://example.com`) to confirm they are returned unchanged.
2. Clearly invalid or malformed values (e.g. `"not a url"`, empty string) to confirm they are returned as-is under the broad `try/except`.
3. IPv6 hosts with credentials (e.g. `http://user:secret@[::1]:1080`) to ensure the bracket/colon handling doesn’t break the `netloc`.

These cases will help guard behavior in future refactors.
</issue_to_address>

### Comment 3
<location path="tests/unit/test_network_utils.py" line_range="28-37" />
<code_context>
+    assert not network_utils._is_socks_proxy("http://127.0.0.1:1080")
+
+
+def test_log_connection_failure_redacts_proxy_in_error_text(monkeypatch):
+    proxy = "http://token@127.0.0.1:1080"
+    captured = {}
+
+    def fake_error(message: str):
+        captured["message"] = message
+
+    monkeypatch.setattr(network_utils.logger, "error", fake_error)
+
+    network_utils.log_connection_failure(
+        provider_label="OpenAI",
+        error=RuntimeError(f"proxy connect failed: {proxy}"),
+        proxy=proxy,
+    )
+
+    assert "http://token@127.0.0.1:1080" not in captured["message"]
+    assert "http://****@127.0.0.1:1080" in captured["message"]
</code_context>
<issue_to_address>
**suggestion (testing):** Add tests for log_connection_failure when no proxy is provided and when the error message doesn’t contain the proxy string.

This test covers the case where both the `proxy` argument and the exception message share the same URL. To more fully exercise `log_connection_failure`, please also add tests for:

1. `proxy=None` (or empty): confirm it logs without proxy info and doesn’t crash while sanitizing.
2. An error whose message does not contain the proxy string: confirm the log still uses the sanitized proxy in the "代理地址" field while leaving the error text unchanged.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@dosubot
Copy link

dosubot bot commented Mar 5, 2026

Related Documentation

Checked 1 published document(s) in 1 knowledge base(s). No updates required.

How did I do? Any feedback?  Join Discord

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR improves AstrBot’s proxy handling by adding SOCKS proxy detection, providing a clearer missing-dependency error for SOCKS support, and strengthening proxy credential redaction in logs across provider clients.

Changes:

  • Add SOCKS proxy detection and proxy URL sanitization/redaction to shared network utilities.
  • Unify OpenAI embedding/TTS providers to use the shared create_proxy_client helper.
  • Add unit tests for proxy URL redaction and SOCKS scheme detection; add socksio dependency.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
astrbot/core/utils/network_utils.py Adds SOCKS detection, proxy URL sanitization, and redacted logging for connection failures / proxy usage.
astrbot/core/provider/sources/openai_embedding_source.py Switches to create_proxy_client for consistent proxy behavior and logging.
astrbot/core/provider/sources/openai_tts_api_source.py Switches to create_proxy_client for consistent proxy behavior and logging.
tests/unit/test_network_utils.py Adds regression tests for proxy credential masking and SOCKS scheme detection.
pyproject.toml Adds socksio dependency to support SOCKS proxies with httpx.

@whatevertogo whatevertogo changed the title fix: 添加 SOCKS 代理支持并提供友好的错误提示 fix: SOCKS 代理支持与 asyncio 事件循环修复 Mar 5, 2026
@whatevertogo whatevertogo force-pushed the fix/5757-socks-proxy-support branch from be0097c to 729363f Compare March 5, 2026 14:32
@whatevertogo whatevertogo changed the title fix: SOCKS 代理支持与 asyncio 事件循环修复 fix: 添加 SOCKS 代理支持并脱敏代理凭据 Mar 5, 2026
@whatevertogo whatevertogo force-pushed the fix/5757-socks-proxy-support branch from 26d801b to 729363f Compare March 5, 2026 14:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:core The bug / feature is about astrbot's core, backend size:L This PR changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]获取模型列表失败: Using SOCKS proxy, but the 'socksio' package is not installed. Make sure to install httpx using pip install httpx[socks].

2 participants