{"_id":"@baishuyun/sandbox","_rev":"2-82cc2ef1abe25b3233586f8608050018","name":"@baishuyun/sandbox","dist-tags":{"alpha":"0.0.1-alpha.1","latest":"0.0.1-alpha.2"},"versions":{"0.0.1-alpha.1":{"name":"@baishuyun/sandbox","version":"0.0.1-alpha.1","keywords":["sandbox","docker","ai-sdk","harness","agent"],"author":"","license":"ISC","_id":"@baishuyun/sandbox@0.0.1-alpha.1","maintainers":[{"name":"baishuyun.com","email":"2559215848@qq.com"}],"dist":{"shasum":"069ea509f5f698276ca6a2c86b904ba8073692a0","tarball":"https://registry.npmjs.org/@baishuyun/sandbox/-/sandbox-0.0.1-alpha.1.tgz","fileCount":24,"integrity":"sha512-g+py+bIYhH7pxeOdQE4YPYzNF81l1qXF9h7MkyZNn0b6xD3jdM6Jt7NcbpE8M1tHaGr/pqvIwGaVI2Bb4QbLKQ==","signatures":[{"sig":"MEUCIQDtNZegBryRpABLknwT5JDpKwwvTBaqnwvb2ehDYbccbgIgf9a0+RbNvI0eZkydzFGoSFeHEjdd1NcOdQUZcpqycAY=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":186941},"main":"dist/index.js","type":"module","_from":"file:baishuyun-sandbox-0.0.1-alpha.1.tgz","types":"dist/index.d.ts","module":"dist/index.js","engines":{"node":">=22"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./package.json":"./package.json"},"scripts":{"dev":"vite build --watch","build":"vite build","clean":"rimraf dist *.tsbuildinfo","type-check":"tsc --noEmit","build-image":"node scripts/build-image.mjs images.online-office.net/ai/agent-sandbox:22 --push"},"_npmUser":{"name":"baishuyun.com","email":"2559215848@qq.com"},"_resolved":"/tmp/6e53b7dfa014c496a3f818b0335e03ef/baishuyun-sandbox-0.0.1-alpha.1.tgz","_integrity":"sha512-g+py+bIYhH7pxeOdQE4YPYzNF81l1qXF9h7MkyZNn0b6xD3jdM6Jt7NcbpE8M1tHaGr/pqvIwGaVI2Bb4QbLKQ==","_npmVersion":"10.9.8","description":"Docker-based HarnessV1 sandbox provider for the AI SDK agent harness.","directories":{},"_nodeVersion":"22.23.2","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"ai":"^7.0.66","vite":"^5.1.4","rimraf":"^6.1.2","typescript":"^5.8.3","@types/node":"^22.15.0","@ai-sdk/harness":"^1.0.72","vite-plugin-dts":"^4.5.4","@baishuyun/typescript-config":"2.0.9"},"_npmOperationalInternal":{"tmp":"tmp/sandbox_0.0.1-alpha.1_1787393961835_0.8018783169524959","host":"s3://npm-registry-packages-npm-production"}},"0.0.1-alpha.2":{"name":"@baishuyun/sandbox","version":"0.0.1-alpha.2","description":"Docker-based HarnessV1 sandbox provider for the AI SDK agent harness.","type":"module","main":"dist/index.js","module":"dist/index.js","types":"dist/index.d.ts","engines":{"node":">=22"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./package.json":"./package.json"},"devDependencies":{"@ai-sdk/harness":"^1.0.72","@types/node":"^22.15.0","ai":"^7.0.66","rimraf":"^6.1.2","typescript":"^5.8.3","vite":"^5.1.4","vite-plugin-dts":"^4.5.4","@baishuyun/typescript-config":"2.0.9"},"publishConfig":{"access":"public"},"keywords":["sandbox","docker","ai-sdk","harness","agent"],"author":"","license":"ISC","scripts":{"build":"vite build","dev":"vite build --watch","clean":"rimraf dist *.tsbuildinfo","type-check":"tsc --noEmit","build-image":"node scripts/build-image.mjs images.online-office.net/ai/agent-sandbox:22 --push"},"_id":"@baishuyun/sandbox@0.0.1-alpha.2","_integrity":"sha512-B5S7y+o6wdWp8mP6ipqUu5ECX9tJnqx7Drr65na7e2NR9PKELYJvF9p8JvTGftKIDnZEz77hBZGdv7ol8fTOYQ==","_resolved":"/tmp/b61901ffe0108c19453b9cefb2472fb5/baishuyun-sandbox-0.0.1-alpha.2.tgz","_from":"file:baishuyun-sandbox-0.0.1-alpha.2.tgz","_nodeVersion":"22.23.2","_npmVersion":"10.9.8","dist":{"integrity":"sha512-B5S7y+o6wdWp8mP6ipqUu5ECX9tJnqx7Drr65na7e2NR9PKELYJvF9p8JvTGftKIDnZEz77hBZGdv7ol8fTOYQ==","shasum":"557711abdfd55768e576e194c941b9dde62a7750","tarball":"https://registry.npmjs.org/@baishuyun/sandbox/-/sandbox-0.0.1-alpha.2.tgz","fileCount":24,"unpackedSize":194185,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCH06bqXsJ2L4WdI3FaGIh0YupTnClDshXe7wOYVa8yFsCIQDENHKm+0mdJForZ66+QMPo/7+ow7fPpm/6nKQKkBVmPQ=="}]},"_npmUser":{"name":"baishuyun.com","email":"2559215848@qq.com"},"directories":{},"maintainers":[{"name":"baishuyun.com","email":"2559215848@qq.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/sandbox_0.0.1-alpha.2_1787636758852_0.6671629084942423"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-22T10:19:21.706Z","modified":"2026-08-25T05:45:59.213Z","0.0.1-alpha.1":"2026-08-22T10:19:21.998Z","0.0.1-alpha.2":"2026-08-25T05:45:59.014Z"},"license":"ISC","keywords":["sandbox","docker","ai-sdk","harness","agent"],"description":"Docker-based HarnessV1 sandbox provider for the AI SDK agent harness.","maintainers":[{"name":"baishuyun.com","email":"2559215848@qq.com"}],"readme":"# @baishuyun/sandbox\n\n基于 Docker 的 AI SDK HarnessV1 沙箱 Provider（`HarnessV1SandboxProvider` 实现），面向桥接型 harness（codex / claude-code 等）运行不可信 agent 代码的生产场景。\n\n- **强隔离**：每个会话一个独立容器——非 root 用户（uid 1000）、drop 全部 capabilities、no-new-privileges、pids/内存限额、端口仅绑定宿主机回环。\n- **端口池 + 按需转发**：容器创建时发布端口池（默认 7100-7107）到宿主机随机回环端口；桥接进程直接绑定池内端口即可被宿主机访问；池外端口由容器内 socat 按需转发。\n- **可恢复**：容器按 `<namePrefix>-<sessionId>` 确定性命名，`resumeSession` 跨进程重连（停止的容器自动 start）。\n- **零运行时依赖**：仅通过 docker CLI（argv 直调，不经 shell，杜绝注入）。\n\n## 准备\n\n```bash\n# 构建基础镜像（node:22 + pnpm + socat + git，非 root）\npnpm --filter @baishuyun/sandbox build-image\n```\n\n## 使用\n\n```ts\nimport { HarnessAgent } from '@ai-sdk/harness/agent';\nimport { createCodex } from '@ai-sdk/harness-codex';\nimport { createDockerSandbox } from '@baishuyun/sandbox';\n\nconst agent = new HarnessAgent({\n  harness: createCodex({ auth: 'direct' }),\n  sandbox: createDockerSandbox(),\n  instructions: '...',\n  tools,\n});\n```\n\n独立使用（不经过 harness）：\n\n```ts\nimport { createDockerSandbox } from '@baishuyun/sandbox';\n\nconst provider = createDockerSandbox({ image: 'baishuyun/agent-sandbox:22' });\nconst session = await provider.createSession({ sessionId: 'task-1' });\n\nawait session.run({ command: 'node --version' });\nawait session.writeTextFile({ path: 'a/b.txt', content: 'hello' });\nconst endpoint = await session.getPortEndpoint({ port: 7100 }); // http://127.0.0.1:<port>\n\nawait session.stop();                 // docker stop（可 resume）\nconst s2 = await provider.resumeSession({ sessionId: 'task-1' });\nawait s2.destroy();                   // docker rm -f\n```\n\n## 配置（`DockerSandboxOptions`）\n\n| 选项 | 默认 | 说明 |\n|---|---|---|\n| `image` | `baishuyun/agent-sandbox:22` | 基础镜像（`docker/Dockerfile`） |\n| `workDir` | `/home/node` | 默认工作目录（须与镜像用户 home 一致） |\n| `env` | `{}` | 注入容器的环境变量 |\n| `memory` | `2g` | 内存限额 |\n| `cpus` | 不限 | CPU 限额，如 `1.5` |\n| `pidsLimit` | `256` | PID 数限额 |\n| `network` | `bridge` | `none` 可完全断网 |\n| `portPoolSize` / `portPoolStart` | `8` / `7100` | 端口池（容器侧） |\n| `pullIfMissing` | `true` | 镜像缺失时自动 `docker pull` |\n| `extraRunArgs` | `[]` | 追加 `docker run` 原生参数 |\n| `dockerBinary` | `docker` | docker CLI 路径 |\n| `egressProxy` | `true` | 出口代理（请求变换/网络策略）开关 |\n| `hostGateway` | `host.docker.internal` | 容器访问宿主机的主机名 |\n| `logger` | - | `{ debug?, info?, warn?, error? }` |\n\n## 端口模型\n\n- **池内端口**（默认 7100-7107）：沙箱内进程直接绑定，`getPortEndpoint({ port })` 返回宿主机回环映射。桥接型 harness 取 `session.ports[0]` 启动桥接进程，即属此类。\n- **池外端口**：自动从池中分配空闲端口，容器内 `socat` 转发到目标端口。\n- 约定：**先解析（getPortEndpoint）再绑定**——已解析的池端口会被标记占用，转发分配会避开。\n\n## 安全说明\n\n- 所有容器内命令以 `docker exec ... sh -c <script> sh <args...>` 执行，脚本内用 `\"$1\"` 引用参数，路径/命令不经宿主 shell、不被二次展开。\n- agent 工具只能拿到 `restricted()` 视图（文件读写 + 进程执行），无法 stop/destroy 容器或改端口。\n- agent 工具只能拿到 `restricted()` 视图（文件读写 + 进程执行），无法 stop/destroy 容器、改端口或改出口策略。\n\n## 出口代理（请求变换与网络策略）\n\n每会话在宿主机起一个 CONNECT 代理（容器经 `HTTP(S)_PROXY=http://host.docker.internal:<port>` 出站），实现 HarnessV1 的完整网络面：\n\n- **`setRequestTransformations` / `addRequestTransformations`**：命中规则的域名走 MITM——会话级 CA（openssl 签发，缓存在 tmpdir）叶证书终止 TLS，按规则在**宿主机侧**改写请求头（如注入 `Authorization`），凭据从不进入沙箱；未命中域名走 CONNECT 盲隧道。支持 host + path(exact/startsWith/regex) + method 匹配，WebSocket Upgrade 同样改写握手头。\n- **`setNetworkPolicy`**：`deny-all` 全拒；`custom` 按 `allowedHosts` 白名单放行（含子域）。\n- **CA 注入**：容器创建时把 CA 安装进系统信任链（`update-ca-certificates`），并通过 `NODE_EXTRA_CA_CERTS` / `SSL_CERT_FILE` 覆盖 Node 与 Rust(reqwest) 运行时。\n- **确定性代理端口**：端口按容器名派生（30000-38999），`resumeSession` 用同一端口重启/复用代理，进程重启后沙箱出站不受影响。\n- 可用 `egressProxy: false` 关闭（harness 回退到凭据转发，安全性较低）。\n\n凭据托管的典型效果（codex harness）：沙箱内 codex CLI 只拿到掩码后的占位 key，真实 key 由代理在请求离开沙箱时注入——不再回退到 `less secure credential forwarding`。\n\n## 目录结构\n\n```\npackages/sandbox/\n├── docker/Dockerfile          # 基础镜像（node:22-slim + socat + git + pnpm）\n├── scripts/build-image.mjs    # 镜像构建脚本\n└── src/\n    ├── index.ts               # 公共出口\n    ├── provider.ts            # DockerSandboxProvider（createSession/resumeSession）\n    ├── session.ts             # DockerSandboxSession（文件/进程/端口/生命周期）\n    ├── docker.ts              # docker CLI 封装（argv 直调）\n    ├── types.ts               # 配置\n    └── errors.ts              # 错误类型\n```\n","readmeFilename":"README.md"}