From 97cd603b9a3a774f55dd70d1422fd4deeda79412 Mon Sep 17 00:00:00 2001 From: NossXBT Date: Wed, 22 Jul 2026 16:24:15 +0700 Subject: [PATCH 1/2] docs(security): claims-vs-roadmap table for sandbox controls Replace the overstated "egress is allowlisted" wording with an honest table of enforced-today vs planned (v0.8 / #64) vs known limitations, each with code pointers. Align docs/security.md with the table. Closes #168 Closes #65 Signed-off-by: NossXBT --- SECURITY.md | 90 ++++++++++++++++++++++++++---------------------- docs/security.md | 21 ++++++++--- 2 files changed, 65 insertions(+), 46 deletions(-) diff --git a/SECURITY.md b/SECURITY.md index 1a00fa0..1abf16a 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -10,40 +10,58 @@ 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` / - `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. +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 | 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 | +| Host-side key-injecting proxy (credentials never enter the sandbox) | **planned (v0.8)** | #64 | +| Disk quotas on the work volume | **planned (v0.8)** | #64 | +| Red-team acceptance harness (hostile-README integration tests) | **planned (v0.8)** | #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 | + +### 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. 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; argv exposure is + tracked in #51. +- **`--allow-docker-socket` pierces isolation.** When set, the Docker socket is + mounted read-write into the sandbox (agent and verify). 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). (Earlier wording that said egress was already “allowlisted” was + incorrect and has been removed.) 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 @@ -78,15 +96,3 @@ What to expect: We support coordinated disclosure: please give us a reasonable window to ship a fix before any public write-up. - -## Scope - -**In scope:** sandbox escapes, isolation-flag bypasses, credential leakage -paths beyond the documented tradeoffs above, grounding bypasses that let an -unverified command reach published output (`tutorial.md`, `step_by_step.md`, -`commands.sh`, or the demo tape), and anything that turns a target repo into -host code execution. - -**Out of scope:** the documented MVP tradeoffs listed above, vulnerabilities -in target repos themselves (that's the point — we run them in a box), and -issues that require the operator to have already disabled the sandbox flags. diff --git a/docs/security.md b/docs/security.md index edceb50..e1eaa2a 100644 --- a/docs/security.md +++ b/docs/security.md @@ -8,21 +8,34 @@ design concern, not an afterthought. The agent runs *inside* a hardened Docker container: - `--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`](../SECURITY.md). That table is the source of truth; this page +is the short summary. ## Known tradeoffs - **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). Until then, + treat outbound network from a compromised agent as possible. ## Reporting a vulnerability From d3240431d7b300a029d70287647835dce3be46c4 Mon Sep 17 00:00:00 2001 From: NossXBT Date: Thu, 23 Jul 2026 05:56:44 +0700 Subject: [PATCH 2/2] =?UTF-8?q?docs(security):=20fix=20B1=E2=80=93B3=20rev?= =?UTF-8?q?iew=20blockers=20on=20claims=20table?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Absolute SECURITY.md link from docs/security.md (mkdocs strict) - Restore ## Scope section verbatim - Scope claims table to agent+verify; document render container gap - Restore credential env var names; link #51/#52/#64 with full URLs Signed-off-by: NossXBT --- SECURITY.md | 48 ++++++++++++++++++++++++++++++++++-------------- docs/security.md | 11 ++++++++--- 2 files changed, 42 insertions(+), 17 deletions(-) diff --git a/SECURITY.md b/SECURITY.md index 1abf16a..11e28ba 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -21,7 +21,7 @@ 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 | Status | Where (verify on your checkout — line numbers drift) | +| 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"` | @@ -30,11 +30,12 @@ honesty, not marketing. Parent epic for planned egress/credential hardening: | 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 | -| Host-side key-injecting proxy (credentials never enter the sandbox) | **planned (v0.8)** | #64 | -| Disk quotas on the work volume | **planned (v0.8)** | #64 | -| Red-team acceptance harness (hostile-README integration tests) | **planned (v0.8)** | #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 | +| 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) @@ -42,19 +43,26 @@ 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. 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; argv exposure is - tracked in #51. + 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 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). That is effectively - root on the host Docker daemon. **Only pass it for repos you trust.** + 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). (Earlier wording that said egress was already “allowlisted” was - incorrect and has been removed.) + ([#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. @@ -96,3 +104,15 @@ What to expect: We support coordinated disclosure: please give us a reasonable window to ship a fix before any public write-up. + +## Scope + +**In scope:** sandbox escapes, isolation-flag bypasses, credential leakage +paths beyond the documented tradeoffs above, grounding bypasses that let an +unverified command reach published output (`tutorial.md`, `step_by_step.md`, +`commands.sh`, or the demo tape), and anything that turns a target repo into +host code execution. + +**Out of scope:** the documented MVP tradeoffs listed above, vulnerabilities +in target repos themselves (that's the point — we run them in a box), and +issues that require the operator to have already disabled the sandbox flags. diff --git a/docs/security.md b/docs/security.md index e1eaa2a..b1ef665 100644 --- a/docs/security.md +++ b/docs/security.md @@ -5,7 +5,7 @@ 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 (`USER demo` in the base image) @@ -18,11 +18,16 @@ 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`](../SECURITY.md). That table is the source of truth; this page +[`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 (and the `docker run` argv). Use a dedicated, low-limit key. A host-side, key-injecting egress proxy is planned so the credential never @@ -34,7 +39,7 @@ is the short summary. 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). Until then, + 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