Self-hosted deployment of the pwn.college dojo (BSD-2-Clause) plus a dockerless-testable runner that turns dojo challenges into agent episodes with verified trajectories. Everything here is stdlib Python; docker is only required for real episodes.
fleet/pwn-college-dojo/
βββ deploy.md # verified self-host guide: build/run, compose wrapper, flag & instance mechanics
βββ runner.py # jsonl-v1 agent protocol driver + flag verifier + trajectory logging
βββ specs.py # task spec generator for the curated module set (pwn/web/crypto/forensics/rev)
βββ smoke_test.py # dockerless end-to-end test (specs, scaffold, protocol, verifier, trajectories)
βββ scaffold/cyber-factory-dojo/ # private custom dojo: 5 modules, randomized per instance start
jsonl-v1)fleet/README.md did not exist when this was written, so per the fallback convention the protocol is JSON-lines: task spec in, actions out, exec in the container namespace. This is the interactive variant of the fleet agent contract β fleet/protocol.md (which landed while this component was being built) defines the fleet-wide one-shot cyber-factory/agent@1 shape used by vulhub-farm and webapp-asserts; dojo challenges are won inside the container namespace rather than over network endpoints, so the runner keeps the stream open and brokers actions:
runner -> agent: {"type":"task","task_id":...,"spec":{...}}
agent -> runner: {"type":"exec","ref":"1","argv":["/bin/bash","-lc","..."],"timeout":30}
runner -> agent: {"type":"result","ref":"1","rc":0,"stdout":"...","stderr":"...","timed_out":false}
agent -> runner: {"type":"submit","ref":"2","flag":"flag{...}"}
runner -> agent: {"type":"verdict","ref":"2","correct":true,"status":"solved"}
runner -> agent: {"type":"end","reason":"solved","solved":true}
Also: ping/pong, done (agent gives up), error (malformed messages don't kill the episode), and an end reason of max_steps when the step budget is exhausted. Every message and verdict is appended to a trajectory JSONL under data/trajectories/pwn-college-dojo/<date>/<run_id>.jsonl (correct flags are logged; incorrect submissions are not stored verbatim).
Attach an agent either way:
--agent "CMD": the runner spawns the agent subprocess itself(shlex.split, per-run scratch cwd, stderr drained to the trajectory) β the same launch style as the other fleet runners, with the same jsonl-v1 stream over the subprocess pipes. --flag-file is a local-dev shortcut that reads the expected flag from a file (pair with --transport local).
# 1. deploy the platform (see deploy.md for the verified path β build from
# source; there is no public ghcr image), register the scaffold dojo,
# and create an agent user with a started challenge instance.
# 2. emit the curated task specs (8 challenges over 5 categories)
python3 specs.py --check-scaffold --out specs.jsonl
# 3. run an episode (agent speaks jsonl-v1 on stdin)
python3 runner.py --spec specs.jsonl --task-id dojo-pwn-bof-stack \
--transport docker --outer-container dojo --user-id 1001 \
< agent.jsonl
# exit 0 = solved (run summary on stderr), 2 = unsolved
Development without docker: --transport local runs argv on this host (only for protocol debugging β the flag check will fail unless /flag exists locally).
| Category | Challenges (difficulty) |
|---|---|
| pwn | bof-stack (2), fmt-string (4) |
| web | sqli-login (2), path-traversal (3) |
| crypto | classic-chain (2), rsa-smalle (4) |
| forensics | log-hunt (3) |
| rev | crackme (3) |
Win condition everywhere: read the platform-managed per-user /flag (randomized + signed by the dojo; verified mechanism in deploy.md Β§3) and submit it. Challenge content is re-randomized at every instance start by scaffold/cyber-factory-dojo/randomize.py, so episodes don't share bytes.
--verify-mode container (default): read the agent user's /flag via thesame exec transport and compare. We own the deployment, so this is exact.
--verify-mode api: POST {"submission": ...} to the dojo's verifiedsolve endpoint /api/v1/dojos/<dojo>/<module>/<challenge>/solve with a session cookie; statuses solved / already_solved / incorrect / ratelimited map straight onto verdicts.
python3 smoke_test.py
Covers: catalogβscaffold consistency (ids, files, dojo.yml modules), scaffold sources compile, the full protocol round-trip against a fake transport (task β exec β result β submit β verdict β end), wrong-flag rejection, bad-JSON tolerance, step budget enforcement, trajectory event completeness, and the specs.py --check-scaffold CLI. No docker needed.
deploy.md (full self-host + custom dojo guide) Β· wiki:[[fleet/pwn-college-dojo]]
docs/[[fleet/procedural-generator]]