16 KiB
16 KiB
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 创建目录结构
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
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
# 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 的沙箱实现。 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<ExecuteResult, SandboxError> {
// 生成 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 模块
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<ExecuteResult, SandboxError> {
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<ExecuteResult, SandboxError> {
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<ExecuteResult, SandboxError> {
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
# 克隆 Codex 仓库
cd /tmp
git clone https://github.com/openai/codex
cd codex/codex-rs
5.2 复制 Windows Sandbox
# 复制到 qiming-sandbox
cp -r windows-sandbox-rs /Users/apple/workspace/qiming-agent/crates/qiming-sandbox/crates/windows-sandbox
5.3 修改 Cargo.toml
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 编译测试
# 编译 Windows 版本(需要 Windows 交叉编译环境)
cargo build --release --target x86_64-pc-windows-msvc
6. Phase 5: Node.js Bindings
6.1 初始化 npm 包
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
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<ExecuteResult> {
// 根据平台调用对应实现
#[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<bool>,
pub memory_limit: Option<String>,
pub cpu_limit: Option<i32>,
pub timeout: Option<i32>,
}
#[napi(object)]
pub struct ExecuteResult {
pub exit_code: i32,
pub stdout: String,
pub stderr: String,
}
EOF
6.3 TypeScript 封装
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<SandboxConfig>
): Promise<ExecuteResult> {
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
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
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
{
"scripts": {
"prepare:sandbox": "ts-node scripts/prepare-sandbox.ts",
"postinstall": "npm run prepare:sandbox"
}
}
8. 测试
8.1 单元测试
cd crates/qiming-sandbox
# 运行 Rust 测试
cargo test
# 运行 Node.js 测试
cd bindings
npm test
8.2 集成测试
# 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 提交代码
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 打包发布
# 构建 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:
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