Setup
Get your environment running
Two independent paths to the same two scenarios. Run whichever your facilitator points you at — both were run end-to-end against this exact data before this guide was written, not just described.
Section 1 — Exact Arsenal setup, in a Kali Docker container
Original instructions: build one Kali image, clone to all laptops. One container instead of a fleet — same steps, no physical laptops to provision.
Prerequisites
- Docker, with the
kalilinux/kali-rollingimage (docker pull kalilinux/kali-rollingif you don't have it yet). - A
.envfile in thetollbooth/folder (copied fromcasky-runner-phase1/.env) with a workingANTHROPIC_API_KEY— the shared key. It's git-ignored and lives here, not the repo root, so every command below is self-contained in one folder:cp /path/to/casky-runner-phase1/.env tollbooth/.env
casky-runner-phase1/.env writes values as ANTHROPIC_API_KEY="sk-ant-..."
(quoted). docker run --env-file does not strip quotes — it
passes the literal string, quote marks included. Result: a key that starts with "
instead of sk-ant-, and Claude Code fails with Invalid API key —
looks like a bad/revoked key, isn't. Fix once, right after copying:
sed -i.bak -E 's/^([A-Z_][A-Z0-9_]*)="(.*)"$/\1=\2/' tollbooth/.env && rm tollbooth/.env.bak
Confirm it worked before moving on — should print 1, not 0:
grep '^ANTHROPIC_API_KEY=' tollbooth/.env | cut -d= -f2- | grep -c '^sk-ant-'
Steps
# Run every command below from THIS folder (casky-workshops/tollbooth/) — $(pwd) becomes the
# container's /root/tollbooth, and .env/the scripts only exist here, not the repo root.
# 0. Start a persistent Kali container with this folder mounted + the shared key available
docker run -d --name kali-tollbooth \
--env-file .env \
-v "$(pwd)":/root/tollbooth \
kalilinux/kali-rolling sleep infinity
# Sanity check — catches a wrong-directory mount immediately instead of 3 steps from now
docker exec kali-tollbooth test -f /root/tollbooth/verify.sh \
&& echo "[+] mount OK — verify.sh found at /root/tollbooth/verify.sh" \
|| echo "[!] wrong directory — re-run from casky-workshops/tollbooth/, then: docker rm -f kali-tollbooth"
# 1. tshark, jq (tcpdump, python3-scapy usually present on Kali)
docker exec kali-tollbooth bash -c \
"apt-get update -y && DEBIAN_FRONTEND=noninteractive apt-get install -y tshark jq tcpdump python3-scapy git curl ca-certificates"
# 2. Install Claude Code; pre-authenticate with the shared key
# (ANTHROPIC_API_KEY is already in the container's env via --env-file above — Claude Code
# picks it up automatically, no login flow needed)
docker exec kali-tollbooth bash -c "curl -fsSL https://claude.ai/install.sh -o /tmp/install.sh && bash /tmp/install.sh"
docker exec kali-tollbooth bash -c 'export PATH="$HOME/.local/bin:$PATH"; claude --version'
# 3. Mount skills into ~/.claude/skills
docker exec -w /root/tollbooth kali-tollbooth bash -c "chmod +x setup-skills.sh verify.sh reset.sh start.sh && ./setup-skills.sh"
# 4. This folder is already at /root/tollbooth (bind-mounted in step 0 — no copy needed).
# Run ./verify.sh — expect 9/9 PASS.
docker exec -w /root/tollbooth kali-tollbooth ./verify.sh
# 5. Print Arsenal-CheatSheet-Book.pdf (color; answer pages are red) — one per attendee.
open Arsenal-CheatSheet-Book.pdf # macOS; use your OS's print/open command
# 6. Bash into the container and start Claude Code interactively — this is what you
# actually drive during the exercise (steps 0-4 above were one-off unattended setup).
docker exec -it -w /root/tollbooth kali-tollbooth bash
# ...now inside the container's shell:
export PATH="$HOME/.local/bin:$PATH"
./start.sh # welcome banner + sanity check
claude # drops into an interactive Claude Code session — paste the scenario
# prompt from the cheat sheet (or this wiki's scenario pages) and let it work
All packages installed clean, Claude Code 2.1.241 on PATH, 10/10 skills
linked, and a real round-trip against the Anthropic API:
$ claude --print "Reply with exactly the single word: PONG"
PONG
== tooling ==
ok tshark
ok jq
ok tcpdump
== pcap ==
PASS IMDS HTTP traffic present (2)
PASS SSRF request references metadata service (2)
PASS leaked AccessKeyId recoverable from pcap (2)
== cloudtrail ==
PASS attacker actions on leaked key (12)
PASS S3 GetObject exfil events (9)
PASS IAM enumeration present (2)
== scenario 2 (opendoor) ==
PASS S3 bucket policy is public (Principal *) (1)
PASS IAM privilege-escalation path present (3)
PASS IMDSv1 allowed (HttpTokens=optional)
----
RESULT: 9 passed, 0 failed
This laptop is READY.
./start.sh (step 6) confirmed too:
[+] lab data present: pcap + cloudtrail/ (S1) + opendoor/ (S2)
[+] agent skills mounted at /root/.claude/skills (10 skills)
[+] Claude Code found.
mode: online (shared key)
Work Scenario 1 or 2 inside that interactive Claude Code session (skills already mounted),
or fall back to the raw tshark/jq commands on the cheat sheet if the
agent path isn't available. exit the claude session and the container
shell (two exits) between attendees, then run
docker exec -w /root/tollbooth kali-tollbooth ./reset.sh from outside to restore
the data.
Section 2 — Same scenarios, run on Casky Box
Casky Box doesn't need per-laptop Claude Code installs or hand-picked skill symlinks — the
casky-runner container already ships all 817 skills (including
the 10 this kit hand-picks) mounted read-only, and its classifier picks the relevant ones from
evidence automatically instead of you curating a list. The trade: no live "attack traffic"
step here — TollBooth/OpenDoor's data is pre-built synthetic evidence, so this is a pure
Path A (evidence-driven) investigation, not a lab-target exercise.
Prerequisites
casky-runner-phase1 running, using its own .env —
nothing to copy in here, unlike Section 1. docker compose up -d automatically
reads .env from the same directory as docker-compose.yml, no
--env-file flag needed, and (unlike Section 1's docker run --env-file)
Compose's own parser strips quotes correctly, so the file works as-is:
cd /path/to/casky-runner-phase1
docker compose up -d
If casky-runner/casky-db are already up from earlier, this is a
no-op — just confirm with docker compose ps.
Steps
# Run every command below from THIS folder (casky-workshops/tollbooth/) — cloudtrail/*.json,
# opendoor/*.json below are relative paths. Live-caught twice: running from
# casky-runner-phase1/ instead silently writes into the wrong repo and
# 'cloudtrail/*.json' matches nothing ("zsh: no matches found").
cd /path/to/casky-workshops/tollbooth
test -f lab-tollbooth.pcap && echo "[+] correct folder" || echo "[!] wrong folder — cd to casky-workshops/tollbooth first"
# 1. Turn the pcap into readable evidence text (tshark isn't needed inside casky-runner —
# reuse the Kali container from Section 1, or run tshark locally if you have it)
docker exec kali-tollbooth tshark -r /root/tollbooth/lab-tollbooth.pcap -Y http \
-T fields -e frame.time -e ip.src -e ip.dst -e http.request.method -e http.request.full_uri -e http.response.code \
> tollbooth-pcap.txt
# 2. Combine with the CloudTrail evidence (same incident, later stage of the same attack)
{
echo "=== HTTP/pcap traffic (SSRF -> IMDS credential leak) ==="; cat tollbooth-pcap.txt
echo; echo "=== CloudTrail events (same account, following hours) ==="
jq -c '.Records[]' cloudtrail/*.json
} > tollbooth-full.txt
# 3. OpenDoor's evidence is already text — just concatenate the configs
for f in opendoor/*.json; do echo "--- $(basename "$f") ---"; cat "$f"; echo; done > opendoor-full.txt
# 4. Copy both into casky-runner-phase1's evidence bind mount — a LIVE bind mount, not a
# copy-once. Anything dropped here shows up inside the running container immediately.
cp tollbooth-full.txt opendoor-full.txt /path/to/casky-runner-phase1/evidence/
docker exec casky-runner ls /var/casky/evidence/ # confirm they're already there
# 5. Investigate — TollBooth (reactive). --auto runs every step's agent for real;
# drop --auto for the manual mode where you paste each step's tool output yourself.
docker exec -it casky-runner casky harness --auto -i /var/casky/evidence/tollbooth-full.txt
# 6. Investigate — OpenDoor (proactive)
docker exec -it casky-runner casky harness --auto -i /var/casky/evidence/opendoor-full.txt
Against the real TollBooth evidence, the classifier independently found and validated 7 MITRE techniques at 83.4% confidence from the combined pcap + CloudTrail text alone — no hints, no pre-picked skill list:
| # | Technique | Skill | Category |
|---|---|---|---|
| 1 | Exploit Public-Facing Application (T1190) | exploiting-server-side-request-forgery | web-app |
| 2 | Unsecured Credentials: Credentials In Files (T1552.001) | detecting-aws-credential-exposure-with-trufflehog | cloud |
| 3 | Gather Cloud Infrastructure Details (T1526) | detecting-aws-cloudtrail-anomalies | cloud |
| 4 | Data from Cloud Storage (T1530) | detecting-s3-data-exfiltration-attempts | cloud |
| 5 | Valid Accounts: Cloud Accounts (T1078.004) | detecting-compromised-cloud-credentials | cloud |
| 6 | Unsecured Credentials: Cloud Instance Metadata API (T1552.007) — the core TollBooth story | analyzing-kubernetes-audit-logs | cloud |
| 7 | Remote Services: SSH (T1021.004) | detecting-aws-cloudtrail-anomalies | cloud |
That's the same territory the original kit's 10 hand-picked skills cover, reached without anyone curating a list — including step 6 correctly landing on T1552.007, the exact technique this scenario is built around, purely from the evidence text.
--auto runs every step's agent for real and produces a real, inspectable
tool-call transcript per step ([VERIFIED] Skill script executed: YES/NO) — a
non-hallucination guarantee. Open casky-ui (http://localhost:8766) afterward for
the Plan / Execution / Findings / Remediation tabs.
See the comparison table on the home page for what's different between Section 1 and Section 2.
Done for the day, or resetting for the next attendee? See Cleanup on the Troubleshooting page.