Skip to content
Open
Show file tree
Hide file tree
Changes from 41 commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
8f7e2ff
feat(examples): add agent tool argv allowlist sandbox (#645)
cgflag Jul 21, 2026
64d0fe5
feat(examples): add thin Dockerfile for agent-tool-allowlist (#645)
cgflag Jul 21, 2026
19e549a
test(examples): add local allowlist unit tests (#645)
cgflag Jul 21, 2026
f817387
docs(examples): document Path B template and layer honesty (#645)
cgflag Jul 21, 2026
c6dd509
fix(examples): avoid remote Dockerfile frontend dependency (#645)
cgflag Jul 21, 2026
ca731d2
test(examples): exercise Windows path rejection branch (#645)
cgflag Jul 21, 2026
76ee7fa
feat(examples): add local verify gate and allowlist sync (#645)
cgflag Jul 21, 2026
b4b04d5
docs(examples): document verify_local and host DNS caveat (#645)
cgflag Jul 21, 2026
d237405
feat(examples): add sidecar allow path for host without DNS (#645)
cgflag Jul 21, 2026
f841f37
docs(examples): document host sidecar allow path (#645)
cgflag Jul 21, 2026
68d047a
docs(examples): trim allowlist README to Quick Start style (#645)
cgflag Jul 22, 2026
0541764
test(examples): assert deny path never calls Sandbox.create (#645)
cgflag Jul 22, 2026
0551642
feat(examples): stack Mode-1 airgap on allowlist allow path (#645)
cgflag Jul 22, 2026
b776fac
feat(examples): treat interpreter as explicit code-execution capabili…
cgflag Jul 22, 2026
e763e59
refactor(examples): DRY assert_allowlisted via is_allowlisted (#645)
cgflag Jul 22, 2026
af55c07
fix(examples): use resolved_path consistently in load_local_dotenv (#…
cgflag Jul 22, 2026
fa9b32b
chore(examples): tighten allowlist example hygiene from bot nits (#645)
cgflag Jul 22, 2026
ea18327
chore(examples): final allowlist hygiene nits (#645)
cgflag Jul 22, 2026
2802bc9
fix(examples): unify deny messages and AST-guard Sandbox.create (#645)
cgflag Jul 22, 2026
0348de6
docs(examples): avoid stale test names; note shlex path caveat (#645)
cgflag Jul 22, 2026
bb5732f
fix(examples): harden shlex denials and verify I/O checks (#645)
cgflag Jul 22, 2026
1f5f44f
refactor(examples): fold tool allowlist into code-sandbox-quickstart …
cgflag Jul 29, 2026
8f0424b
docs(examples): spell out tool-allowlist threat model and shell-chain…
cgflag Jul 29, 2026
7ee3be8
feat(examples): add hardened host-gated agent tool loop (#645)
cgflag Jul 29, 2026
67357c5
test(examples): add host-only unit tests for tool allowlist (#645)
cgflag Jul 29, 2026
57f656e
docs(examples): point allowlist stub README at full quickstart suite …
cgflag Jul 30, 2026
e0d7fee
feat(examples): add BYOI tool-profile template and address bot review…
cgflag Jul 30, 2026
0bcdff5
fix(examples): drop personal gitignore globs and tighten gate helpers…
cgflag Jul 30, 2026
4782905
docs(examples): tighten allowlist BYOI docs and optional curl layer (…
cgflag Jul 30, 2026
e6cfef6
fix(examples): clarify allowlist deny message for unparseable command…
cgflag Jul 30, 2026
5a94fac
fix(examples): drop duplicate README link and harden health parse (#645)
cgflag Jul 30, 2026
220a5d0
fix(examples): refuse silent allowlist growth without unsafe flag (#645)
cgflag Jul 30, 2026
e0ca75c
fix(examples): harden exit_code coerce and drop printf profile write …
cgflag Jul 30, 2026
2d18b1b
feat(examples): make agent-tool-allowlist a self-contained example (#…
cgflag Jul 30, 2026
43a9377
feat(examples): stack allowlist with checkpoint, egress, and fan-out …
cgflag Jul 30, 2026
fe2f7e3
feat(examples): ship in-guest cube-tool runner in allowlist BYOI (#645)
cgflag Jul 30, 2026
19a75e4
fix(examples): address serious bot notes on allowlist example (#645)
cgflag Jul 30, 2026
aeb3c49
fix(examples): pin dotenv Path support and share exit_code coerce (#645)
cgflag Jul 30, 2026
8a305cc
fix(examples): clarify envd probe and POSIX command -v (#645)
cgflag Jul 30, 2026
8987c47
docs(examples): reframe allowlist example vs #1062 close notes
cgflag Aug 1, 2026
f842558
fix(examples): harden bash constructs and fix bot accuracy notes (#645)
cgflag Aug 1, 2026
85ba1c5
feat(examples): reshape allowlist as BYOI happy-path template (#645)
cgflag Aug 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/guide/tutorials/examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ Hands-on examples demonstrating various Cube Sandbox use cases. Each example is
| Example | Description |
|---------|-------------|
| [Code Sandbox Quickstart](https://github.com/tencentcloud/CubeSandbox/tree/master/examples/code-sandbox-quickstart) | The most basic usage: create a sandbox, run Python code, execute shell commands, manage network policies, and more — all via the E2B SDK. |
| [Agent Tool Allowlist Sandbox](https://github.com/tencentcloud/CubeSandbox/tree/master/examples/agent-tool-allowlist-sandbox) | BYOI toolbox image with in-guest `cube-tool` runner + host argv allowlist; stacks airgap/CIDR, pause/resume, and multi-sandbox fan-out (#645). |
| [Browser Sandbox (Playwright)](https://github.com/tencentcloud/CubeSandbox/tree/master/examples/browser-sandbox) | Run a headless Chromium inside a MicroVM and control it remotely with Playwright via CDP. |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: table ordering suggestion

Since "Agent" (A) alphabetically precedes both "Code" (C) and "Browser" (B), consider moving this entry to the top of the table (before "Code Sandbox Quickstart") for consistency. The existing table has a weak first-letter alphabetical convention, and the A-prefixed entry fits best at the start.

| [OpenClaw Integration](https://github.com/tencentcloud/CubeSandbox/tree/master/examples/openclaw-integration) | Deploy Cube Sandbox and configure the OpenClaw skill so AI agents can execute code in isolated VM environments. |
| [SWE-bench with mini-swe-agent](https://github.com/tencentcloud/CubeSandbox/tree/master/examples/mini-rl-training) | Automate SWE-bench coding tasks in isolated sandboxes using cube-sandbox + mini-swe-agent, with multi-model support and RL training vision. |
Expand Down
1 change: 1 addition & 0 deletions docs/zh/guide/tutorials/examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
| 示例 | 说明 |
|------|------|
| [代码沙箱快速入门](https://github.com/tencentcloud/CubeSandbox/tree/master/examples/code-sandbox-quickstart) | 最基础的用法:创建沙箱、执行 Python 代码、运行 Shell 命令、管理网络策略等,全部通过 E2B SDK 完成。 |
| [Agent 工具白名单沙箱](https://github.com/tencentcloud/CubeSandbox/tree/master/examples/agent-tool-allowlist-sandbox) | BYOI 工具集镜像(含 guest `cube-tool`)+ 宿主机 argv 白名单;可叠加断网/CIDR、pause/resume、多沙箱扇出(#645)。 |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

建议:表格排序位置

与英文版一样,由于"Agent"(A)在字母序上排在"代码沙箱"(代)和"浏览器沙箱"(浏)之前,建议将新行移到表格最上方("代码沙箱快速入门"之前),以便与表格的近似字母序惯例保持一致。

| [浏览器沙箱(Playwright)](https://github.com/tencentcloud/CubeSandbox/tree/master/examples/browser-sandbox) | 在 MicroVM 中运行无头 Chromium,通过 CDP 协议使用 Playwright 远程控制浏览器。 |
| [OpenClaw 集成](https://github.com/tencentcloud/CubeSandbox/tree/master/examples/openclaw-integration) | 部署 Cube Sandbox 并配置 OpenClaw Skill,让 AI Agent 能够在隔离的虚拟机环境中执行代码。 |
| [SWE-bench + mini-swe-agent](https://github.com/tencentcloud/CubeSandbox/tree/master/examples/mini-rl-training) | 使用 cube-sandbox + mini-swe-agent 在隔离沙箱中自动化 SWE-bench 编码任务,支持多模型切换和 RL 训练愿景。 |
Expand Down
11 changes: 11 additions & 0 deletions examples/agent-tool-allowlist-sandbox/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Required: Cube API server address
export E2B_API_URL="http://<your-node-ip>:3000"

# Required: any non-empty value satisfies the SDK check
export E2B_API_KEY="e2b_000000"

# Required: template ID from cubemastercli tpl create-from-image (this Dockerfile)
export CUBE_TEMPLATE_ID="<your-template-id>"

# Optional: mkcert CA when talking to cube.app HTTPS
# export SSL_CERT_FILE="/root/.local/share/mkcert/rootCA.pem"
2 changes: 2 additions & 0 deletions examples/agent-tool-allowlist-sandbox/.gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
guest/cube-tool text eol=lf
tool-profile.txt text eol=lf
4 changes: 4 additions & 0 deletions examples/agent-tool-allowlist-sandbox/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.env
__pycache__/
*.pyc
.venv/
44 changes: 44 additions & 0 deletions examples/agent-tool-allowlist-sandbox/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# syntax=docker/dockerfile:1.7
#
# Agent toolbox BYOI: cubesandbox-base + guest cube-tool runner + tool-profile.
# Host demos allowlist "cube-tool" (and/or bare toolbox names); the wrapper
# re-checks the real tool name inside the MicroVM.
#
# Inherited cube-entrypoint keeps envd on :49983 (/health). This Dockerfile
# only EXPOSEs that port — it does not add a separate health server.
#
# docker build -t agent-tool-allowlist-sandbox:latest .
#
# cubesandbox-base already ships coreutils and curl. INSTALL_CURL=1 is only a
# defensive reinstall if a custom CUBE_BASE_IMAGE omits curl.

ARG CUBE_BASE_IMAGE=ghcr.io/tencentcloud/cubesandbox-base:2026.16
FROM ${CUBE_BASE_IMAGE}

ARG DEBIAN_FRONTEND=noninteractive
ARG INSTALL_CURL=0

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor accuracy nit: the base image ghcr.io/tencentcloud/cubesandbox-base:2026.16 is built from docker/Dockerfile.cube-base, which installs curl (apt-get install ... curl ...). So the "Skip apt unless INSTALL_CURL=1" comment here, the README's "Default build does not apt-install curl", and the "curl not in image → skip airgap probe" branches in tool_agent_loop.py / tool_allowlist_egress.py are effectively dead — curl is always present. Not a functional problem (the demos handle both cases), just reconcile the docs with reality.


RUN mkdir -p /etc/cube-sandbox /workspace /usr/local/bin \
&& if [ "$INSTALL_CURL" = "1" ]; then \
apt-get update \
&& apt-get install -y --no-install-recommends curl \
&& rm -rf /var/lib/apt/lists/*; \
fi

COPY tool-profile.txt /etc/cube-sandbox/tool-profile.txt
COPY guest/cube-tool /usr/local/bin/cube-tool
COPY workspace/ /workspace/

# Base image default user is ``user`` (uid 1000). Make /workspace writable for
# demos that choose WORKDIR paths (most demos still write under /tmp).
RUN sed -i 's/\r$//' /usr/local/bin/cube-tool /etc/cube-sandbox/tool-profile.txt \
&& chmod 0755 /usr/local/bin/cube-tool \
&& chown -R 1000:1000 /workspace \
&& /usr/local/bin/cube-tool echo "cube-tool-ok" \
&& if /usr/local/bin/cube-tool bash -c id; then echo "cube-tool must deny bash" >&2; exit 1; fi \
&& test -f /workspace/README.txt

WORKDIR /workspace
ENV CUBE_TOOL_PROFILE=/etc/cube-sandbox/tool-profile.txt

EXPOSE 49983
135 changes: 135 additions & 0 deletions examples/agent-tool-allowlist-sandbox/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
# Agent Tool Allowlist Sandbox

[中文文档](README_zh.md)

[#645](https://github.com/TencentCloud/CubeSandbox/issues/645) **scenario template**:
agent-host **tool argv policy** + a BYOI image with a real guest runner
(`/usr/local/bin/cube-tool`). Same shelf as `network-policy` (egress) — a
platform control-plane pattern users copy — not another language runtime.

**Why a dedicated example (answer to [#1062](https://github.com/TencentCloud/CubeSandbox/pull/1062) close notes)**
- Not a thin script on `sandbox-code`: this tree builds its **own** OCI image,
installs `cube-tool`, embeds `tool-profile.txt`, and verifies with
`verify_template.py` on a live cluster.
- Not a second code-interpreter tutorial: the product is **host+guest tool
policy**, stacked with airgap / CIDR / pause / fan-out.

**What you get**
- Host refuses illegal tools before `Sandbox.create` / `commands.run`
- Image installs `/usr/local/bin/cube-tool` + `/etc/cube-sandbox/tool-profile.txt`
+ `/workspace` — guest re-checks the profile (not just a text file on disk)
- Stacks with airgap, CIDR `allow_out`, pause/resume, and parallel sandboxes

**What this is not:** kernel jail, bash-free base, language runtime, or an LLM agent.

## Use cases

- Agent hosts that should prefer `cube-tool <name> …` over raw `bash`/`curl`
- Defense-in-depth: host allowlists `cube-tool`; guest refuses off-profile names
- Differentiated stacks: checkpoint / egress / multi-sandbox fan-out

## Resources

| Item | Suggestion |
|------|------------|

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two nits here: (1) the demos actually write to /tmp (/tmp/agent_loop.txt, /tmp/allowlist_checkpoint.txt, /tmp/tool_out.txt), not /workspace, so the "demos write under /workspace" hint is inaccurate. (2) If a user follows this and does write to /workspace, it will fail: the base image's default command user is user (uid 1000, see docker/Dockerfile.cube-base), while COPY workspace/ /workspace/ creates a root-owned 0755 directory. Either chown/chmod /workspace in the Dockerfile or fix the doc.

| Writable layer | `--writable-layer-size 1G` (demos write under `/tmp`; `/workspace` is chowned to uid 1000) |
| Ports | expose/probe `49983` (envd from `cubesandbox-base`) |
| Fan-out | keep `N≤2` on shared nodes |
| CPU/mem | default template quotas are enough for echo/artifact demos |

## Prerequisites

- Cube Sandbox cluster + `cubemastercli` + Docker + Python 3.10+

```bash
pip install -r requirements.txt
cp .env.example .env
```

## Quick start

### 1 — Build & register

```bash
docker build -t agent-tool-allowlist-sandbox:latest .
# optional: --build-arg INSTALL_CURL=1

# local image smoke (no cluster)
docker run --rm agent-tool-allowlist-sandbox:latest \
cube-tool echo build-ok

cubemastercli tpl create-from-image \
--image <registry-or-local>/agent-tool-allowlist-sandbox:latest \

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clarify: nothing listens on port 49983 / /health

The template registration command passes --probe-path /health for port 49983, but the Dockerfile only EXPOSEs the port — nothing inside the image actually serves that port or handles /health requests. If the platform's probe mechanism requires a successful response for template registration to complete, this may fail or timeout.

If the probe is informational / tolerated by the platform, consider noting that briefly so readers aren't confused when it doesn't return a meaningful health payload.

--writable-layer-size 1G \
--expose-port 49983 \
--probe 49983 \
--probe-path /health
```

`--probe 49983 --probe-path /health` targets **envd** from `cubesandbox-base`
(inherited entrypoint); this Dockerfile only `EXPOSE`s that port and does not
add its own health server.

Put READY template id into `.env` as `CUBE_TEMPLATE_ID`.

### 2 — Run demos

| Step | Command | Expect | Cluster |
|------|---------|--------|---------|
| Limits | `python tool_allowlist_limits.py` | `LIMITS_DEMO_OK` | no |
| Unit tests | `python -m unittest test_tool_allowlist.py -v` | OK | no |
| Deny | `python tool_allowlist_deny.py` | denied | no |
| Template smoke | `python verify_template.py` | `TEMPLATE_VERIFY_OK` | yes |
| Guest runner | `python tool_allowlist_guest_runner.py` | `GUEST_RUNNER_OK` | yes |
| Allow + airgap | `python tool_allowlist_allow.py` | echo + artifact | yes |
| Loop | `python tool_agent_loop.py` | `AGENT_LOOP_OK` | yes |
| Checkpoint | `python tool_allowlist_checkpoint.py` | `CHECKPOINT_OK` | yes |
| Egress stack | `python tool_allowlist_egress.py` | `EGRESS_STACK_OK` | yes |
| Fan-out | `python tool_allowlist_fanout.py` | `FANOUT_OK` | yes |

## How it works

```
propose: cube-tool echo hi
host assert_allowlisted (argv0 must be allowlisted; prefer cube-tool)
Sandbox.create(this BYOI)
/usr/local/bin/cube-tool → checks tool-profile.txt → exec echo
```

Bare allowlisted tools (`echo`, `cat`, …) still pass the host gate for demos;
prefer `cube-tool` so the guest profile is enforced.

## Directory

```
├── Dockerfile # installs cube-tool + profile + /workspace
├── tool-profile.txt # guest allowlist (copied into the image)
├── guest/cube-tool # in-guest runner
├── workspace/ # default WORKDIR in the image
├── tool_allowlist.py # host argv gate
├── tool_allowlist_*.py # demos
├── verify_template.py
└── …
```

## Limits

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Documentation gap: Shell globbing (*, ?) and input redirect (<) are unmentioned residuals in this "Limits" section.

The documented residuals cover echo … > file (arbitrary write, line 226) and allowlisted cat (path confinement out of scope, line 223). But these also pass the gate:

  • echo * — the shell expands * before executing echo; not caught by _has_shell_meta because * and ? aren't in _SHELL_META_CHARS
  • cat < /etc/passwd — input redirect uses <, which is also not blocked; only > is mentioned, and then only as a write vector

These are consistent with the gate's documented scope (no shell-expansion or path confinement), but listing them explicitly here would help readers understand the full residual surface. Same gap in README_zh.md.


- Base image still has a shell; callers that bypass `cube-tool` are out of scope
for the guest wrapper.
- Allowlisting bare `cat` still permits `cat /etc/passwd` through the host gate.
- Documented residuals (host gate is not a full shell parser): simple redirects
(`echo … > file`, `cat < /etc/passwd`) and glob chars `*` / `?` (guest shell
may expand them before the binary runs).
- Host gate **does** refuse bash process substitution (`<(…)`, `>(…)`) and
`/dev/tcp` / `/dev/udp` (argv0 would otherwise stay allowlisted while the
guest shell runs other programs or opens sockets).
- Growing the allowlist needs `extra_binaries` + `allow_unsafe_allowlist_extension=True`.
- `cubesandbox-base` already includes `curl`; `INSTALL_CURL=1` is only for custom
bases that omit it. Demo “curl missing” branches are defensive fallbacks.
- Fan-out creates real VMs — keep `N` small.
78 changes: 78 additions & 0 deletions examples/agent-tool-allowlist-sandbox/README_zh.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# Agent 工具白名单沙箱

[English](README.md)

[#645](https://github.com/TencentCloud/CubeSandbox/issues/645) **场景模板**:Agent
宿主侧 **工具 argv 策略** + 带真实 guest runner(`/usr/local/bin/cube-tool`)的
BYOI 镜像。与 `network-policy`(出口策略)同层——给用户可复制的平台控制面样例,
而不是又一个语言运行时。

**为何单独成例(回应 [#1062](https://github.com/TencentCloud/CubeSandbox/pull/1062) 关闭意见)**
- 不是在 `sandbox-code` 上叠薄脚本:本目录自建 OCI 镜像、安装 `cube-tool`、嵌入
`tool-profile.txt`,并用 `verify_template.py` 在真集群冒烟。
- 不是第二份代码解释器教程:交付物是 **宿主+guest 工具策略**,并可叠加断网 /
CIDR / pause / 扇出。

**你会得到**
- 宿主机在 `Sandbox.create` / `commands.run` 前拒绝非法工具
- 镜像安装 `/usr/local/bin/cube-tool`、`tool-profile.txt`、`/workspace`——guest
会再校验工具名(不是只放一个文本文件)
- 可叠加断网、CIDR `allow_out`、pause/resume、多沙箱扇出

**这不是:** 内核级 jail、无 bash 基础镜像、语言运行时,或 LLM Agent。

## 资源建议

| 项 | 建议 |
|----|------|
| 可写层 | `--writable-layer-size 1G`(演示脚本写 `/tmp`;镜像内 `/workspace` 已 chown 给 uid 1000) |
| 端口 | expose/probe `49983`(`cubesandbox-base` 的 envd) |
| 扇出 | 共享节点上保持 `N≤2` |
| CPU/内存 | 默认模板配额即可跑 echo/artifact 演示 |

## 快速开始

```bash
pip install -r requirements.txt
cp .env.example .env

docker build -t agent-tool-allowlist-sandbox:latest .
docker run --rm agent-tool-allowlist-sandbox:latest cube-tool echo build-ok

cubemastercli tpl create-from-image \
--image <仓库或本地>/agent-tool-allowlist-sandbox:latest \
--writable-layer-size 1G \
--expose-port 49983 \
--probe 49983 \
--probe-path /health
```

`--probe 49983 --probe-path /health` 指向基础镜像自带的 **envd**(继承
entrypoint);本 Dockerfile 只 `EXPOSE` 该端口,不另起健康检查服务。

把 READY 模板 id 写入 `.env` 的 `CUBE_TEMPLATE_ID`。

| 步骤 | 命令 | 期望 |
|------|------|------|
| 边界/单测/拒绝 | `tool_allowlist_limits.py` / unittest / `deny` | OK |
| 模板冒烟 | `verify_template.py` | `TEMPLATE_VERIFY_OK` |
| Guest runner | `tool_allowlist_guest_runner.py` | `GUEST_RUNNER_OK` |
| allow / loop / checkpoint / egress / fanout | 见英文 README 表 | 对应 `*_OK` |

## 原理

推荐路径:宿主白名单放行 `cube-tool` → 镜像内 `cube-tool` 对照
`/etc/cube-sandbox/tool-profile.txt` 再 `exec`。裸 `echo`/`cat` 仍可过宿主门控
(演示用);生产更应只放行 `cube-tool`。

## 限制

- 基础镜像仍有 shell;绕过 `cube-tool` 直接调 bash/路径二进制,不在 guest wrapper 范围内。
- 宿主白名单含裸 `cat` 时,`cat /etc/passwd` 仍过宿主门控。
- 文档化残差(本门控不是完整 shell 解析器):简单重定向(`echo … > file`、`cat < /etc/passwd`)
以及 `*` / `?` 通配(可能由 guest shell 先展开)。
- 宿主门控**会拒绝** bash 进程替换(`<(…)` / `>(…)`)以及 `/dev/tcp` / `/dev/udp`
(否则 argv0 仍在白名单内,guest shell 却能拉起其他程序或开套接字)。
- 扩白名单需 `extra_binaries` + `allow_unsafe_allowlist_extension=True`。
- `cubesandbox-base` 已含 `curl`;`INSTALL_CURL=1` 仅用于自定义缺 curl 的 base。
- Fan-out 会创建真实 VM,共享集群请保持小 `N`。
12 changes: 12 additions & 0 deletions examples/agent-tool-allowlist-sandbox/env_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from pathlib import Path

from dotenv import load_dotenv


def load_local_dotenv() -> None:
"""Best-effort load of a nearby .env file without overriding real env vars."""
for path in (Path(__file__).with_name(".env"), Path.cwd() / ".env"):
if path.is_file():
# str() keeps older python-dotenv working; requirements pin >=0.17.
load_dotenv(dotenv_path=str(path), override=False)
return
58 changes: 58 additions & 0 deletions examples/agent-tool-allowlist-sandbox/guest/cube-tool
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
#!/bin/sh
# Copyright (c) 2024 Tencent Inc.
# SPDX-License-Identifier: Apache-2.0
#
# In-guest tool runner: only exec binaries listed in tool-profile.txt.
# Complements the host argv gate — host should allow "cube-tool" and let
# this wrapper re-check the real tool name inside the MicroVM.
#
# Not a kernel jail: callers that bypass cube-tool (raw bash, path binaries)
# are out of scope for this wrapper.

set -eu

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hardening nit: Consider resetting IFS after set -eu

The script quotes variables correctly and uses the basename check at line ~28 for path defense, but doesn't reset IFS. While the current code doesn't word-split, adding IFS=$(printf ' \t\n_') && IFS=${IFS%_} after set -eu would eliminate IFS as an attack surface for any future code path or for a reader copying this pattern as a template.


PROFILE="${CUBE_TOOL_PROFILE:-/etc/cube-sandbox/tool-profile.txt}"

usage() {
echo "usage: cube-tool <tool> [args...]" >&2
echo "profile: ${PROFILE}" >&2
exit 2
}

[ "$#" -ge 1 ] || usage

tool=$1
shift

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: the case pattern already rejects / and \ — the later basename check (around line 48-53) is a defense-in-depth guard

The case pattern on this line already rejects any input containing / or \, making the basename mismatch check at lines 48-53 unreachable for standard path-hijack scenarios. Consider adding a brief comment on lines 47-53 noting that this is defense-in-depth against subtler PATH manipulation (e.g., a wrapper script named echo appearing earlier in PATH than /bin/echo).


case "$tool" in
''|*[!A-Za-z0-9._-]*|.*|*/*|*\\*)
echo "cube-tool: refusing unsafe tool name: ${tool}" >&2
exit 1
;;
esac

if [ ! -f "$PROFILE" ]; then
echo "cube-tool: missing profile ${PROFILE}" >&2
exit 1
fi

# Exact line match against the profile (no substrings).
if ! grep -qxF -- "$tool" "$PROFILE"; then
echo "cube-tool: tool not in profile: ${tool}" >&2
exit 1

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: use command -v "$tool" without -- for POSIX portability

The -- terminator after command -v is accepted by bash, dash, and busybox ash, but POSIX command does not require it to be recognized in -v mode. On a strictly POSIX-minimal /bin/sh, -- could be treated as the operand name to look up, causing command -v to always return empty and the wrapper to refuse all tools.

Since $tool is already validated against [A-Za-z0-9._-] in the preceding case statement, there's no injection concern from dropping the -- separator.

fi

bin=$(command -v "$tool" 2>/dev/null || true)
if [ -z "$bin" ]; then
echo "cube-tool: tool not installed: ${tool}" >&2
exit 127
fi

# Refuse path hijacks if command -v returned something unexpected.
base=$(basename "$bin")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This base=$(basename "$bin") / [ "$base" != "$tool" ] guard is effectively dead code and cannot detect the hijack its comment describes. After the case pattern above rejects any tool name containing /, \, or a leading ., command -v "$tool" can only return either a bare name (a shell builtin such as echo/pwd) or a path whose basename equals $tool — so $base != $tool never fires. The realistic hijack (a user-writable directory earlier in PATH containing an executable named like an allowlisted tool) keeps basename(...) == "$tool", so this check passes straight through it.

Since in-VM code execution already makes PATH hijacking moot, this isn't a security hole — but the comment overstates what the check does. Consider either removing the check and comment, or making it meaningful (e.g. verifying the resolved path is under a trusted directory like /usr/bin//bin, or invoking with a fixed PATH).

if [ "$base" != "$tool" ]; then
echo "cube-tool: resolved binary basename mismatch: ${bin}" >&2
exit 1
fi

exec "$bin" "$@"
2 changes: 2 additions & 0 deletions examples/agent-tool-allowlist-sandbox/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
e2b-code-interpreter>=2.4.1
python-dotenv>=0.17.0
Loading
Loading