RuFlo 私有网络 MCP 隧道:在 HuggingFace Chat UI 中安全启用 HTTP 内部 MCP 服务的构建期补丁方案
导读:本文围绕 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.ts:isValidUrl() 对 localhost、127.0.0.1、::1、host.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.yml 中 chat-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 明确列出该补丁的五条安全边界,这是理解整个方案正确性的核心:
- 仅私有网络可达 —— MCP Bridge(
mcp-bridge:3001)只在 Docker 网络内可访问,公网不可达; - 管理员配置 ——
MCP_SERVERS由部署者在docker-compose.yml中设置,不是最终用户输入; - IP 安全检查保留 —— 补丁仅放宽协议检查(允许 HTTP),用户 URL 的内部 IP / 回环地址绕过检查仍然生效;
- 构建期补丁 —— 在 Docker 镜像构建时应用,而非运行时,可在 Dockerfile 中审计;
- Cloud Run 不受影响 —— Cloud Run 部署使用真实 HTTPS URL,无需此补丁。
这一模型与 RuFlo 上游 ruvocal 分支的 urlSafety.ts 中 assertSafeIp() 的职责一脉相承:在连接时通过 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/Dockerfile 以 ghcr.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
三个关键设计点:
USER root→ 执行补丁 →USER 1000:补丁需要写/app/build/server下的构建产物,因此临时切到 root,执行完立即切回非 root 用户(UID 1000),最小化特权暴露;RUN ... && rm:补丁脚本执行后立即从镜像中删除,不留残余脚本;- 构建期注入:补丁在镜像构建时固化,运行时无任何动态修改,可通过
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.js 的PROVIDER_ROUTES);- 同一 Compose 文件中,
mcp-bridge服务默认以MCP_BIND_HOST=127.0.0.1绑定回环、read_only: true只读根文件系统 +/tmptmpfs,并配置了/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 记录并否决了四条备选路径,理解这些取舍有助于评估补丁方案的边界:
- Caddy HTTPS sidecar —— 引入 TLS 证书与额外容器,对内部通信属过度设计,否决;
- stdio MCP transport —— HF Chat UI 不支持基于命令的 MCP(只支持 URL),不可行;
- 跳过 MCP 只用 tool-calling —— 会失去 MCP 工具发现机制和 UI 中的工具侧边栏,功能降级;
- 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,否则启动即退出)。
八、延伸阅读
- ADR-029: HuggingFace Chat UI Cloud Run —— 理解 Cloud Run 场景为何不需要本补丁;
- ADR-033: RuVector / RuFlo MCP 集成 —— Bridge 如何聚合 ruvector 与 ruflo 后端的 MCP 工具;
- ADR-035: MCP 工具组 ——
MCP_GROUP_*工具组开关的设计依据; - Docker 部署指南 —— 完整的 Compose 启动、日志与工具扩展流程;
- docker-compose.yml 与 docker-compose.public.yml —— 私有网络与公网两种部署形态的对比。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust4.21 K637- DDeepSeek-V4.1-FlashDeepSeek-V4.1-Flash 是一个多模态混合专家(MoE)模型,拥有 5520 亿骨干参数,并支持最多一百万 token 的上下文长度。该模型原生支持图像和文本输入,并以自回归方式生成文本Python290
cherry-studio🍒 Cherry Studio 是一款支持多个 LLM 提供商的桌面客户端TypeScript2 K146
hello-agents📚 《从零开始构建智能体》——从零开始的智能体原理与实践教程Python46267
new-apiAI模型聚合管理中转分发系统,一个应用管理您的所有AI模型,支持将多种大模型转为统一格式调用,支持OpenAI、Claude、Gemini等格式,可供个人或者企业内部管理与分发渠道使用。🍥 A Unified AI Model Management & Distribution System. Aggregate all your LLMs into one app and access them via an OpenAI-compatible API, with native support for Claude (Messages) and Gemini formats.Go20043
JeecgBoot🔥企业级低代码平台集成了AI应用平台,帮助企业快速实现低代码开发和构建AI应用!前后端分离架构 SpringBoot,SpringCloud、Mybatis,Ant Design4、 Vue3.0、TS+vite!强大的代码生成器让前后端代码一键生成,无需写任何代码! 引领AI低代码开发模式: AI生成->OnlineCoding-> 代码生成-> 手工MERGE,显著的提高效率,又不失灵活~Java33951