share the host ~/.agents read-only, so skills come along
A sandboxed harness found none of your skills, prompt templates or standing AGENTS.md, because those live in ~/.agents and only the project was mounted. It is now bind-mounted at /home/sandbox/.agents by default -- bound, never copied -- with --no-agents to opt out and --agents-dir for a curated set, reported by --print-policy and by doctor. Read-only is the load-bearing part and has no escape hatch: a skill is an instruction the model follows later, on the host, so a sandbox that could write ~/.agents had a persistence path out of itself, planted as a helpful-looking SKILL.md. It holds even under --insecure-keep-root, the mount lands after /home/sandbox itself so it cannot be shadowed by the home volume or tmpfs, and an explicit --mount on the same destination wins because podman refuses duplicates. A missing or unreadable directory is a silent skip like the pi bridge's; an explicit --agents-dir that resolves to nothing is a hard error. The exposure is stated in docs/threat-model.md: the tree, its .git history and anything pasted into a skill are readable by whatever runs in there. tests/unit/09_agents.sh covers resolution, precedence and the bind-never-copy canary; tests/e2e/12_sandbox.sh mounts it in a real container, checks the host tree survives a run byte for byte and that even root inside the sandbox cannot write it. LLM-Generated: true
This commit is contained in:
@@ -101,8 +101,9 @@ Asking for a variable that is not set fails the run closed.
|
||||
|
||||
## Filesystem
|
||||
|
||||
Your cwd is mounted at `/project` and is the working directory — that is the whole
|
||||
default. Everything else is opt-in:
|
||||
Your cwd is mounted at `/project` and is the working directory. That is the only
|
||||
*writable* mount, and nothing else on the host is visible except the one exception
|
||||
below. Everything else is opt-in:
|
||||
|
||||
```bash
|
||||
--mount ~/.ssh:ro # if the agent must be able to push
|
||||
@@ -115,6 +116,31 @@ default. Everything else is opt-in:
|
||||
Files the agent writes land on your disk owned by **your** uid, not root
|
||||
(`--userns=keep-id`, verified by `doctor`).
|
||||
|
||||
### Your `.agents` is shared, read-only
|
||||
|
||||
A harness with no skills is a harness you have to re-brief on every run, so `~/.agents`
|
||||
— skills, prompt templates, your standing `AGENTS.md` — is bind-mounted at
|
||||
`/home/sandbox/.agents`, which is where pi looks for user-level skills. Nothing is
|
||||
copied; it is one mount, and it is **read-only**:
|
||||
|
||||
```bash
|
||||
pi # your skills are there
|
||||
pi --no-agents # a run where the harness sees none of them
|
||||
pi --agents-dir ~/work/.agents # a different set, e.g. per-team
|
||||
```
|
||||
|
||||
Read-only is not a convenience limit. A skill is an instruction the model follows
|
||||
*later*, on the host, outside the sandbox — so a sandbox that could edit `~/.agents`
|
||||
had a persistence path out of itself, planted as a helpful-looking `SKILL.md`. There is
|
||||
no flag to make it writable, and the `ro` holds even with `--insecure-keep-root`.
|
||||
|
||||
A project's own `.agents/` needs no setting at all: it is inside the project mount, and
|
||||
harnesses that read both (`pi` reads `~/.agents/skills` and `.agents/skills`) see the
|
||||
project's next to yours. What that costs in exposure is spelled out in
|
||||
[`docs/threat-model.md`](docs/threat-model.md): whatever is in those files — including
|
||||
any token someone pasted into a skill — is now readable by the agent you are sandboxing.
|
||||
Run `--no-agents` if a set of skills is not one you would hand to a stranger.
|
||||
|
||||
## Pi inside the sandbox
|
||||
|
||||
The image has `pi` baked in, and its home is a volume, so out of the box the sandboxed
|
||||
|
||||
Reference in New Issue
Block a user