From bef0ba080d51c5d69244229d083c202aa8f85f03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Lima?= <165610515+TheHefty@users.noreply.github.com> Date: Sat, 8 Aug 2026 16:07:38 +0000 Subject: [PATCH] docs: add a security policy with the container's threat model MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Private vulnerability reporting is now enabled on the repo, so the policy points at GitHub's advisory flow rather than an email address. The substance is the scope section. It separates the host boundary — the one that is meant to hold, and where a finding is a real vulnerability — from the boundaries inside the container, which are weaker on purpose. The four deliberate ones are named explicitly so they are not rediscovered as surprises and reported as bugs: ai-jail's restrictions are advisory within the container (a `--rw-map /config/.docker` grant reaches the whole container's filesystem through the nested daemon), code-server runs with an empty PASSWORD, SYS_ADMIN plus the unconfined seccomp/systempaths exist so bwrap can create user namespaces, and nested containers get no cgroup limits of their own. Each points back at docs/OVERVIEW.md, which stays the authoritative account. Co-Authored-By: Claude Opus 5 (1M context) --- SECURITY.md | 82 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 SECURITY.md diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..77642b8 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,82 @@ +# Security Policy + +## Supported versions + +Only the most recent release is supported. Fixes ship as a new release rather than as patches +backported to older tags — see [`CHANGELOG.md`](CHANGELOG.md) for what has landed. + +If you consume this template as a git submodule, pin it to a **tag** rather than a bare commit. +A bare commit from a branch that is later squash-merged becomes unreachable, and every fresh clone +of your repo then fails its `git submodule update`. + +## Reporting a vulnerability + +Report privately through GitHub: **[Security → Report a +vulnerability](https://github.com/TheHefty/jvsl.env.agents.code-server/security/advisories/new)**. + +Please do not open a public issue for something you believe is exploitable. + +This is a personal project with a single maintainer, so there is no guaranteed response time and no +bounty. Expect a best-effort acknowledgement, and please include what you did, what happened, and +what you expected instead — a reproduction against a container built from `setup` is worth far more +than a description of a suspicious-looking flag. + +## What this template is, and what it is not + +This is a **single-user development environment** for a personal workstation. It is not a +multi-tenant sandbox, not a hosting platform, and not a boundary you should place between yourself +and code you actively distrust. + +The boundary that is meant to hold is the **host boundary**: nothing running inside the container — +including the Claude Code agent — should be able to reach the host's filesystem, its Docker daemon, +or its network beyond what was deliberately handed in. The host's Docker socket is +deliberately *not* mounted for exactly this reason; `docker` inside the container talks to a nested +rootless daemon that cannot see the host's containers. If you find a way across that line, that is a +vulnerability and we want to hear about it. + +Boundaries *inside* the container are weaker on purpose, and are documented as such below. + +### In scope + +- Escaping the container to the host — filesystem, daemon, or privileged host resources. +- The nested rootless daemon reaching the host's daemon, containers, or images. +- code-server's port becoming reachable beyond the host's loopback interface (it is published as + `-p 127.0.0.1:0:8443`). +- Credentials or tokens baked into the built image, or leaked from the bind-mounted + `/config` / `~/.claude` state to somewhere they should not be. +- Supply-chain problems in the build itself: a `Dockerfile.frag` fetching an artifact over an + unverified channel, a compromised or typosquatted dependency, a pinned digest that does not + match what it claims. + +### Out of scope — deliberate design decisions + +These are known, documented, and accepted. Reports about them will be closed as intended behaviour, +though a report arguing the *tradeoff itself* is wrong is a reasonable thing to open a normal issue +about. + +- **`ai-jail`'s restrictions are advisory within the container.** They bound the agent's own shell, + not everything the agent can reach. In particular, an agent granted `--rw-map /config/.docker` + can talk to the nested daemon, and `docker run -v /:/probe` against that daemon hands it this + container's own root filesystem — including paths the sandbox hides. Writes are bounded by the + rootless uid mapping (container-root maps to `abc`), not by `ai-jail`. This was measured rather + than assumed, and the socket was kept knowingly; the reasoning is in + [`docs/OVERVIEW.md`](docs/OVERVIEW.md) under "Why the container is this permissive". +- **code-server runs with no password.** `start` passes an empty `PASSWORD=`, so any user or process + on the host that can reach the published loopback port gets the editor, and through it a shell in + the container. This is a single-user-workstation assumption, not an oversight. +- **`--cap-add=SYS_ADMIN` and `--security-opt seccomp=unconfined` / `systempaths=unconfined`.** + These exist so `ai-jail`'s `bwrap` sandbox can create user namespaces on distros whose AppArmor + policy restricts unprivileged namespace creation. Without them `ai-jail` cannot build its sandbox + at all. The properly narrow fix is a host AppArmor profile, which a container cannot install for + itself. +- **Nested containers get no cgroup limits of their own.** `/sys/fs/cgroup` is read-only, so the + rootless daemon cannot enforce per-container cpu/memory. Everything stays bounded by the outer + container's limits, which is the containment that matters here. +- **Anything you deliberately grant.** `--device` passthrough, extra `--rw-map` entries in a + project's `.ai-jail`, and mounts you add yourself widen the boundary by design. + +## Reviewing this yourself + +`docs/OVERVIEW.md` records the reasoning behind every permissive flag, including the decisions that +were reversed and why. If you are evaluating whether to adopt this template, that document — not +this file — is the honest account.