第 11 章:安全、配置、升级与 CLI —— 桌面应用的真实边界¶
11.1 密钥不属于 WebView localStorage¶
Provider 配置可以被 Zustand 管理,但 API key 的持久化路径会被剥离,重新加载时由 Rust get_provider_keys 从 OS keyring seed 回内存。远程 provider 注册时再把 key chain 传给 Rust AppState。
用户输入 key
→ UI store(短暂)
→ Tauri set_secret / keyring
→ reload
→ get_provider_keys
→ runtime provider object
→ register_provider_config
这条路径避免把 secret 写进普通 settings JSON,但“运行时内存里仍存在 secret”是不可消除的事实。日志、错误、诊断 bundle 和 provider list 都必须避免回显。
11.2 App data folder 与配置文件¶
Rust setup.rs 负责默认数据目录、配置文件迁移和 JAN_DATA_FOLDER/平台环境变量解析。配置包含用户选择的数据 folder;改变 folder 时会复制文件,并阻止把新目录设置为旧目录的子目录,以免递归复制。
这是一个经常被低估的安全问题:路径变更既是用户功能,也是数据迁移、权限和磁盘空间问题。代码使用 PathBuf 和显式目录检查,而不是在 Web 层拼接字符串。
11.3 Tauri capability 与 CSP¶
tauri.conf.json 同时配置 frontendDist/devUrl、capabilities、CSP、asset protocol 和 updater endpoint。CSP 允许本地 asset/ipc、localhost/ws 和指定外部域;plugin capabilities 决定窗口能否访问日志、system monitor、MLX 等资源。
从安全模型上看,Jan 有三道边界:
- WebView 的 CSP 与 Tauri permission。
- Rust command 的参数校验与路径/host 限制。
- 子进程/远程服务器本身的权限与 token。
任何一道边界都不能代替另一道。比如 CSP 不能阻止 Rust command 把任意路径写入磁盘;API key 也不能替代 trusted host 校验。
11.4 MCP 的进程安全¶
MCP 配置可能启动 npx、uvx 或自定义 command。mcp/helpers.rs 处理 stdio 子进程、SSE 和 streamable HTTP,并对 shutdown context 区分 AppExit、ManualRestart、FactoryReset 的超时。
重要安全面包括:
- command/env/args 是否来自用户可信配置。
npx/uvx的覆盖路径是否允许被恶意环境变量劫持。- server 输出是否可能包含 secret。
- app exit 时是否彻底清掉子进程和 PID。
- server-side tool execution 是否被明确关闭。
11.5 更新器的信任链¶
Tauri updater 使用公钥和 endpoint;应用本身还维护 backend checksum 与版本校验。它们是不同的信任链:应用更新的是 Jan bundle,backend 更新的是模型运行时二进制。后者需要额外验证 hash、平台和兼容版本。
11.6 CLI 是另一种入口¶
src-tauri/src/bin/jan-cli.rs 和 cli feature 让 Jan 具备无 WebView 的命令行入口。CLI 会复用 data folder/config resolution、部分 server/MCP/模型能力,但不能假设它拥有完整的前端 approval 与 UI 反馈。
这说明“桌面 App”不是唯一运行模式;诊断、自动化和 CI 可能从 CLI 进入,因此 command 的默认安全值、错误文本和退出码都应独立设计。
11.7 工程上的安全结论¶
Jan 的安全模型不是“本地 = 安全”,而是:
开启 Web Search、MCP、RAG、远程 Provider、local API server 后,数据会跨越更多边界。产品应该把这些能力显示为可配置的 trust choices,而不是把它们隐藏在“模型能不能回答”里。