docs: replace stale .sh paths with claude_bottle/*.py equivalents
test / run tests/run_tests.py (push) Successful in 13s
test / run tests/run_tests.py (push) Successful in 13s
Cleans up references to the pre-refactor bash layout (cli.sh, lib/*.sh, scripts/*.sh) across README, Dockerfile, the pipelock PRD, and research notes. Refreshes line numbers in the oauth-token note against the current cli/start.py. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
+3
-3
@@ -17,7 +17,7 @@ FROM node:22-slim
|
||||
# image, those features fail in surprising ways once the user does any
|
||||
# real work. ca-certificates is already in the slim base; listed for
|
||||
# clarity in case the base ever drops it. socat is the privileged
|
||||
# forwarder for the in-container ssh-agent (see lib/ssh.sh): the agent
|
||||
# forwarder for the in-container ssh-agent (see claude_bottle/ssh.py): the agent
|
||||
# runs as root and rejects non-root connections, so socat sits between
|
||||
# node and the agent socket.
|
||||
RUN apt-get update \
|
||||
@@ -38,7 +38,7 @@ USER node
|
||||
WORKDIR /home/node
|
||||
|
||||
# Pre-create the skills directory so PRD 0002's host->container skill
|
||||
# copier (scripts/lib/skills.sh) drops files into a path owned by the
|
||||
# copier (claude_bottle/skills.py) drops files into a path owned by the
|
||||
# `node` user. `skills_copy_into` also `mkdir -p`s defensively, but
|
||||
# baking it into the image avoids a permission-confusion footgun if a
|
||||
# future change to the launcher copies in as a different user.
|
||||
@@ -58,7 +58,7 @@ RUN cat > "$HOME/.claude.json" <<JSON
|
||||
JSON
|
||||
|
||||
# Default to an interactive claude session. In the v1 launcher,
|
||||
# `scripts/start.sh` runs the container detached and uses `docker exec`
|
||||
# `claude_bottle/cli/start.py` runs the container detached and uses `docker exec`
|
||||
# to attach a TTY, but this CMD makes `docker run -it claude-bottle` also
|
||||
# do something useful for ad-hoc debugging.
|
||||
CMD ["claude"]
|
||||
|
||||
@@ -9,7 +9,7 @@ Spins up an isolated container for running Claude Code with a curated set of ski
|
||||
Each container is a bottle; Claude is the genie inside. The genie has
|
||||
broad powers within the bottle — read, write, run anything — but it
|
||||
cannot escape to the host. You uncork one bottle per agent
|
||||
(`./cli.sh start <agent>`), many bottles run in parallel, and each
|
||||
(`./cli.py start <agent>`), many bottles run in parallel, and each
|
||||
one's powers are scoped to what the manifest grants it: a curated set
|
||||
of skills, env vars, and a starting prompt. When the session ends the
|
||||
bottle is destroyed and the genie does not persist.
|
||||
@@ -97,7 +97,7 @@ as `CLAUDE_BOTTLE_OAUTH_TOKEN`:
|
||||
export CLAUDE_BOTTLE_OAUTH_TOKEN="<token>"
|
||||
```
|
||||
|
||||
`cli.sh` automatically forwards it to every container as
|
||||
`cli.py` automatically forwards it to every container as
|
||||
`CLAUDE_CODE_OAUTH_TOKEN` via `docker run -e` — no manifest wiring
|
||||
required, and the value is never written to disk or placed on argv.
|
||||
|
||||
|
||||
@@ -43,12 +43,12 @@ The feature works when all of the following are observable:
|
||||
|
||||
The feature is **done** when all of the following ship:
|
||||
|
||||
- `cli.sh start` brings up a per-agent pipelock sidecar on a `--internal`
|
||||
- `cli.py start` brings up a per-agent pipelock sidecar on a `--internal`
|
||||
Docker network and points the agent's `HTTPS_PROXY` at it.
|
||||
- A per-agent pipelock YAML config is generated from a bottle-level
|
||||
`egress.allowlist` field, plus baked-in defaults for Claude Code's
|
||||
required hosts so basic bottles work out of the box.
|
||||
- The existing `cli.sh` y/N preflight shows the resolved allowlist before
|
||||
- The existing `cli.py` y/N preflight shows the resolved allowlist before
|
||||
launch.
|
||||
- When the agent container exits, the pipelock sidecar and the internal
|
||||
network are torn down cleanly (no orphaned containers or networks).
|
||||
@@ -76,7 +76,7 @@ The feature is **done** when all of the following ship:
|
||||
- Pipelock sidecar container lifecycle (start, attach to network,
|
||||
receive config, stop on agent exit).
|
||||
- `HTTPS_PROXY` / `HTTP_PROXY` injection into the agent container.
|
||||
- Preflight integration: the existing y/N plan in `cli.sh` lists the
|
||||
- Preflight integration: the existing y/N plan in `cli.py` lists the
|
||||
resolved allowlist.
|
||||
|
||||
### Out of scope
|
||||
@@ -95,30 +95,31 @@ The feature is **done** when all of the following ship:
|
||||
|
||||
### New services / components
|
||||
|
||||
Two new files under `lib/`:
|
||||
Two new modules under `claude_bottle/`:
|
||||
|
||||
- **`lib/pipelock.sh`** — pipelock-specific logic. Generates the
|
||||
per-bottle YAML config from the manifest's `egress` block plus baked-in
|
||||
defaults; copies the YAML into the sidecar via `docker cp`; starts and
|
||||
stops the sidecar container; resolves the allowlist for display in the
|
||||
preflight.
|
||||
- **`lib/network.sh`** — Docker network plumbing. Creates the per-agent
|
||||
`--internal` network (named `claude-bottle-net-<slug>` with the same
|
||||
slug-and-suffix scheme used for container names), attaches the agent
|
||||
and sidecar to it, removes it on teardown. Kept separate from
|
||||
`lib/docker.sh` so a future PRD can add non-pipelock network controls
|
||||
without entangling them with pipelock specifics.
|
||||
- **`claude_bottle/pipelock.py`** — pipelock-specific logic. Generates
|
||||
the per-bottle YAML config from the manifest's `egress` block plus
|
||||
baked-in defaults; copies the YAML into the sidecar via `docker cp`;
|
||||
starts and stops the sidecar container; resolves the allowlist for
|
||||
display in the preflight.
|
||||
- **`claude_bottle/network.py`** — Docker network plumbing. Creates the
|
||||
per-agent `--internal` network (named `claude-bottle-net-<slug>` with
|
||||
the same slug-and-suffix scheme used for container names), attaches
|
||||
the agent and sidecar to it, removes it on teardown. Kept separate
|
||||
from `claude_bottle/docker.py` so a future PRD can add non-pipelock
|
||||
network controls without entangling them with pipelock specifics.
|
||||
|
||||
This split mirrors the existing per-concern lib/ pattern
|
||||
(`manifest.sh`, `env_resolve.sh`, `skills.sh`, `ssh.sh`).
|
||||
This split mirrors the existing per-concern module pattern
|
||||
(`manifest.py`, `env_resolve.py`, `skills.py`, `ssh.py`).
|
||||
|
||||
### Existing code touched
|
||||
|
||||
- **`cli.sh`** — wire the new lifecycle into `start`: create the
|
||||
internal network, launch the pipelock sidecar, then launch the agent
|
||||
container with `HTTPS_PROXY` / `HTTP_PROXY` set to the sidecar's
|
||||
service name. Add the resolved allowlist to the preflight y/N output.
|
||||
Tear down sidecar + network in the existing exit trap.
|
||||
- **`claude_bottle/cli/start.py`** — wire the new lifecycle into the
|
||||
`start` subcommand: create the internal network, launch the pipelock
|
||||
sidecar, then launch the agent container with `HTTPS_PROXY` /
|
||||
`HTTP_PROXY` set to the sidecar's service name. Add the resolved
|
||||
allowlist to the preflight y/N output. Tear down sidecar + network in
|
||||
the existing exit handler.
|
||||
- **`README.md`** — public-facing description should mention that
|
||||
agent containers route HTTP egress through pipelock by default, and
|
||||
document the new `egress.allowlist` bottle field.
|
||||
@@ -128,9 +129,10 @@ This split mirrors the existing per-concern lib/ pattern
|
||||
the image. This keeps the image agnostic to whether a sidecar is in use
|
||||
(useful if a future bottle definition opts out of the proxy for testing).
|
||||
|
||||
`lib/docker.sh` may grow one or two helpers if there is a clean place
|
||||
for shared primitives, but the network-specific helpers live in
|
||||
`lib/network.sh`. Decide during implementation; not a contract.
|
||||
`claude_bottle/docker.py` may grow one or two helpers if there is a
|
||||
clean place for shared primitives, but the network-specific helpers
|
||||
live in `claude_bottle/network.py`. Decide during implementation; not a
|
||||
contract.
|
||||
|
||||
### Data model changes
|
||||
|
||||
@@ -174,9 +176,9 @@ bottle share the same allowlist.
|
||||
|
||||
- **Pipelock binary** is pulled from
|
||||
`ghcr.io/luckypipewrench/pipelock@sha256:<digest>`. The digest is
|
||||
pinned in `lib/pipelock.sh` (or a sibling `.env`-shaped constants
|
||||
file) and bumped deliberately, mirroring the claude-code version
|
||||
pinning pattern in `Dockerfile`.
|
||||
pinned in `claude_bottle/pipelock.py` (or a sibling constants module)
|
||||
and bumped deliberately, mirroring the claude-code version pinning
|
||||
pattern in `Dockerfile`.
|
||||
- No new host-side runtimes. The pipelock image is the only new
|
||||
external artifact.
|
||||
|
||||
@@ -189,10 +191,10 @@ bottle share the same allowlist.
|
||||
which features used by this PRD are Apache-2.0-core. v1's plan
|
||||
(proxy + 48 default DLP patterns + subdomain entropy + sidecar
|
||||
topology) is expected to be core-only, but this should be confirmed.
|
||||
- **Where to put the digest pin.** A constant in `lib/pipelock.sh` is
|
||||
the lowest-friction option; a separate `lib/versions.sh` (or similar)
|
||||
may be cleaner once there are multiple pinned dependencies. Decide
|
||||
during implementation.
|
||||
- **Where to put the digest pin.** A constant in
|
||||
`claude_bottle/pipelock.py` is the lowest-friction option; a separate
|
||||
`claude_bottle/versions.py` (or similar) may be cleaner once there
|
||||
are multiple pinned dependencies. Decide during implementation.
|
||||
- **Per-agent overrides.** The PRD scopes egress to the bottle. If a
|
||||
later use case calls for tightening (not loosening) the allowlist for
|
||||
one agent within a bottle, revisit. Out of scope for v1.
|
||||
|
||||
@@ -34,7 +34,7 @@ Three pieces in combination give a 100% guarantee:
|
||||
- `get_status(job_id)` — check running/done
|
||||
- `get_output(job_id)` — read results
|
||||
|
||||
3. **Non-interactive container run mode** — `cli.sh run <agent> "<task>"` passes the task to `claude --print` inside the container and captures output. Currently `cli.sh start` is interactive only; this mode does not yet exist.
|
||||
3. **Non-interactive container run mode** — `cli.py run <agent> "<task>"` passes the task to `claude --print` inside the container and captures output. Currently `cli.py start` is interactive only; this mode does not yet exist.
|
||||
|
||||
## Proposal
|
||||
|
||||
@@ -42,7 +42,7 @@ Build host-dispatch-to-container in two deliverables:
|
||||
|
||||
**Deliverable 1: Non-interactive run mode for claude-bottle**
|
||||
|
||||
Extend `cli.sh` with a `run <agent> <task>` subcommand. Starts the container, writes the task prompt to a file inside it (same `docker cp` pattern used for `--append-system-prompt-file`), invokes `claude --print` with the prompt, streams stdout back to the host, and exits when Claude finishes. Results committed and pushed from inside the container as usual.
|
||||
Extend `cli.py` with a `run <agent> <task>` subcommand. Starts the container, writes the task prompt to a file inside it (same `docker cp` pattern used for `--append-system-prompt-file`), invokes `claude --print` with the prompt, streams stdout back to the host, and exits when Claude finishes. Results committed and pushed from inside the container as usual.
|
||||
|
||||
**Deliverable 2: MCP server wrapping claude-bottle**
|
||||
|
||||
|
||||
@@ -234,7 +234,7 @@ which does not work on macOS Desktop.
|
||||
|
||||
Moderate. The script itself is well-understood and can be lifted nearly
|
||||
verbatim from Anthropic's devcontainer repo. The integration points in
|
||||
`cli.sh` are:
|
||||
`cli.py` are:
|
||||
|
||||
1. Pass `--cap-add NET_ADMIN --cap-add NET_RAW` in the `docker run` invocation.
|
||||
2. `docker cp` an `init-firewall.sh` script into the container (alongside
|
||||
|
||||
@@ -20,11 +20,12 @@ that does not route through `ANTHROPIC_BASE_URL` at all.
|
||||
|
||||
## How the token reaches claude today
|
||||
|
||||
1. `cli.sh:526–528` — host's `CLAUDE_BOTTLE_OAUTH_TOKEN` is exported into
|
||||
the launcher process as `CLAUDE_CODE_OAUTH_TOKEN`, then forwarded with
|
||||
1. `claude_bottle/cli/start.py` (around line 237–238) — host's
|
||||
`CLAUDE_BOTTLE_OAUTH_TOKEN` is exported into the launcher process as
|
||||
`CLAUDE_CODE_OAUTH_TOKEN`, then forwarded with
|
||||
`docker run -e CLAUDE_CODE_OAUTH_TOKEN` (no `=value`, so the value
|
||||
never lands on argv — good).
|
||||
2. `cli.sh:603–605` — claude is launched via
|
||||
2. `claude_bottle/cli/start.py` (around line 318–325) — claude is launched via
|
||||
`docker exec -it <container> claude …`, which inherits the container
|
||||
PID 1's env, including the token.
|
||||
3. claude runs as `node` (UID 1000) with `--dangerously-skip-permissions`.
|
||||
|
||||
@@ -273,7 +273,7 @@ reach, and avoids the `--best-effort` issue on macOS Docker Desktop.
|
||||
The claude-bottle manifest model would need one new piece of plumbing: a
|
||||
per-agent pipelock ACL YAML generated from the manifest's `allowlist`
|
||||
and `ssh` entries, analogous to what the smokescreen section of
|
||||
`network-egress-guard.md` already sketches. The `cli.sh` changes required
|
||||
`network-egress-guard.md` already sketches. The `cli.py` changes required
|
||||
are the same pattern: `docker network create --internal`, `docker run` for
|
||||
the proxy container, then `docker run` for the agent with `HTTPS_PROXY`
|
||||
injected.
|
||||
@@ -408,7 +408,7 @@ The reasoning:
|
||||
subdomain-entropy DNS exfil detection, MCP scanning, and request
|
||||
redaction. The integration shape for claude-bottle is identical: a
|
||||
separate container on an internal Docker network, with the agent's
|
||||
`HTTPS_PROXY` pointing at it. The `cli.sh` changes are the same pattern.
|
||||
`HTTPS_PROXY` pointing at it. The `cli.py` changes are the same pattern.
|
||||
|
||||
2. **The DLP layer is the most direct answer to the content-tripwire gap.**
|
||||
The `secret-exfil-tripwire-encodings.md` note concluded that no
|
||||
|
||||
Reference in New Issue
Block a user