Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
17 changes: 17 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,20 @@ jobs:
run: python -m compileall -q tiny_harness tests
- name: Run tests
run: python -m pytest -q tests --ignore=tests/test_integration.py

sandbox-smoke:
name: Linux bubblewrap probe
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install sandbox test dependencies
run: |
sudo apt-get update -qq
sudo apt-get install -y bubblewrap util-linux
python -m pip install -e ".[dev]"
- name: Check actual kernel namespace support and run isolation tests
run: |
python -m pytest -q tests/test_shell_bwrap.py tests/test_sandbox_resource_limits.py -rs
10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,15 @@ ANTHROPIC_API_KEY="sk-ant-..." pytest tests/test_integration.py -v -m integratio

The configured `context_window` reserves `max_tokens` for model output. When input history grows too large, the harness compacts oversized tool outputs and can discard complete older turns, preserving the latest user turn and tool-call/result pairing. This budget currently uses a heuristic estimate, not model-specific tokenizers.

The built-in Shell skill requires human approval by default. In `--no-hitl` mode, explicit unsandboxed Shell access requires `--allow-unsafe-shell`; **this does not provide OS filesystem or network isolation**.
The built-in Shell skill requires human approval by default. In `--no-hitl` mode, Shell additionally requires `--allow-unsafe-shell`. The default `shell_sandbox=off` is **not** OS-isolated; Linux users may opt into `--shell-sandbox bubblewrap` for namespace-isolated execution with per-process resource limits. See [Linux Shell Sandbox](docs/SHELL_SANDBOX.md).

## Optional Linux Shell Sandbox

```bash
tiny-harness --provider deepseek --skills files,shell --shell-sandbox bubblewrap --sandbox-cpu-seconds 30 --sandbox-memory-mb 1024 --sandbox-file-mb 32
```

Requires system-installed `bubblewrap` and `prlimit`, and working unprivileged namespaces. Sandboxed mode blocks external network by default; it never falls back to a host shell if setup fails. These per-process limits are **not** cgroup-wide quotas.

## Security

Expand Down
4 changes: 3 additions & 1 deletion docs/OPTIMIZATION_STATUS.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,14 @@
| Phase 1 | [#19](https://github.com/1998x-stack/tiny-harness/pull/19) | OpenAI/Anthropic 流式请求在首次输出前重试 429/5xx/部分网络错误,Retry-After 上限;输出后禁重试 | PR CI 通过;真实 API 冒烟仍待执行 |
| Phase 4 | [#21](https://github.com/1998x-stack/tiny-harness/pull/21) | ToolDef 显式 path_args、所有声明路径规范化、第三方兼容字段全部检查 | PR CI 通过;仍非 OS 沙箱 |
| Phase 4 | [#22](https://github.com/1998x-stack/tiny-harness/pull/22) | Draft 2020-12 JSON Schema(嵌套/组合/数组),注册时检查 Schema,拒绝远程引用 | PR CI 通过;引入 jsonschema 运行时依赖 |
| Phase 5 | [#24](https://github.com/1998x-stack/tiny-harness/pull/24) | Linux Bubblewrap 可选 shell namespace + workspace-only bind, 缺失时 fail-closed | Python CI 通过,实际 namespace smoke 最初被跳过 |
| Phase 5 | [#25](https://github.com/1998x-stack/tiny-harness/pull/25) | prlimit CPU/虚拟内存/单文件大小逐进程资源约束 | Python CI 通过,非 cgroup 总配额 |

## 待完成事项(不应标记为已修复)

### P1:继续按独立 PR 完成

1. **真正的执行沙箱**:Shell 的 cwd、HITL 和环境过滤均不是 filesystem/network sandbox。评估 Linux bubblewrap/container;对 Windows/macOS 另定明确边界。**禁止**宣传当前 Shell 已沙箱化。
1. **OS 沙箱验证及增强**:已实现可选 Linux Bubblewrap namespace 和 prlimit;尚需真实机器上的隔离测试、cgroup 总资源配额、系统级加固和 Windows/macOS 后端。默认 off 仍是不隔离的 Shell,**禁止**把 opt-in Bubblewrap 描述为完整生产沙箱。
2. **ToolDef 参数级策略**:基础风险枚举与 no-HITL Shell 显式 opt-in 已完成;后续引入细粒度 allow/ask/deny、按命令参数审批策略,扩展其他危险工具的无 HITL 拒绝边界。
3. **Provider 全面契约**:Anthropic 自定义 endpoint、截断判断和首次输出前的有限流式重试已完成;仍需统一非流式/流式重试、DeepSeek thinking 透传、HTTP 客户端连接复用及真实 API 冒烟。
4. **上下文窗口**:已提供可配置窗口与启发式压缩;仍需 provider 真实 usage 回收、tokenizer 精确统计、可审计摘要和更精细的窗口分层治理。
Expand Down
87 changes: 87 additions & 0 deletions docs/SHELL_SANDBOX.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# Linux Shell Isolation / Bubblewrap

The built-in Shell skill supports an **optional**, Linux-only `bubblewrap`
execution mode. It is opt-in and does not change other tool handlers or
the default unsandboxed behavior.

## Setup

Install `bubblewrap` and util-linux `prlimit` from the operating system's
package manager, and verify the host permits unprivileged user namespaces.

```bash
tiny-harness \
--workspace /path/to/disposable-workspace \
--provider deepseek \
--skills files,shell \
--shell-sandbox bubblewrap \
--sandbox-cpu-seconds 30 \
--sandbox-memory-mb 1024 \
--sandbox-file-mb 32
```

Shell retains the usual HITL approvals. Disabling HITL still requires a
separate explicit `--allow-unsafe-shell` option; the sandbox is not a
substitute for user authorization.

## Namespace and filesystem contract

- `--unshare-all` isolates namespaces, including network and PID.
There is **no** `--share-net` fallback.
- `--tmpfs /` starts with an empty root. Selected system binary and library
directories (`/usr`, `/bin`, `/lib`, etc.) are mounted **read-only**.
No blanket `--ro-bind / /` or `--ro-bind /etc /etc` is used.
- Only the explicitly configured workspace is writable as
`/workspace`. Relative `cwd` is mapped inside that mount after
canonicalization. `/tmp` is a private tmpfs.
- The environment is cleared; only PATH, HOME, TMPDIR and LANG are set
inside the sandbox. Outside API keys are not forwarded.
- The host `bwrap` executable and `prlimit` are resolved from the system
binary directories, not from the working directory or arbitrary PATH.

## Resource constraints

The `prlimit` wrapper imposes `RLIMIT_CPU`, `RLIMIT_AS`, and
`RLIMIT_FSIZE`. Defaults: CPU 30 seconds, virtual address space 1024 MiB,
file size 32 MiB. They are inherited by child processes.

**Important:** These limits are *per process* and are **not aggregate cgroup
CPU or memory quotas**. The executor's existing timeout limits wall-clock
duration; its output cap limits retained output. `RLIMIT_AS` caps virtual
address space, which is not the same as RSS. The sandbox also does not limit
total disk writes across multiple files or processes.

## Unsupported cases / failure

If the OS is not Linux, the sandbox binaries are unavailable, the workspace
is invalid, the Linux kernel blocks namespace creation, or the sandbox
launcher returns an error, the operation fails. There is **no automatic
retry as an unsandboxed shell**.

The legacy direct `run_command()` Python helper remains unsandboxed;
the backend applies only to the Shell skill's registered
`run_command_async` handler.

## Limitations and validation

- Workspace contents and symlinks remain untrusted. The mount boundary
does not protect credentials already stored **inside** the workspace.
- This does not sandbox custom third-party Python tool handlers, and it
does not claim to protect against kernel vulnerabilities or bind-mount
time-of-check/time-of-use races.
- Binary compatibility depends on which trusted system directories are
exposed. Programs requiring host services, sockets, device access,
arbitrary `/opt` paths, or networking may fail intentionally.
- The CI Python test matrix checks launcher construction, explicit opt-in,
missing-binary fail-closed behavior, and resource flags. The separate
**Linux bubblewrap probe** job installs bwrap and attempts an actual
isolation smoke test. That test explicitly **SKIPs** when the runner
cannot create unprivileged user namespaces. A green CI result with a
skip does not establish successful real isolation; inspect job logs.

### Production follow-up

A production multi-tenant deployment still needs cgroup v2 aggregate quotas,
verified seccomp/AppArmor/SELinux policy, process identity isolation,
safe bind-mount handling, independent credentials, and dedicated
platform-specific integration tests.
3 changes: 2 additions & 1 deletion docs/security.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,13 @@ tiny-harness 是可执行工具的 Agent harness,**不是安全沙箱**。模
- HITL 默认审核 mutation/destructive/dangerous;destructive/dangerous 不可使用会话级永久批准。回调异常按拒绝处理。
- `ToolDef` 未声明风险时默认 mutation;未知 risk_level 在注册阶段拒绝;safe 风险等级仍执行文件路径守卫。
- `--no-hitl` 下内置 Shell 工具默认不可执行;使用 `--no-hitl --allow-unsafe-shell` 才能显式启用(不等于沙箱)。
- 可选 `shell_sandbox="bubblewrap"` 为 Linux 内置 async Shell 提供 mount/network/PID namespace 约束,启动失败不自动降级;`prlimit` CPU/AS/FSIZE 限制是逐进程限制,非 cgroup 总配额。参见 [SHELL_SANDBOX.md](SHELL_SANDBOX.md)。
- built-in Shell skill 的 async handler 运行于指定 cwd;POSIX 上超时/取消时终止进程组;不继承 `*_API_KEY` 等未在 allow-list 的环境变量。
- 会话快照使用原子替换与受限文件权限(POSIX `0600`)。

## 未提供的隔离能力

- **Shell 没有操作系统级文件系统隔离**:即使 cwd 在 workspace,命令仍可能访问机器上任何该进程有权限访问的路径;显式设置 `--no-hitl --allow-unsafe-shell` 后,模型可以无需审批执行 Shell。
- **默认 `shell_sandbox=off` 时 Shell 没有操作系统级文件系统隔离**:即使 cwd 在 workspace,命令仍可能访问机器上该进程有权限访问的路径。开启 bubblewrap 只是选配 Linux namespace 隔离,尚未在所有目标环境做端到端验证。
- Shell 可能访问网络、用户凭据文件、Git 配置及 SSH agent;环境变量白名单不能覆盖全部隐式凭据路径。
- 历史同步 `run_command` 辅助函数保留用于兼容,并未获得与 registered async Shell 等价的进程组取消保障。
- Path guard 的检查和 handler 的实际打开文件不是一个不可分割的 OS 操作;工作区内容可被外部进程并发替换时仍有 TOCTOU 风险。
Expand Down
Loading