人人都会AI编程

21.4 沙箱与进程隔离

更新时间:2026-07-11

1. 进程分离:前端与后端的天然屏障

  • Rust 主进程:拥有完整的系统能力,负责窗口管理、文件系统访问、系统通知、剪贴板操作等。它不渲染任何 HTML 或执行 JavaScript。
  • WebView 渲染进程:只负责展示前端 UI 并执行页面脚本。它运行在操作系统为浏览器引擎提供的沙箱中,默认无法访问本地文件系统、子进程或系统调用。

这种分离意味着,即使前端代码中存在恶意脚本(如 XSS 攻击),攻击者也无法直接读写本地文件或执行系统命令——因为它根本不在同一个进程空间里,也没有系统调用的权限。


2. 上下文隔离:让前端无法触及 Node.js 或 Rust API

Tauri 默认开启 WebView 的上下文隔离(Context Isolation)。前端的 JavaScript 代码与用来桥接 Rust 的“预加载脚本”分别运行在隔离的 JavaScript 上下文中,两者不能互相篡改原型链或全局变量。

预加载脚本通过 window.TAURI 暴露有限的、安全的 API 给页面,而不是让页面直接访问 IPC 底层对象。这样一来,即使攻击者注入了代码,他能操作的也只是 Tauri 事先暴露出的那部分功能,而无法直接调用 Rust 命令或修改 IPC 通道。


3. 命令白名单:每一个系统功能都需要“开门”

在 Tauri 配置文件中,你必须在 allowlist 段显式声明每个被允许的系统能力,例如:

{
  "tauri": {
    "allowlist": {
      "fs": {
        "scope": ["$APPDATA/*"]
      },
      "shell": {
        "open": true
      }
    }
  }
}

Rust 侧定义的命令也需要在 invoke_handler 中明确注册,前端不能随意调用任何未注册的 Rust 函数。这种默认拒绝、按需开放的策略,确保只有经过审核的操作才能被执行。


4. IPC 通信的安全约束

前后端通过参数化消息交互,所有参数都经过序列化与类型检查。Rust 端的命令接收参数时,强制进行类型反序列化和逻辑校验,不会盲目执行来自前端的原始字符串。例如:

#[tauri::command]
fn read_secret_file(path: String) -> Result<String, String> {
    // 检查路径是否在允许范围内
    if !path.starts_with("/safe/dir/") {
        return Err("unauthorized".into());
    }
    std::fs::read_to_string(path).map_err(|e| e.to_string())
}

这种设计将安全边界从“信任前端”转移到“后端严格校验”,即使前端传递了恶意参数,也很容易被 Rust 的类型系统和自定义验证拦截。


5. 沙箱化的实际意义

假设你的 Tauri 应用渲染了一段用户提交的 Markdown,其中被插入了一段窃取 token 的脚本。在传统 Web 应用中,这段脚本可能读取 localStorage 并发送到外部。但在 Tauri 中:

  • 该脚本仍受同源策略限制,只能访问页面内的数据;
  • 它无法通过 window.TAURI 调用未注册的命令;
  • 即使它尝试调用已注册的 read_file 命令,也需要传递合法的路径参数,而 Rust 后端会严格校验路径是否在许可范围内;
  • 更重要的是,read_file 这个命令本身需要被显式注册,如果应用没有暴露,前端就完全没有调用入口。

也就是说,前端漏洞的影响范围被严格限制在 WebView 内部,不会轻易扩大为系统级安全事件。


最佳实践提醒

  • 不要为了方便把 Rust 命令设计成“万能函数”,尽量保持命令功能单一、参数明确。
  • 永远不要在前端代码中直接拼接系统命令或路径,所有可能被用户输入影响的数据都应在 Rust 侧进行校验。
  • 如果使用自定义协议或深度链接,必须在后端进行 URL 解析和安全判断,而不是依赖前端路由。
  • 定期审查 allowlist 配置,去掉那些不再需要的权限。

沙箱与进程隔离让 Tauri 应用天然具备高安全基线,但它不是“自动挡”,开发者仍需在暴露原生能力时保持最小权限原则。只有这样,才能真正把前端攻击面关在笼子里。