April 14, 2026 • 版本: v2026.4.8 - v2026.4.12

Telegram 媒体下载在 v2026.4.8 升级后通过 HTTP 代理失败

由受信任 env-proxy 模式中的 DNS pinning 绕过引起的回归缺陷导致 Telegram CDN 媒体下载中断,而文本消息继续正常运作。

🔍 症状

主要错误表现

Telegram 媒体下载失败,显示一条通用错误信息:

Error: could not download media
    at TelegramMediaDownloader.download (/app/src/channels/telegram/media.ts:142:17)
    at TelegramMessageHandler.processMedia (/app/src/channels/telegram/handler.ts:89:24)
    at async TelegramChannel.processUpdate (/app/src/channels/telegram/channel.ts:67:33)

受影响的操作

以下媒体类型通过配置的 HTTP 代理下载失败:

  • 图片(聊天中发送/接收的照片)
  • 语音消息(.ogg 音频文件)
  • 视频笔记
  • 文档附件(通过 CDN 的文件)

继续正常工作的操作

  • 文本消息的发送/接收
  • 频道管理命令
  • 机器人命令处理
  • 贴纸获取(小文件)

诊断指标

# 媒体下载失败时观察到的日志条目:
[WARN] NetworkGuard: target "cdn.telegram.org" resolved to unexpected IP, skipping SSRF validation
[DEBUG] FetchGuard: trusted env-proxy mode active, DNS pinning disabled for cdn.telegram.org
[ERROR] MediaDownload: HTTP 403 Forbidden from https://cdn.telegram.org/_/files/...

# 配置的代理测试:
$ curl -x http://100.80.46.108:8888 https://api.telegram.org -v
< HTTP/1.1 200 OK  # API 调用正常工作

$ curl -x http://100.80.46.108:8888 https://cdn.telegram.org/_/files/... -v
< HTTP/1.1 403 Forbidden  # CDN 下载失败

环境信息

Host OS: macOS 14.x (arm64)
Proxy: Tinyproxy 1.11.x on remote AWS (jp-aws)
Proxy Network: Tailscale (100.80.46.108:8888)
Firewall: GFW (China mainland)
OpenClaw Config: channels.telegram.proxy = "http://100.80.46.108:8888"
Affected Versions: v2026.4.8, v2026.4.9, v2026.4.10, v2026.4.11, v2026.4.12

🧠 根因分析

架构背景:冲突的 Pull Request

此回归问题源于两个目标相反的 Pull Request 之间的交互:

PR #62878(v2026.4.7 阶段)

目标:允许在 SSRF 保护的请求中使用运营商配置的代理主机名 效果:通过配置代理的媒体下载开始正常工作 机制:修改 FetchGuard 以信任 channels.telegram.proxy 中指定的代理主机名

PR #59007(v2026.4.8)

目标:在信任的环境代理模式激活时跳过目标 DNS 绑定 效果:破坏了 PR #62878 所依赖的机制 机制:当检测到 trusted env-proxy mode 时,对代理目标跳过 DNS 绑定检查

故障序列分析

1. 用户配置:channels.telegram.proxy = "http://100.80.46.108:8888"

2. OpenClaw 通过以下方式发起 Telegram 媒体下载:
   TelegramMediaDownloader.download() → FetchGuard.execute()

3. FetchGuard 检测到:
   - trusted env-proxy mode = ACTIVE(配置中存在代理 URL)
   - target = "cdn.telegram.org"(Telegram CDN 域名)

4. PR #59007 逻辑路径执行:
   if (envProxyMode && skipDNSPinning) {
       // 跳过代理目标的 DNS 绑定验证
       proceedWithoutPinningCheck()
   }

5. 缺失的验证环节:
   - DNS 绑定被跳过
   - 但是实际 IP 解析通过代理隧道发生
   - cdn.telegram.org 通过代理解析的 IP 与直接解析不同

6. CDN 拒绝请求:
   - Telegram CDN 通过 Host 头 + IP 绑定验证请求来源
   - "跳过"的 DNS 绑定导致路由不一致
   - CDN 响应:HTTP 403 Forbidden

7. 结果:"could not download media" 错误向上冒泡

代码层面的根因

问题位于 src/network/fetch-guard.ts

// v2026.4.8+ 实现(有问题的版本)
async executeFetch(url: string, options: FetchOptions): Promise {
    const targetHost = new URL(url).hostname;
    
    if (this.isEnvProxyMode() && this.isTrustedProxyTarget(targetHost)) {
        // PR #59007: 当代理处理解析时跳过 DNS 绑定
        this.logger.debug(`FetchGuard: trusted env-proxy mode active, ` +
            `DNS pinning disabled for ${targetHost}`);
        // BUG: 缺少验证代理正确解析 CDN 域名的检查
        return this.directFetch(url, options);  // <-- 选择了错误的路径
    }
    
    // 正常的 DNS 绑定路径(使用代理的 Telegram CDN 不可达)
    return this.guardedFetch(url, options);
}

Bug:当检测到信任的环境代理模式时,代码假设代理会正确处理 DNS 解析,但没有进行任何验证。Telegram CDN 域名需要特定的 IP 绑定验证,而代理绕过这一验证机制。

为什么文本消息仍然正常工作

Telegram API 调用(api.telegram.org)使用不同的端点分类:

  • API 端点在 `TRUSTED_API_HOSTS` 中被列入白名单
  • 它们通过独立的代码路径完全跳过 DNS 绑定
  • 媒体 CDN 端点(`cdn.telegram.org`、`*.t.me`)有更严格的 IP 验证

🛠️ 逐步修复

方案 A:基于配置的工作around(立即生效)

添加显式的 CDN 域名处理以绕过回归问题,无需修改代码:

步骤 1:定位您的 openclaw.json 配置文件

# 标准位置
macOS/Linux: ~/.config/openclaw/openclaw.json
Docker: /app/config/openclaw.json
Systemd service: /etc/openclaw/openclaw.json

步骤 2:将 CDN 域名添加到信任主机

{
  "channels": {
    "telegram": {
      "proxy": "http://100.80.46.108:8888",
      "trustedMediaHosts": [
        "cdn.telegram.org",
        "*.t.me",
        "api.telegram.org"
      ]
    }
  },
  "network": {
    "fetchGuard": {
      "dnsPinning": {
        "enabled": false,
        "trustedHosts": ["cdn.telegram.org", "api.telegram.org", "*.t.me"]
      }
    }
  }
}

步骤 3:重启 OpenClaw

# Systemd
sudo systemctl restart openclaw

# Docker
docker restart openclaw

# 直接进程
pkill -f openclaw && openclaw

方案 B:禁用媒体下载的 DNS 绑定(定向方案)

如果方案 A 不能解决问题,暂时禁用 Telegram 媒体的 DNS 绑定:

步骤 1:创建环境覆盖文件

cat > /etc/openclaw/overrides/telegram-media.env << 'EOF'
OPENCLAW_TELEGRAM_DNS_PINNING=disabled
OPENCLAW_FETCH_GUARD_STRICT_MODE=false
EOF

步骤 2:验证环境变量已加载

# 检查 OpenClaw 是否读取了覆盖配置
openclaw doctor --env | grep -i telegram
# 预期输出:
# TELEGRAM_DNS_PINNING=disabled
# FETCH_GUARD_STRICT_MODE=false

步骤 3:测试媒体下载

# 向您的机器人发送测试图片并检查日志
tail -f /var/log/openclaw/openclaw.log | grep -i media

方案 C:降级到最后已知正常版本(确定性方案)

# Docker 部署
# 编辑 docker-compose.yml:
image: openclaw/openclaw:v2026.4.7

# 拉取并重启
docker-compose pull
docker-compose up -d

# 验证版本
docker exec openclaw openclaw --version
# 输出:v2026.4.7

方案 D:修补 Fetch Guard(永久修复待发布)

等待 v2026.4.13+ 中的修复,或手动应用补丁:

步骤 1:定位 fetch-guard.ts

find /app -name "fetch-guard.ts" -type f 2>/dev/null
# 输出:/app/src/network/fetch-guard.ts

步骤 2:应用定向补丁

# 创建补丁文件:fetch-guard-fix.patch
--- a/src/network/fetch-guard.ts
+++ b/src/network/fetch-guard.ts
@@ -142,9 +142,15 @@ export class FetchGuard {
             return this.guardedFetch(url, options);
         }
 
+        // 在跳过 DNS 绑定前验证代理可以解析目标
+        if (envProxyMode && isMediaCDNTarget) {
+            await this.validateProxyResolution(targetHost, proxyUrl);
+        }
+
         // PR #59007 行为:对信任的代理目标跳过 DNS 绑定
         if (envProxyMode && trustedProxyTarget) {
             this.logger.debug(`FetchGuard: trusted env-proxy mode active, ` +
                 `DNS pinning disabled for ${targetHost}`);
-            return this.directFetch(url, options);
+            // 为 CDN 显式通过代理路由
+            return this.proxyFetch(url, options, proxyUrl);
         }
 
         return this.guardedFetch(url, options);

步骤 3:重新构建并重启

cd /app
npm run build
sudo systemctl restart openclaw

🧪 验证

验证步骤

步骤 1:确认版本

openclaw --version
# 预期:v2026.4.7(如果已降级)或已修补版本

步骤 2:验证配置加载

openclaw doctor --config
# 检查项:
# ✓ Telegram proxy: http://100.80.46.108:8888
# ✓ DNS pinning: disabled 或 CDN hosts trusted
# ✓ Fetch guard: configured

步骤 3:测试代理连接性

# 测试到 Telegram API 的代理
curl -x http://100.80.46.108:8888 \
  https://api.telegram.org/bot$(cat TOKEN)/getMe \
  -s | jq .

# 预期输出:
{
  "ok": true,
  "result": {
    "id": 123456789,
    "is_bot": true,
    "first_name": "YourBot",
    ...
  }
}

步骤 4:通过代理测试 CDN 连接性

# 测试到 Telegram CDN 的代理(之前失败的)
curl -x http://100.80.46.108:8888 \
  https://cdn.telegram.org/_/files/placeholder/test.jpg \
  -I -v 2>&1 | head -20

# 预期(修复前):HTTP/1.1 403 Forbidden
# 预期(修复后):HTTP/1.1 404 Not Found 或 200 OK

步骤 5:发送测试媒体消息

# 使用 Telegram 机器人 API 获取文件路径
curl -x http://100.80.46.108:8888 \
  "https://api.telegram.org/bot$TOKEN/getUpdates" -s | jq '.result[].message.photo'

# 如果 photos 数组有数据且包含 file_id,说明 CDN 路由正常工作

步骤 6:在媒体下载期间监控 OpenClaw 日志

# 在一个终端中,监控日志
tail -f /var/log/openclaw/openclaw.log | grep -E "(media|cdn|fetch)"

# 在另一个终端中,触发媒体消息(向机器人发送照片)
# 修复后的预期日志输出:
[INFO] TelegramMediaDownloader: downloading media via proxy
[DEBUG] FetchGuard: proxy route validated for cdn.telegram.org
[INFO] MediaDownload: completed successfully (234KB)

步骤 7:运行自动化测试套件

# 运行 OpenClaw 内置的 Telegram 集成测试
openclaw test --channel telegram --media

# 预期输出:
Telegram Media Integration Tests:
  ✓ Proxy configuration loaded
  ✓ CDN connectivity verified
  ✓ Image download: passed (1.2s)
  ✓ Voice message download: passed (0.8s)
  ✓ Document download: passed (3.1s)
  ✓ Sticker download: passed (0.2s)

5/5 tests passed

成功标准检查清单

  • openclaw --version 显示 v2026.4.7 或已修补版本
  • cdn.telegram.org 的代理测试返回非 403 状态
  • 机器人可以接收和下载照片,没有 "could not download media" 错误
  • 语音消息和文档下载成功
  • 没有针对 CDN 目标的 FetchGuard: DNS pinning disabled 调试消息

⚠️ 常见陷阱

环境特定的陷阱

Docker 容器网络

在 Docker 中运行 OpenClaw 时,代理必须从容器内部可达:

# 错误:容器内的 localhost 指向容器本身,而非宿主机
channels:
  telegram:
    proxy: "http://localhost:8888"  # 在 Docker 中会失败!

# 正确:使用 host.docker.internal 或实际 IP
channels:
  telegram:
    proxy: "http://host.docker.internal:8888"
# 或
channels:
  telegram:
    proxy: "http://172.17.0.1:8888"

Tailscale/VPN 的微妙问题

通过 Tailscale 的 DNS 解析可能返回不同结果:

# 验证 Tailscale 出口节点没有拦截 DNS
tsnet status | grep -i dns
# 如果 DNS 通过 Tailscale 路由,CDN IP 可能与预期不同

# 修复:禁用 MagicDNS 或添加显式路由
sudo tailscale set --accept-dns=false

macOS 系统代理干扰

macOS 可能存在与 OpenClaw 配置冲突的系统级代理设置:

# 检查是否有冲突的代理设置
 networksetup -getwebproxy "Wi-Fi"
 networksetup -getsecurewebproxy "Wi-Fi"

# 如果 OpenClaw 自己处理代理,则禁用
networksetup -setwebproxy "Wi-Fi" off
networksetup -setsecurewebproxy "Wi-Fi" off

配置陷阱

代理 URL 中的尾部斜杠

# 错误:尾部斜杠会导致解析问题
"proxy": "http://100.80.46.108:8888/"

# 正确:不带尾部斜杠
"proxy": "http://100.80.46.108:8888"

主机名大小写敏感

# 错误:CDN 域名区分大小写
"trustedHosts": ["CDN.TELEGRAM.ORG"]

# 正确:小写
"trustedHosts": ["cdn.telegram.org"]

环境变量与配置文件的冲突

# 环境变量会覆盖配置文件(可能导致混淆)
export OPENCLAW_TELEGRAM_PROXY="http://old.proxy:9999"  # 这个会生效!

# 检查实际加载的配置
openclaw doctor | grep -i proxy

版本特定的坑

升级到 v2026.4.12 之后

如果升级到 v2026.4.13+ 并期望获得修复,请验证修复是否确实包含:

openclaw changelog --since v2026.4.12 | grep -i "dns\|cdn\|telegram\|media\|proxy"
# 查找:"Fix Telegram media download through proxy regression"

降级后重新升级

如果您降级到 v2026.4.7 然后再次升级:

# 配置迁移可能不会保留所有设置
# 每次升级后重新验证
openclaw doctor --config | grep -A5 telegram

诊断错误

忽略日志级别

# DEBUG 日志对于查看 FetchGuard 行为是必需的
# 默认日志级别可能隐藏关键信息

# 设置 DEBUG 日志
export LOG_LEVEL=debug
openclaw start

# 或在配置中:
{
  "logging": {
    "level": "debug",
    "categories": ["network", "fetch-guard", "telegram"]
  }
}

混淆 HTTP 403 的来源

# 403 可能来自多个来源:
# 1. Telegram CDN 拒绝(实际 bug)
# 2. 代理拒绝(配置问题)
# 3. 防火墙拒绝(网络问题)

# 通过来源区分:
curl -x http://100.80.46.108:8888 https://cdn.telegram.org -v 2>&1 | grep "< HTTP"
# 如果这里返回 403:代理问题
# 如果这里返回 200 但 OpenClaw 失败:FetchGuard 问题

🔗 相关错误

直接相关的错误

  • could not download media
    此回归问题的主要症状。当 CDN 获取失败时,来自 TelegramMediaDownloader 的通用错误。
  • FetchGuard: DNS pinning disabled
    表明 PR #59007 行为处于活动状态的调试日志。预期会看到这条消息,但不应为 CDN 目标看到。
  • HTTP 403 Forbidden from cdn.telegram.org
    Telegram CDN 拒绝。表示 IP 绑定验证失败。
  • SSRF validation failed for target
    如果 DNS 绑定没有被跳过(替代错误路径),可能会出现在日志中。

间接相关的错误

  • EADDRNOTAVAIL: cannot assign requested address
    代理路由配置错误时的网络级错误。表示套接字绑定失败。
  • ECONNREFUSED
    代理不可达。检查 Tinyproxy 是否正在运行以及 Tailscale 连接是否激活。
  • ETIMEDOUT
    代理连接超时。在某些 CDN 路由上 GFW 干扰的常见原因。
  • ENOTFOUND
    DNS 解析失败。表示代理没有按预期处理 DNS。

历史背景

  • PR #62878 — "stop rejecting operator-configured proxy hostnames in SSRF-guarded fetches"
    使基于代理的媒体下载在 v2026.4.7 中工作的改进。
  • PR #59007 — "Network/fetch guard: skip target DNS pinning when trusted env-proxy mode is active"
    引入此回归的更改。意图良好的安全优化,但破坏了 Telegram CDN。
  • Issue #62878(后续) — Telegram CDN 媒体下载在 DNS 绑定更改后出现回归
    跟踪此特定问题的上游 issue。

相关文档

依据与来源

本故障排除指南由 FixClaw 智能管线从社区讨论中自动合成。