MCP / IPC 安全模型
本页说明支持transcribe MCP 工具与 echophrase transcribe CLI 指令的
本机 IPC 服务器的安全设计 - 也就是 Echophrase 让 AI 代理与桌面应用程序
沟通的部分。本页写给希望自行检验设计,而非只听我们说法的读者。以下每项
说法都已对照目前的源代码核实,并附上文件与行号,方便您自行查证。
本页描述 CLI/MCP 进程与正在运行的桌面应用程序之间的传输方式。其余四个
MCP 工具(
record、stop、status、transcode)完全不会用到这个服务器 -
它们完全在 CLI 进程内对本机文件运作。工具总览请参阅 MCP 服务器。仅绑定本机,永不对外
IPC 服务器明确且仅绑定127.0.0.1:
src-tauri/src/ipc/mod.rs:124-126)。代码中没有任何路径会绑定
0.0.0.0 或其他界面 - 模块本身的文档注解也载明同样的限制
(src-tauri/src/ipc/server.rs:3:「仅绑定 127.0.0.1(绝不使用
0.0.0.0)」)。
这具体代表什么:这个 socket 只有在同一台机器、同一个操作系统用户工作阶段
下运行的进程才能连上(loopback 界面不会对外部主机在您的区网或互联网上
公开,而典型的操作系统层级 socket 权限,也代表共用机器上的其他操作系统
用户账号无法连接)。除了「已经以您的身份在您机器上运行的软件」之外,
没有任何东西能连到 1424 端口。相较于绑定在可路由界面上的服务器,这是实质上
更小的攻击面:没有连接端口转发设置错误、没有路由器 UPnP 漏洞、也没有可能
意外暴露的云端元数据端点。
持有者权杖:每次启动全新产生,停止即失效
每个POST /v1/transcribe 请求都必须带有
Authorization: Bearer <token>。检查采用常数时间比对,而非单纯的 ==,
目的正是避免比对过程出现计时侧通道
(src-tauri/src/ipc/server.rs:100-135,constant_time_eq)。不需验证的
GET /v1/health 路由只回报应用程序版本 - 从不透露任何关于权杖或用户的
信息,因此探测这个端口不会得到「应用程序正在运行」以外的任何情报
(src-tauri/src/ipc/server.rs:15-16, 65-71)。
权杖的生命周期:
- 每次启动服务器都全新产生。
start()每次被调用时都会调用write_fresh_token(),从rand::thread_rng()取出 32 个随机字节并 以十六进位编码 - 不是安装时产生一次,也不会跨重启重复使用 (src-tauri/src/ipc/mod.rs:107-133, 176-194)。 - 产生后立即以仅拥有者可读写的权限写入(Unix 上为
0o600) (src-tauri/src/ipc/mod.rs:187-191)。(目前 Windows 与 macOS 依赖的是 每位用户设置档目录的默认 ACL,而非此程序路径中明确的等效 chmod 调用 - 详见下方「限制」一节。) - 存放位置与 CLI 读取的相同每位用户数据目录 -
<平台数据目录>/echophrase-cli/run/ipc-token(src-tauri/src/ipc/paths.rs:76-105)。桌面应用程序与 CLI 各自独立 解析这个路径,但刻意设计为完全一致,避免任一方依赖另一方的 crate。 - 服务器停止时即移除,而非放着不管:
stop()在返回前会调用clear_token_best_effort()(src-tauri/src/ipc/mod.rs:161-166, 200-207),强制结束时也会运行同样的清除。这一点值得特别强调: 停止的服务器是无法验证的,而不只是无法连接。即使有东西缓存了 前一个工作阶段的有效权杖,只要应用程序(或仅是开关)关闭,该权杖 立刻失效,因为支撑它的权杖文件已不存在,且下次start()会产生 完全无关的新权杖。 - CLI 自己的读取路径,会把「找不到权杖文件」与「无法连接」视为同一种
情况,回报同一则友好消息,而非原始的连接错误(
cli/src/ops.rs:316-322)。
明确选择加入,并在服务器端强制运行
服务器不会因为应用程序启动就自动开始监听。spawn() 依序检查两个条件,
两者皆成立才会启动(src-tauri/src/ipc/mod.rs:67-88):
- 目前工作阶段为 Premium 方案(
is_premium_tier()),且 - 设置中的 MCP 服务器选项已明确激活。
false(src-tauri/src/settings/mod.rs:575,注解为
「默认关闭(Pro 限定)」),且 Pro 检查采取失败即拒绝的原则:若尚无
应用程序句柄或工作阶段,is_premium_tier() 会返回 false
(src-tauri/src/ipc/mod.rs:95-101) - 用代码自己的话说,就是「宁可
不公开本机服务器」。
要判断这是真正的闸门还是只是界面上的开关,有两个细节很重要:
- 这个闸门是在 Rust 端强制运行,而非前端。支持设置开关的 Tauri 指令
set_mcp_server_enabled,会在打开设置前于服务器端重新检查 Pro 方案, 不受前端所认为的状态影响(src-tauri/src/settings/mod.rs:840-857)。 - 手动编辑设置档尝试绕过同样行不通:用于 YAML 导入/协调的通用设置修补
路径,在启动服务器前会再次检查 Pro 方案,正是为了封堵这条路径
(
src-tauri/src/settings/mod.rs:802-814,注解:「免费方案用户 导入/编辑 yaml 打开此设置,绝不能真的打开这个端口」)。
五个工具能做什么、不能做什么
MCP 服务器(echophrase mcp)仅公开五个工具,通过 #[tool_router]
定义在固定方法集合上(cli/src/commands/mcp.rs:93-156):record、
stop、status、transcode、transcribe。内部还有第六个子指令
worker,用来运行背景录音进程,但从未注册为工具
(cli/src/commands/mcp.rs:6-9、cli/README.md:150) - 代理无法调用任何
工具对应到它。
已核实的能力边界:
- 无法任意读取文件。
transcribe接受一个wav_path字符串,但 HTTP 处理常式在碰触转录流程前会先验证:必须能通过canonicalize()解析 (这也会消除任何符号链接的花招)、必须存在、必须是文件、且扩展名 必须是.wav,否则请求会在任何文件内容被模型读取前,直接被拒绝为400 Bad Request(src-tauri/src/ipc/server.rs:163-186,validate_wav_path)。回应内容仅有转录文字,绝不包含文件内容、 目录列表,或任何其他文件系统信息。 - 无法运行代码或调用 shell。 HTTP 路由器(
src-tauri/src/ipc/server.rs) 与工具路由器(cli/src/commands/mcp.rs)都没有任何程序路径会启动子进程、 将字符串当作代码运行,或把用户输入传给 shell。CLI 中唯一与外部进程 的交互,是它自己为了record/stop而产生的录音背景工作进程, 且不接受任何来自网络的未受信任输入。 record/stop/status/transcode从不触及网络。 它们完全通过crate::ops(cli/src/ops.rs)对本机文件与本机cpal音频设备运作, 与桌面应用程序或其 IPC 服务器是否正在运行无关。只有transcribe会与桌面应用程序沟通,且是通过上述持有者权杖保护的本机连接进行。transcribe的请求内容是文件路径,而非音频本身。 CLI 读取权杖后, 会将{"wav_path": "<本機路徑>"}以 POST 送至http://127.0.0.1:1424/v1/transcribe(cli/src/ops.rs:315-329)。 桌面进程会自行从磁盘读取 WAV 字节;音频内容在整个往返过程中都 不会经由 socket 串行化传输,来回只有一个路径字符串与一段转录文字。
音频与转录结果会流向哪里
转录作业在已经运行中的桌面应用程序进程内进行,使用它已加载的后端 (Candle、ONNX/parakeet-rs)在本机 GPU/CPU 上推理 - 与桌面界面本身的 听写功能使用完全相同的程序路径。转录后端的任何实作中都没有 HTTP 用户端 (src-tauri/src/transcription/{mod,candle_whisper,onnx_backend,parakeet}.rs
除了文档注解中的网址外,不含任何网络调用),因此在这个流程中,音频、
路径或最终文字都不会发送到任何 Echophrase 服务器。这里涉及的唯一网络
流量,就是同一台机器上 CLI/MCP 进程与桌面应用程序之间的 loopback HTTP
请求。
这与产品更广泛的设计原则一致(与 MCP 无关):语音处理在本机完成,
Echophrase 的服务器端仅用于验证与订阅管理,绝不处理音频。
诚实面对限制
- 权杖文件的保护程度,仅等同于您的操作系统账号。 它的权限比特
(Unix 上为
0600)能阻挡共用机器上其他用户读取,但任何以您 的身份运行的进程 - 行为异常的浏览器扩充功能、其他应用程序中受感染的 依赖项、恶意软件,或您已授予广泛文件系统访问权的另一个 AI 代理 - 都能读取您终端机能读取的同一个文件。这与您的操作系统用户账号属于 相同的信任边界,而非更强的边界。若您不信任以您身份运行的任意本机 进程,那么也不该信任这个权杖文件对它们保密 - 而这其实是几乎所有采用 「本机权杖 over loopback」设计的开发工具都有的共通事实,并非 Echophrase 特有。 - 我们未验证 Windows/macOS 的文件 ACL。
0o600权限设置调用是#[cfg(unix)]条件式编译(src-tauri/src/ipc/mod.rs:187-191);在 Windows 与 macOS 上,权杖文件继承的是您每位用户数据目录的默认 操作系统权限,我们在此并未独立审核。 - 硬崩溃可能让过期的「running」状态文件残留。 用来回报服务器
「为何」停止服务的状态档(
not_pro/disabled/running), 只会在正常关闭时清除;若进程硬崩溃,可能留下显示"running"的 状态,即使进程实际上已不存在。这不会造成安全性后果 - 真正把关请求的 是权杖文件,已死亡的进程无论如何都无法回应请求 - 但既然我们说过会 诚实揭露落差而非只讲保证,这一点值得一提 (src-tauri/src/ipc/mod.rs:252-265)。 - 本页描述的是我们于 2026-07-30 读取的代码。 撰写本文时 IPC 模块正在积极开发中(正在进行的变更,是为了让 CLI 错误消息更友好而 添加状态档);本页在设计层级的说法(仅本机、每次启动全新权杖、 停止即移除、Pro 限定、失败即拒绝)是我们预期会保持稳定的部分, 但请将具体行号视为当下快照,而非永久保证。
MCP 服务器
返回工具总览与设置说明。