anthropics / anthropics/sandbox-runtime

denyRead does not block access to individual files inside an allowRead directory

Aperta
#193 1 commento 6 reazioni 0 assegnatari Vedi su GitHub
Lingua principale
TypeScript
Stelle
5.2k
Fork
439
Merge medio
2g 9m
PR unite (30g)
13

Descrizione

## Summary

When a directory is listed in `allowRead` (e.g. a project workspace), adding individual file paths to `denyRead` has no effect. The files remain fully readable inside the sandbox.

This makes it impossible to expose a working directory to the sandbox while hiding specific sensitive files within it (`.env`, `*_rsa`, `.secret`, etc.).

## Reproduction

```ts
import { spawnSync } from "child_process"
import fs from "fs"

fs.writeFileSync("secret.txt", "TOP SECRET DATA")

const config = {
network: {},
filesystem: {
denyRead: [process.cwd() + "/secret.txt"],
allowRead: [process.cwd()],
allowWrite: [process.cwd()],
denyWrite: []
}
}

fs.writeFileSync("/tmp/srt-test.json", JSON.stringify(config))

const res = spawnSync("srt", ["--settings", "/tmp/srt-test.json", "-c", "cat secret.txt"])
console.log("exit:", res.status) // 0 — expected non-zero
console.log("stdout:", res.stdout.toString()) // "TOP SECRET DATA" — expected empty/error
```

**Expected:** `cat secret.txt` fails with `Permission denied` or `No such file or directory`.
**Actual:** File is read successfully. `denyRead` is silently ignored.

## Root Cause

On Linux, `srt` uses `bubblewrap` (`bwrap`) to construct the mount namespace. The workspace directory is exposed via `--bind` or `--ro-bind`, which bind-mounts the entire directory tree. A subsequent `denyRead` entry for a file inside that tree has no mechanism to punch a hole in the existing bind mount.

`bwrap` does support file-level bind-mount overlays, which would solve this:

```bash
# This works natively with bwrap:
bwrap \
--bind /workspace /workspace \
--ro-bind /dev/null /workspace/.env \
-- cat /workspace/.env

# Result: empty read, secret is hidden
```

By bind-mounting `/dev/null` (or an empty tmpfs file) over each `denyRead` path, the file is effectively masked at the VFS level. The inode is replaced, so `cat`, `head`, `less`, and any other read operation sees an empty file. The original file is untouched on the host.

## Proposed Fix

When srt generates the bwrap command internally, for each denyRead path that falls inside an allowRead directory:

1. Check if the path is a regular file (not a directory).
2. If so, add `--ro-bind /dev/null ` **after** the parent directory's `--bind`/`--ro-bind` directive. Mount order matters — later mounts override earlier ones at the same path.
3. For directories in `denyRead`, the existing `--tmpfs ` approach should already work.

## Environment

- `srt` installed via `npm install -g @anthropic-ai/sandbox-runtime`
- Linux x64, kernel 6.x
- bubblewrap available in PATH

Guida per i contributori

Nessuna guida per i contributori indicizzata per questo repository

Valutazione

Questa issue non è ancora stata valutata.

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.