# QimingClaw Sandbox 实施指南 > **版本**: 1.0.0 > **更新日期**: 2026-03-27 --- ## 1. 实施概述 ### 1.1 实施阶段 | 阶段 | 内容 | 预计时间 | 状态 | |------|------|---------|------| | **Phase 1** | 创建 qiming-sandbox 子模块 | 1 小时 | 待开始 | | **Phase 2** | 实现 macOS Sandbox | 1 小时 | 待开始 | | **Phase 3** | 实现 Linux Sandbox | 1-2 小时 | 待开始 | | **Phase 4** | 实现 Windows Sandbox (Codex) | 2-3 小时 | 待开始 | | **Phase 5** | 配置系统 + UI | 2 小时 | 待开始 | | **Phase 6** | 测试 + 文档 | 2 小时 | 待开始 | | **总计** | - | **9-11 小时** | - | --- ## 2. Phase 1: 创建子模块 ### 2.1 创建目录结构 ```bash cd /Users/apple/workspace/qiming-agent # 创建源码目录 mkdir -p crates/qiming-sandbox/crates mkdir -p crates/qiming-sandbox/bindings/native mkdir -p crates/qiming-sandbox/prebuilt # 创建运行时目录 mkdir -p crates/agent-electron-client/resources/sandbox/darwin-x64 mkdir -p crates/agent-electron-client/resources/sandbox/linux-x64 mkdir -p crates/agent-electron-client/resources/sandbox/win32-x64 # 创建 .gitkeep touch crates/agent-electron-client/resources/sandbox/darwin-x64/.gitkeep touch crates/agent-electron-client/resources/sandbox/linux-x64/.gitkeep touch crates/agent-electron-client/resources/sandbox/win32-x64/.gitkeep ``` ### 2.2 初始化 Cargo Workspace ```bash cd crates/qiming-sandbox # 初始化 Cargo.toml cat > Cargo.toml << 'EOF' [workspace] members = [ "crates/windows-sandbox", "crates/macos-sandbox", "crates/linux-sandbox", "bindings/native", ] resolver = "2" [workspace.package] version = "0.1.0" edition = "2021" license = "Apache-2.0" repository = "https://github.com/qiming-ai/qimingclaw" authors = ["QimingClaw Team"] description = "QimingClaw Agent Sandbox - based on OpenAI Codex" [workspace.dependencies] napi = "2" napi-derive = "2" tokio = { version = "1", features = ["full"] } serde = { version = "1", features = ["derive"] } serde_json = "1" [target.'cfg(windows)'.workspace.dependencies] winapi = "0.3" EOF ``` ### 2.3 创建 README 和 LICENSE ```bash # README.md cat > README.md << 'EOF' # QimingClaw Sandbox 基于 OpenAI Codex 沙箱方案的安全执行环境。 ## 许可证 Apache-2.0 (继承自 OpenAI Codex) ## 平台支持 | 平台 | 实现方式 | 状态 | |------|---------|------| | **macOS** | sandbox-exec | ✅ | | **Linux** | bubblewrap | ✅ | | **Windows** | Codex Sandbox | ✅ | ## 构建 ```bash make build # 构建所有平台 make build-darwin # 构建 macOS make build-linux # 构建 Linux make build-windows # 构建 Windows ``` ## 致谢 本项目基于 [OpenAI Codex](https://github.com/openai/codex) 的沙箱实现。 EOF # LICENSE cat > LICENSE << 'EOF' Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ Copyright 2024-2026 QimingClaw Team Copyright 2024 OpenAI Licensed under the Apache License, Version 2.0 (the "License"); ... EOF ``` --- ## 3. Phase 2: macOS Sandbox ### 3.1 创建 macOS 模块 ```bash mkdir -p crates/qiming-sandbox/crates/macos-sandbox/src cat > crates/macos-sandbox/Cargo.toml << 'EOF' [package] name = "qiming-macos-sandbox" version.workspace = true edition.workspace = true license.workspace = true [dependencies] tokio = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } EOF cat > crates/macos-sandbox/src/lib.rs << 'EOF' use std::process::Command; use std::fs; pub struct MacSandbox { profile_path: String, } impl MacSandbox { pub fn new() -> Self { Self { profile_path: String::new(), } } pub async fn execute(&self, command: &str, cwd: &str, config: &SandboxConfig) -> Result { // 生成 sandbox-exec 配置文件 let profile = self.generate_profile(cwd, config); let profile_path = format!("/tmp/sandbox-{}.sb", chrono::Utc::now().timestamp()); fs::write(&profile_path, &profile)?; // 执行命令 let output = Command::new("sandbox-exec") .arg("-f") .arg(&profile_path) .arg("bash") .arg("-c") .arg(command) .current_dir(cwd) .output()?; // 清理配置文件 fs::remove_file(&profile_path)?; Ok(ExecuteResult { exit_code: output.status.code().unwrap_or(-1), stdout: String::from_utf8_lossy(&output.stdout).to_string(), stderr: String::from_utf8_lossy(&output.stderr).to_string(), }) } fn generate_profile(&self, cwd: &str, config: &SandboxConfig) -> String { let network_rules = if config.network_enabled { self.generate_network_rules(&config.allowed_domains, &config.denied_domains) } else { "(deny network*)".to_string() }; format!(r#" (version 1) (allow default) ; 系统库读取 (allow file-read* (subpath "/usr") (subpath "/System")) ; 网络规则 {} ; 工作区访问 (allow file-read* (subpath "{}")) (allow file-write* (subpath "{}")) ; 禁止敏感目录 (deny file-read* (subpath "~/.ssh")) (deny file-read* (subpath "~/.aws")) (deny file-read* (subpath "~/.gnupg")) "#, network_rules, cwd, cwd) } fn generate_network_rules(&self, allowed: &[String], denied: &[String]) -> String { let mut rules = String::from("(allow network-outbound\n"); for domain in allowed { rules.push_str(&format!(" (remote tcp \"{}\" 443)\n", domain)); } rules.push_str(")\n"); for domain in denied { rules.push_str(&format!("(deny network-outbound (remote tcp \"{}\"))\n", domain)); } rules } } EOF ``` --- ## 4. Phase 3: Linux Sandbox ### 4.1 创建 Linux 模块 ```bash mkdir -p crates/qiming-sandbox/crates/linux-sandbox/src cat > crates/linux-sandbox/Cargo.toml << 'EOF' [package] name = "qiming-linux-sandbox" version.workspace = true edition.workspace = true license.workspace = true [dependencies] tokio = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } which = "5" EOF cat > crates/linux-sandbox/src/lib.rs << 'EOF' use std::process::Command; use which::which; pub struct LinuxSandbox { use_bubblewrap: bool, } impl LinuxSandbox { pub fn new() -> Self { let use_bubblewrap = which("bwrap").is_ok(); Self { use_bubblewrap } } pub async fn execute(&self, command: &str, cwd: &str, config: &SandboxConfig) -> Result { if self.use_bubblewrap { self.execute_in_bubblewrap(command, cwd, config).await } else { self.execute_unsandboxed(command, cwd, config).await } } async fn execute_in_bubblewrap(&self, command: &str, cwd: &str, config: &SandboxConfig) -> Result { let mut args = vec![ "--ro-bind".to_string(), "/usr".to_string(), "/usr".to_string(), "--ro-bind".to_string(), "/lib".to_string(), "/lib".to_string(), "--ro-bind".to_string(), "/bin".to_string(), "/bin".to_string(), "--bind".to_string(), cwd.to_string(), cwd.to_string(), "--dev".to_string(), "/dev".to_string(), "--proc".to_string(), "/proc".to_string(), "--unshare-all".to_string(), "--die-with-parent".to_string(), ]; if config.network_enabled { args.push("--share-net".to_string()); } args.push("bash".to_string()); args.push("-c".to_string()); args.push(command.to_string()); let output = Command::new("bwrap") .args(&args) .current_dir(cwd) .output()?; Ok(ExecuteResult { exit_code: output.status.code().unwrap_or(-1), stdout: String::from_utf8_lossy(&output.stdout).to_string(), stderr: String::from_utf8_lossy(&output.stderr).to_string(), }) } async fn execute_unsandboxed(&self, command: &str, cwd: &str, config: &SandboxConfig) -> Result { let output = Command::new("bash") .arg("-c") .arg(command) .current_dir(cwd) .output()?; Ok(ExecuteResult { exit_code: output.status.code().unwrap_or(-1), stdout: String::from_utf8_lossy(&output.stdout).to_string(), stderr: String::from_utf8_lossy(&output.stderr).to_string(), }) } } EOF ``` --- ## 5. Phase 4: Windows Sandbox (Codex) ### 5.1 克隆 Codex ```bash # 克隆 Codex 仓库 cd /tmp git clone https://github.com/openai/codex cd codex/codex-rs ``` ### 5.2 复制 Windows Sandbox ```bash # 复制到 qiming-sandbox cp -r windows-sandbox-rs /Users/apple/workspace/qiming-agent/crates/qiming-sandbox/crates/windows-sandbox ``` ### 5.3 修改 Cargo.toml ```bash cd /Users/apple/workspace/qiming-agent/crates/qiming-sandbox/crates/windows-sandbox # 修改 Cargo.toml 以符合 workspace cat > Cargo.toml << 'EOF' [package] name = "qiming-windows-sandbox" version.workspace = true edition.workspace = true license.workspace = true description = "Windows sandbox for QimingClaw Agent (based on Codex)" [dependencies] tokio = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } [target.'cfg(windows)'.dependencies] winapi = { workspace = true, features = ["winbase", "processthreadsapi", "jobapi2"] } [build-dependencies] # 保持 Codex 的原始构建依赖 EOF ``` ### 5.4 编译测试 ```bash # 编译 Windows 版本(需要 Windows 交叉编译环境) cargo build --release --target x86_64-pc-windows-msvc ``` --- ## 6. Phase 5: Node.js Bindings ### 6.1 初始化 npm 包 ```bash cd crates/qiming-sandbox/bindings cat > package.json << 'EOF' { "name": "@qiming/sandbox-native", "version": "0.1.0", "main": "index.js", "types": "index.d.ts", "napi": { "name": "sandbox", "triples": { "defaults": false, "additional": [ "x86_64-apple-darwin", "x86_64-unknown-linux-gnu", "x86_64-pc-windows-msvc" ] } }, "scripts": { "build": "napi build --platform --release", "build:debug": "napi build --platform" }, "devDependencies": { "@napi-rs/cli": "^2.0.0" } } EOF npm install ``` ### 6.2 创建 Rust Binding ```bash mkdir -p native/src cat > native/Cargo.toml << 'EOF' [package] name = "qiming-sandbox-native" version.workspace = true edition.workspace = true [lib] crate-type = ["cdylib"] [dependencies] napi = { workspace = true } napi-derive = { workspace = true } [features] default = [] [build-dependencies] napi-build = "2" EOF cat > native/src/lib.rs << 'EOF' use napi::bindgen_prelude::*; #[macro_use] extern crate napi_derive; #[napi] pub struct Sandbox; #[napi] impl Sandbox { #[napi(constructor)] pub fn new() -> Self { Self } #[napi] pub async fn execute( &self, command: String, cwd: String, config: SandboxConfig, ) -> Result { // 根据平台调用对应实现 #[cfg(target_os = "macos")] { // 调用 macOS 实现 } #[cfg(target_os = "linux")] { // 调用 Linux 实现 } #[cfg(target_os = "windows")] { // 调用 Windows 实现 } Ok(ExecuteResult { exit_code: 0, stdout: String::new(), stderr: String::new(), }) } } #[napi(object)] pub struct SandboxConfig { pub network_enabled: Option, pub memory_limit: Option, pub cpu_limit: Option, pub timeout: Option, } #[napi(object)] pub struct ExecuteResult { pub exit_code: i32, pub stdout: String, pub stderr: String, } EOF ``` ### 6.3 TypeScript 封装 ```bash cat > index.ts << 'EOF' import { Sandbox as NativeSandbox, SandboxConfig, ExecuteResult } from './native'; export class QimingSandbox { private sandbox: NativeSandbox; constructor() { this.sandbox = new NativeSandbox(); } async execute( command: string, cwd: string, config?: Partial ): Promise { const fullConfig: SandboxConfig = { networkEnabled: config?.networkEnabled ?? false, memoryLimit: config?.memoryLimit ?? '2g', cpuLimit: config?.cpuLimit ?? 2, timeout: config?.timeout ?? 300, }; return this.sandbox.execute(command, cwd, fullConfig); } isAvailable(): boolean { return true; } } export type { SandboxConfig, ExecuteResult }; EOF ``` --- ## 7. Phase 6: 配置和构建 ### 7.1 创建 Makefile ```bash cd crates/qiming-sandbox cat > Makefile << 'EOF' .PHONY: build clean prebuilt build: cargo build --release cd bindings && npm run build build-darwin: cargo build --release --target x86_64-apple-darwin cd bindings && npm run build -- --target x86_64-apple-darwin build-linux: cargo build --release --target x86_64-unknown-linux-gnu cd bindings && npm run build -- --target x86_64-unknown-linux-gnu build-windows: cargo build --release --target x86_64-pc-windows-msvc cd bindings && npm run build -- --target x86_64-pc-windows-msvc prebuilt: build mkdir -p prebuilt/darwin-x64 mkdir -p prebuilt/linux-x64 mkdir -p prebuilt/win32-x64 cp bindings/native/*.node prebuilt/darwin-x64/ || true cp bindings/native/*.node prebuilt/linux-x64/ || true cp bindings/native/*.node prebuilt/win32-x64/ || true cp target/x86_64-pc-windows-msvc/release/qiming-sandbox.exe prebuilt/win32-x64/ || true clean: cargo clean rm -rf bindings/native/*.node rm -rf prebuilt/* EOF ``` ### 7.2 创建 prepare-sandbox.ts ```bash cd crates/agent-electron-client/scripts cat > prepare-sandbox.ts << 'EOF' import * as fs from "fs"; import * as path from "path"; const SANDBOX_SRC = path.join(__dirname, "..", "..", "qiming-sandbox", "prebuilt"); const SANDBOX_DEST = path.join(__dirname, "..", "resources", "sandbox"); const PLATFORM = process.platform; const ARCH = process.arch; function prepareSandbox(): void { const sourceDir = path.join(SANDBOX_SRC, `${PLATFORM}-${ARCH}`); const targetDir = path.join(SANDBOX_DEST, `${PLATFORM}-${ARCH}`); fs.mkdirSync(targetDir, { recursive: true }); if (fs.existsSync(sourceDir)) { const files = fs.readdirSync(sourceDir); files.forEach(file => { fs.copyFileSync( path.join(sourceDir, file), path.join(targetDir, file) ); }); console.log(`✅ Sandbox binaries copied for ${PLATFORM}-${ARCH}`); } else { console.log(`ℹ️ No sandbox binaries needed for ${PLATFORM}-${ARCH}`); fs.writeFileSync(path.join(targetDir, ".gitkeep"), ""); } } prepareSandbox(); EOF ``` ### 7.3 更新 package.json ```json { "scripts": { "prepare:sandbox": "ts-node scripts/prepare-sandbox.ts", "postinstall": "npm run prepare:sandbox" } } ``` --- ## 8. 测试 ### 8.1 单元测试 ```bash cd crates/qiming-sandbox # 运行 Rust 测试 cargo test # 运行 Node.js 测试 cd bindings npm test ``` ### 8.2 集成测试 ```bash # macOS 测试 make build-darwin node test/sandbox.test.js # Linux 测试 (需要 Linux 环境) make build-linux node test/sandbox.test.js # Windows 测试 (需要 Windows 环境) make build-windows node test/sandbox.test.js ``` --- ## 9. 发布 ### 9.1 提交代码 ```bash cd /Users/apple/workspace/qiming-agent # 添加新文件 git add crates/qiming-sandbox/ git add crates/agent-electron-client/scripts/prepare-sandbox.ts git add crates/agent-electron-client/docs/sandbox/ # 提交 git commit -m "feat(sandbox): add qiming-sandbox module based on Codex" ``` ### 9.2 打包发布 ```bash # 构建 Electron 应用 cd crates/agent-electron-client npm run build npm run package # 测试安装包 # macOS: open release/QimingClaw-0.9.3.dmg # Windows: release/QimingClaw-0.9.3.exe # Linux: release/QimingClaw-0.9.3.AppImage ``` --- ## 10. 常见问题 ### Q1: macOS 提示 sandbox-exec 权限错误? A: 检查应用是否正确签名,sandbox-exec 需要有效的开发者签名。 ### Q2: Linux 提示 bwrap 未找到? A: 安装 bubblewrap: ```bash sudo apt install bubblewrap socat ripgrep ``` ### Q3: Windows 编译失败? A: 确保安装了: - Visual Studio Build Tools - Windows SDK - Rust target: `rustup target add x86_64-pc-windows-msvc` --- **文档版本**: 1.0.0 **最后更新**: 2026-03-27