Skip to content
Open
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
84 changes: 55 additions & 29 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,40 +10,66 @@ are.

The security design follows from that single fact: the agent runs *inside* a
hardened Docker container, and **that container is the permission boundary** —
not the prompt, not the agent's judgment. The container is configured to
drop all Linux capabilities, forbid privilege escalation
(`--no-new-privileges`), run as a non-root user, and cap memory, CPU, and PID
counts. A malicious README can do whatever it likes to that disposable
container; the goal is that it can't reach your host, your network, or your
other runs.

## Known tradeoffs (MVP, documented on purpose)

These are real limitations of the current release. We would rather name them
than let you discover them.

- **The API key enters the sandbox.** In the current design, the agent needs
model credentials inside the container — whichever provider you run on
(`ANTHROPIC_API_KEY`/`CLAUDE_CODE_OAUTH_TOKEN`, or `OPENAI_API_KEY` /
not the prompt, not the agent's judgment. A malicious README can do whatever it
likes to that disposable container; the goal is that it can't reach your host,
your secrets beyond what you deliberately inject, or your other runs.

## Sandbox claims vs roadmap

Every row marked **enforced today** was re-verified against the cited code on
this checkout. Rows marked **planned** or **known limitation** are intentional
honesty, not marketing. Parent epic for planned egress/credential hardening:
[#64](https://github.com/alphacrack/readme2demo/issues/64).

| Claim (agent + verify containers) | Status | Where (verify on your checkout — line numbers drift) |
|---|---|---|
| All Linux capabilities dropped | **enforced today** | `src/readme2demo/sandbox.py` `start()` — `"--cap-drop", "ALL"` |
| No privilege escalation | **enforced today** | `sandbox.py` `start()` — `"--security-opt", "no-new-privileges"` |
| Memory / CPU / PID caps | **enforced today** | `sandbox.py` `Sandbox` defaults `memory="4g"`, `cpus="2"`, `pids_limit=512`; applied in `start()` |
| Non-root user in image | **enforced today** | `images/base/Dockerfile` — `useradd … demo` then `USER demo` |
| Target repo mounted read-only | **enforced today** | `agent.py` — mount `(…/repo, "/repo", "ro")`; agent copies to writable `/work` |
| Verify replays in a fresh container without model credentials | **enforced today** | `verify.py` — `Sandbox(...)` built with **no** `env=` argument |
| Docker socket only on explicit opt-in | **enforced today** | `agent.py` / `verify.py` — socket mounted only when `cfg.allow_docker_socket` |
| Network egress domain allowlist | **planned (v0.8)** — today: plain Docker network | `sandbox.py` default `network: str = "bridge"`; `start()` passes `"--network", self.network`. No allowlist in tree. See [#64](https://github.com/alphacrack/readme2demo/issues/64) |
| Host-side key-injecting proxy (credentials never enter the sandbox) | **planned (v0.8)** | [#64](https://github.com/alphacrack/readme2demo/issues/64) |
| Disk quotas on the work volume | **planned (v0.8)** | [#64](https://github.com/alphacrack/readme2demo/issues/64) |
| Red-team acceptance harness (hostile-README integration tests) | **planned (v0.8)** | [#64](https://github.com/alphacrack/readme2demo/issues/64) |
| API key visible on the docker argv (`-e KEY=VALUE`) | **known limitation** | `sandbox.py` `start()` env loop (`-e f"{k}={v}"`); agent passes `env=env`. Tracked in [#51](https://github.com/alphacrack/readme2demo/issues/51) |
| Render-stage container hardening (cap-drop / no-new-privileges / pids) | **known limitation** | `render.py` builds its own `docker run` with `--memory`/`--cpus`/`--network` only (no `--cap-drop ALL`, no `no-new-privileges`, no `--pids-limit`). Untrusted-execution surface separate from `Sandbox.start()`. See architecture/README.md |

### Known tradeoffs (narrative)

These match the table; keep them named so adopters are not surprised.

- **The API key enters the sandbox (MVP).** The agent stage constructs
`Sandbox(..., env=env)` so the model credential is present inside the
container and also appears on the `docker run` argv — whichever provider you
run on (`ANTHROPIC_API_KEY`/`CLAUDE_CODE_OAUTH_TOKEN`, or `OPENAI_API_KEY` /
`GEMINI_API_KEY` via the litellm-style `LLM_API_KEY` with the provider
presets). A malicious repo that fully
compromises the agent could read them. **Mitigation: use a dedicated,
low-limit API key for runs — never your primary key.** A host-side,
key-injecting egress proxy that keeps credentials out of the sandbox is on
the roadmap.
- **`--allow-docker-socket` pierces the isolation.** Some tools legitimately
manage their own containers, so this flag mounts the Docker socket into the
sandbox. Doing so is effectively root on the host's Docker daemon. **Only
pass it for repos you trust**, and the CLI treats it as an explicit,
opt-in security tradeoff.
- **Network egress is allowlisted, not eliminated.** Runs need to clone repos
and pull packages, so the sandbox has outbound network access. Data
exfiltration by a determined, compromised agent is not fully prevented in
this release.
presets). A fully compromised agent can read it. **Mitigation: use a
dedicated, low-limit key — never your primary key.** Host-side key injection
is planned in [#64](https://github.com/alphacrack/readme2demo/issues/64);
argv exposure is tracked in
[#51](https://github.com/alphacrack/readme2demo/issues/51). Related:
[#52](https://github.com/alphacrack/readme2demo/issues/52).
- **`--allow-docker-socket` pierces isolation.** When set, the Docker socket is
mounted read-write into the sandbox (agent and verify; render can also mount
it when enabled). That is effectively root on the host Docker daemon. **Only
pass it for repos you trust.**
- **Network egress is open bridge networking today, not allowlisted.** Runs need
to clone repos and pull packages, so the default is `--network bridge` with
no domain filter. Data exfiltration by a determined, compromised agent is not
fully prevented in this release. Domain allowlisting is planned for v0.8
([#64](https://github.com/alphacrack/readme2demo/issues/64)).
- **Render container is less hardened than agent/verify.** The render stage does
not go through `Sandbox.start()`; see the table row above.

Do not run readme2demo against untrusted repos on a machine that holds
secrets you can't afford to rotate. Prefer a throwaway VM or CI runner.

A shorter operator-facing summary lives in
[`docs/security.md`](docs/security.md) and links back here.

## Supported versions

readme2demo is pre-1.0 and moves fast. Security fixes land on `main` and in
Expand Down
28 changes: 23 additions & 5 deletions docs/security.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,42 @@ design concern, not an afterthought.

## The sandbox is the boundary

The agent runs *inside* a hardened Docker container:
The **agent and verify** stages run *inside* hardened Docker containers (`Sandbox.start()`):

- `--cap-drop ALL` and `--security-opt no-new-privileges`
- non-root user
- non-root user (`USER demo` in the base image)
- memory, CPU, and PID limits
- the repo is mounted read-only; the agent works on a writable copy

The verify stage then replays the distilled `commands.sh` in a **separate,
fresh container** with no state carried over from the agent's run.
fresh container** with no model credentials and no state carried over from the
agent's run.

For a **claims-vs-roadmap table** (enforced today vs planned v0.8 vs known
limitations, each with code pointers), see the root
[`SECURITY.md`](https://github.com/alphacrack/readme2demo/blob/main/SECURITY.md). That table is the source of truth; this page
is the short summary.

## Known tradeoffs

- **Render container is not fully hardened like agent/verify.** `render.py` runs
its own `docker run` with memory/CPU/network limits only (no cap-drop /
no-new-privileges / pids-limit). See the root SECURITY.md table.


- **API key in the sandbox** (MVP): the model credential currently enters the
sandbox. Use a dedicated, low-limit key. A host-side, key-injecting egress
proxy is planned so the credential never crosses the boundary.
sandbox (and the `docker run` argv). Use a dedicated, low-limit key. A
host-side, key-injecting egress proxy is planned so the credential never
crosses the boundary ([#64](https://github.com/alphacrack/readme2demo/issues/64);
argv exposure tracked in
[#51](https://github.com/alphacrack/readme2demo/issues/51)).
- **`--allow-docker-socket`** is off by default. Enabling it mounts the host
Docker socket into the sandbox so demos that manage containers work — but the
socket pierces isolation. Only enable it for repositories you trust.
- **Network egress is open bridge networking today, not allowlisted.** The
sandbox defaults to Docker `--network bridge` so package installs and git
clones work. Domain allowlisting is planned for v0.8 ([#64](https://github.com/alphacrack/readme2demo/issues/64)). Until then,
treat outbound network from a compromised agent as possible.

## Reporting a vulnerability

Expand Down