首页
/ RuFlo 私有网络 MCP 隧道:在 HuggingFace Chat UI 中安全启用 HTTP 内部 MCP 服务的构建期补丁方案

RuFlo 私有网络 MCP 隧道:在 HuggingFace Chat UI 中安全启用 HTTP 内部 MCP 服务的构建期补丁方案

2026-09-09 21:17:34作者:董斯意

导读:本文围绕 RuFlo 仓库中的 ADR-032: RVF Private Network MCP Tunnel 展开,剖析 HuggingFace Chat UI 因 SSRF 防护强制 MCP 服务 URL 必须为 HTTPS,与 Docker Compose 私有网络内 http://mcp-bridge:3001/mcp 内部通信产生的冲突,以及 RuFlo 采用的构建期补丁(RVF-inspired private tunnel pattern)化解之道。读完你将掌握该补丁的完整实现(shell 脚本 + Dockerfile + Compose 配置)、安全模型与验证方法,并能将同一思路复用到任何受 HTTPS-only 校验约束的容器化内部服务通信场景。

一、问题背景:HTTPS-only 校验 vs 私有 Docker 网络

HuggingFace Chat UI(RuFlo 的 Chat UI 基于其 ruvocal 分支)在服务端对 MCP Server 的 URL 做了严格的 SSRF 防护:通过 urlSafety.isValidUrl() 只允许 https: 协议的 MCP 地址。而在 Docker Compose 容器化部署中,MCP Bridge 运行在私有 Docker 网络上,其地址是 http://mcp-bridge:3001/mcp,该地址不暴露到公网

这形成了一个典型冲突:

  • 安全控制(HTTPS-only)阻止了合法的内部服务通信
  • 而内部通信本身并不需要 TLS——它永远不会离开 Docker 网络。

RuFlo 的对应实现位于 urlSafety.tsisValidUrl()localhost127.0.0.1::1host.docker.internal 及无点号(Docker 内部服务名)的主机名放行 HTTP,但对普通公网主机名仍强制 https:。ADR-032 描述的正是针对 HuggingFace Chat UI 上游构建产物的同类约束,如何在不改动上游代码、不引入 HTTPS 基础设施的前提下解除。

二、决策:RVF 启发的私有网络隧道模式

ADR-032 的决策是:采用一种受 RVF 启发的私有隧道模式(RVF-inspired private tunnel pattern)——在构建期修补 Chat UI 的 URL 安全检查,允许 HTTP 用于由管理员配置、运行在私有容器网络上的 MCP_SERVERS 地址。

"隧道"在这里并非创建真正的加密隧道,而是放宽协议校验边界,使私有网络内部合法的 HTTP 流量可以通过,同时保留面向公网 URL 的 IP/SSRF 防护。其架构如下:

┌──────────────────────────────────────────────────────────────┐
│                    Private Docker Network                      │
│                                                                │
│  ┌──────────────┐   HTTP (private)   ┌──────────────────────┐ │
│  │  Chat UI     │──────────────────►│  MCP Bridge           │ │
│  │  :3000       │   MCP JSON-RPC    │  :3001                │ │
│  │              │                   │                       │ │
│  │  RVF Patch:  │   /chat/completions│  ├─ /mcp (tools)     │ │
│  │  Allow HTTP  │──────────────────►│  ├─ /models           │ │
│  │  for private │                   │  ├─ /chat/completions │ │
│  │  network     │                   │  └─ /health           │ │
│  └──────────────┘                   └──────────────────────┘ │
│         │                                     │               │
│         └──── Not exposed to internet ────────┘               │
└──────────────────────────────────────────────────────────────┘
         │
    Port 3000 (only this is exposed to host)

该架构与 Docker 部署指南 中的拓扑一致:Chat UI(:3000)与 MCP Bridge(:3001)同处私有网络,Chat UI 将所有模型请求通过 OPENAI_BASE_URL=http://mcp-bridge:3001 转发给 Bridge,Bridge 再按模型名解析到 OpenAI / Gemini / OpenRouter 上游(见 docker-compose.ymlchat-ui 服务配置)。

RVF 段映射

ADR-032 将这一补丁与 RuFlo 的 RVF(RVF Manifest 分段)体系做了对应,帮助理解各层职责:

RVF Segment Application
WASM_SEG (0x10) Lightweight query microkernel — MCP bridge acts as the runtime
CRYPTO_SEG (0x0C) Request signing between kernel and bridge (optional)
META_IDX_SEG (0x0D) Tool registry cache in bridge /models endpoint
KERNEL_SEG (0x0E) Docker container as execution boundary

其中 KERNEL_SEG 的含义很关键:容器即执行边界——正是 Docker 网络隔离保证了这个"放宽协议校验"的补丁不会扩大公网攻击面。

三、安全模型:放宽协议、不放宽 IP

ADR-032 明确列出该补丁的五条安全边界,这是理解整个方案正确性的核心:

  1. 仅私有网络可达 —— MCP Bridge(mcp-bridge:3001)只在 Docker 网络内可访问,公网不可达;
  2. 管理员配置 —— MCP_SERVERS 由部署者在 docker-compose.yml 中设置,不是最终用户输入
  3. IP 安全检查保留 —— 补丁仅放宽协议检查(允许 HTTP),用户 URL 的内部 IP / 回环地址绕过检查仍然生效
  4. 构建期补丁 —— 在 Docker 镜像构建时应用,而非运行时,可在 Dockerfile 中审计;
  5. Cloud Run 不受影响 —— Cloud Run 部署使用真实 HTTPS URL,无需此补丁。

这一模型与 RuFlo 上游 ruvocal 分支的 urlSafety.tsassertSafeIp() 的职责一脉相承:在连接时通过 undici 自定义 DNS lookup 校验解析后的 IP 是否为内网地址,防止 TOCTOU DNS 重绑定攻击。换言之,协议层放宽与 IP 层收紧是并行的两条防线

四、实现剖析:三件套的完整代码

ADR-032 的实现由三个文件构成,仓库中均有实际落地版本,且比 ADR 记录更完整。

4.1 补丁脚本 patch-mcp-url-safety.sh

ADR 中记录的脚本是简化版,仓库实际版本 ruflo/src/chat-ui/patch-mcp-url-safety.sh 更健壮,包含文件查找失败时的优雅降级:

#!/bin/sh
# RVF Security Patch — Allow private network MCP connections
#
# HF Chat UI enforces HTTPS-only for MCP server URLs to prevent SSRF.
# In containerized deployments, MCP servers run on the private Docker
# network (not exposed to the internet). This patch allows HTTP for
# admin-configured MCP_SERVERS URLs while maintaining SSRF protection
# for user-provided URLs.

URLSAFETY_FILE=$(find /app/build/server -name "urlSafety-*.js" | head -1)

if [ -z "$URLSAFETY_FILE" ]; then
  echo "[rvf-patch] urlSafety file not found, skipping"
  exit 0
fi

# Allow http: protocol in addition to https:
sed -i 's/if (url\.protocol !== "https:")/if (url.protocol !== "https:" \&\& url.protocol !== "http:")/' "$URLSAFETY_FILE"

# Allow localhost for container-internal MCP servers
sed -i 's/if (hostname === "localhost")/if (false \&\& hostname === "localhost")/' "$URLSAFETY_FILE"

echo "[rvf-patch] Patched $URLSAFETY_FILE for private network MCP"

两处 sed 的含义:

  • 第一处:把 if (url.protocol !== "https:")(HTTP 一律拒绝)改为 if (url.protocol !== "https:" && url.protocol !== "http:")(HTTP/HTTPS 均放行),即协议校验从"仅 HTTPS"放宽为"HTTPS 或 HTTP";
  • 第二处:把 if (hostname === "localhost") 改为 if (false && hostname === "localhost"),使"localhost 直接拒绝"的分支恒为假,从而允许容器内部服务名(含 localhost 与 Docker 服务名)通过。

注意脚本通过 find /app/build/server -name "urlSafety-*.js" 动态定位 SvelteKit 构建产物中的哈希文件名(如 urlSafety-<hash>.js),这正是 ADR-032 "Consequences" 中提醒的维护点:若上游 Chat UI 修改了 urlSafety 文件的命名方式,此查找模式需要同步更新。

4.2 构建期接入 Dockerfile

仓库实际版本 ruflo/src/chat-ui/Dockerfileghcr.io/huggingface/chat-ui-db:latest 为基础镜像,在构建期执行补丁:

FROM ghcr.io/huggingface/chat-ui-db:latest

# Switch to root for patching and file operations
USER root

# Bake .env.local with MODELS config (too large for Cloud Run env vars)
COPY dotenv-local.txt /app/.env.local

# RVF Security Patch — allow private Docker network MCP connections.
# HF Chat UI enforces HTTPS for MCP URLs (SSRF protection).
# In containerized deployments, MCP bridge runs on private Docker network.
# This patch allows HTTP for admin-configured MCP_SERVERS only.
COPY patch-mcp-url-safety.sh /tmp/patch-mcp-url-safety.sh
RUN sh /tmp/patch-mcp-url-safety.sh && rm /tmp/patch-mcp-url-safety.sh

# Copy branded welcome GIF into the SvelteKit static asset directory.
COPY static/chatui/omni-welcome.gif /app/build/client/chatui/omni-welcome.gif
COPY static/chatui/omni-welcome.gif /app/static/chatui/omni-welcome.gif

# Copy PWA icon (fixes 404 for /chat/chatui/icon-144x144.png)
COPY static/chatui/icon-144x144.png /app/build/client/chatui/icon-144x144.png
COPY static/chatui/icon-144x144.png /app/static/chatui/icon-144x144.png

# Switch back to non-root user
USER 1000

三个关键设计点:

  1. USER root → 执行补丁 → USER 1000:补丁需要写 /app/build/server 下的构建产物,因此临时切到 root,执行完立即切回非 root 用户(UID 1000),最小化特权暴露;
  2. RUN ... && rm:补丁脚本执行后立即从镜像中删除,不留残余脚本;
  3. 构建期注入:补丁在镜像构建时固化,运行时无任何动态修改,可通过 docker history / Dockerfile 直接审计。

4.3 运行时配置 docker-compose.yml

ADR 中给出一行配置,仓库实际版本 ruflo/docker-compose.yml 更完整,将 Chat UI 与 Bridge 同置私有网络,并通过 MCP_SERVERS 注入多个按工具组拆分的端点:

  chat-ui:
    build:
      context: ./src/ruvocal
      dockerfile: Dockerfile
      args:
        INCLUDE_DB: "false"
    restart: unless-stopped
    expose:
      - "3000"
    environment:
      DOTENV_LOCAL: |
        MONGODB_URL=mongodb://${MONGO_INITDB_ROOT_USERNAME:-ruflo}:${MONGO_INITDB_ROOT_PASSWORD}@mongodb:27017/${MONGODB_DB_NAME:-chat-db}?authSource=admin
        MONGODB_DB_NAME=${MONGODB_DB_NAME:-chat-db}
        PUBLIC_APP_NAME=${BRAND_NAME:-RuFlo}
        PUBLIC_APP_DESCRIPTION=${BRAND_DESCRIPTION:-Enterprise AI Agent Orchestration Platform}
        PUBLIC_APP_ASSETS=chatui
        PUBLIC_ORIGIN=
        LLM_SUMMARIZATION=true
        ENABLE_DATA_EXPORT=true
        ALLOW_IFRAME=true
        OPENAI_BASE_URL=http://mcp-bridge:3001
        OPENAI_API_KEY=${OPENAI_API_KEY:-sk-placeholder}
        MCP_SERVERS=[{"name":"Core Tools","url":"http://mcp-bridge:3001/mcp/core"},{"name":"Intelligence & Learning","url":"http://mcp-bridge:3001/mcp/intelligence"},{"name":"Agents & Orchestration","url":"http://mcp-bridge:3001/mcp/agents"},{"name":"Memory & Knowledge","url":"http://mcp-bridge:3001/mcp/memory"},{"name":"Dev Tools & Analysis","url":"http://mcp-bridge:3001/mcp/devtools"}]
        COOKIE_SECURE=${COOKIE_SECURE:-false}
        COOKIE_SAMESITE=${COOKIE_SAMESITE:-lax}
    depends_on:
      - mongodb
      - mcp-bridge

几点值得注意:

  • expose: - "3000"(而非 ports:Chat UI 仅暴露给 Docker 网络内部,宿主机访问由 nginx 服务(ports: "3000:3000")代理——这与 ADR-032 架构图中"只有 3000 端口暴露给宿主机"一致;
  • MCP_SERVERS 是管理员配置:所有 URL 都是 http://mcp-bridge:3001/mcp/<group> 形式的内部地址,这正是补丁要放行的对象;
  • OPENAI_BASE_URL=http://mcp-bridge:3001:Chat UI 的所有模型请求同样走私有网络的 Bridge,Bridge 侧再由 resolveProvider(model) 按模型名前缀路由到 OpenAI / Gemini / OpenRouter(见 mcp-bridge/index.jsPROVIDER_ROUTES);
  • 同一 Compose 文件中,mcp-bridge 服务默认以 MCP_BIND_HOST=127.0.0.1 绑定回环、read_only: true 只读根文件系统 + /tmp tmpfs,并配置了 /health 健康检查,安全面控制贯穿始终。

4.4 Bridge 侧的多端点结构

补丁放行的 http://mcp-bridge:3001/mcp/<group> 并非单一端点。从 mcp-bridge/index.js 可以看到,Bridge 为每个启用的工具组注册了独立 MCP 端点:

  • POST /mcp/<group> —— 组内工具的 JSON-RPC 处理(initialize / tools/list / tools/call / notifications/initialized);
  • GET /mcp/<group> —— SSE 端点,用于 MCP Streamable HTTP 会话发现;
  • DELETE /mcp/<group> —— 会话清理(Codex / RMCP 客户端在关闭时发送);
  • GET /mcp-servers —— 返回启用的 MCP Server 列表 JSON,供 Chat UI 配置使用。

工具组按前缀过滤(如 intelligence 组对应 ruvector 后端的 hooks_ 前缀工具),每个组的启用状态由 MCP_GROUP_* 环境变量控制(默认开启 intelligence/agents/memory/devtools,其余默认关闭),AI 只能看到已启用组的工具。

五、备选方案回顾:为什么选 sed 补丁

ADR-032 记录并否决了四条备选路径,理解这些取舍有助于评估补丁方案的边界:

  1. Caddy HTTPS sidecar —— 引入 TLS 证书与额外容器,对内部通信属过度设计,否决;
  2. stdio MCP transport —— HF Chat UI 不支持基于命令的 MCP(只支持 URL),不可行;
  3. 跳过 MCP 只用 tool-calling —— 会失去 MCP 工具发现机制和 UI 中的工具侧边栏,功能降级;
  4. Fork HF Chat UI —— 维护负担大,构建期 sed 补丁更简单。

最终选择"构建期 sed 补丁"的核心理由是:侵入面最小、可审计性最强(Dockerfile 可见)、且与上游保持同步升级能力——补丁以精确字符串替换方式针对 urlSafety 构建产物,上游升级时只需重新评估替换模式是否仍然匹配。

六、验证:补丁生效的端到端证据

ADR-032 给出的验证输出展示了补丁生效后的完整链路:

[MCP] Loaded 1 server(s): AI Assistant Tools
Listening on http://0.0.0.0:3000

Models: gemini-2.5-pro, gemini-2.5-flash, gpt-4.1, gpt-4.1-mini, gpt-4o, gpt-4o-mini, o3-mini, o1-mini
Bridge health: ok (3 tools: search, web_research, system_guide)
Chat completions: working via Gemini proxy

解读各行的验证含义:

  • [MCP] Loaded 1 server(s) —— Chat UI 成功从 MCP_SERVERS 加载了 http://mcp-bridge:3001/mcp(补丁放行 HTTP 协议的直接证据);
  • Listening on http://0.0.0.0:3000 —— Chat UI 正常监听;
  • Bridge health: ok (3 tools: ...) —— Bridge 的 /health 检查通过,tools/list 返回了工具清单;
  • Chat completions: working via Gemini proxy —— OPENAI_BASE_URL=http://mcp-bridge:3001 的模型代理链路连通。

此外,仓库为 MCP Bridge 准备了多套安全与运行时测试,可用于回归验证补丁相关的安全边界:test-security-lock.js(锁定 Node 版本与安全依赖)、test-runtime-security.mjs(运行时安全校验)、test-harness.js(功能测试框架)。这些测试配合 Docker Compose 的 /health 健康检查(interval: 30s, timeout: 5s, retries: 3),构成"部署即验证"的闭环。

七、影响与运维注意事项

ADR-032 的 Consequences 小节明确了该方案的长期影响与责任边界:

  • MCP 工具可在 Docker Compose 中工作而无需任何 HTTPS 基础设施 —— 这是本 ADR 的直接收益;
  • 维护耦合点:若上游 HF Chat UI 修改 urlSafety 文件命名或校验逻辑,构建期补丁必须同步更新(脚本中 find /app/build/server -name "urlSafety-*.js" 与两处精确 sed 替换模式即为需要关注的锚点);
  • Cloud Run 部署不受影响:其使用真实 HTTPS URL,无需(也不应)应用此补丁;
  • 安全姿态:HTTP 仅在私有 Docker 网络上被允许,公网场景下 HTTPS-only 约束依然完整保留

运维层面还需注意:此补丁的合理性完全依赖"私有网络 + 管理员配置"两个前提。若将 Chat UI 容器暴露到公网且允许用户配置 MCP 地址,则该补丁会削弱 SSRF 防护,此时应回退到仅 HTTPS 校验或采用 docker-compose.public.yml 描述的公网部署模式(Bridge 需绑定 MCP_BIND_HOST=0.0.0.0 并设置至少 32 字节的 MCP_AUTH_TOKEN,否则启动即退出)。

八、延伸阅读

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.16 K
2.78 K
kernelkernel
deepin linux kernel
C
34
18
docsdocs
暂无描述
Markdown
904
5.83 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
933
1.86 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
862
1.36 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.96 K
1.03 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.38 K
1.47 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
535
606
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
549
398
leetcodeleetcode
🔥LeetCode solutions in any programming language | 多种编程语言实现 LeetCode、《剑指 Offer(第 2 版)》、《程序员面试金典(第 6 版)》题解
Markdown
77
23