pwn-college dojo β€” self-hosted training environments

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

The agent protocol (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:

(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).

Usage


# 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).

Curated module set (specs.py catalog == scaffold)

CategoryChallenges (difficulty)
pwnbof-stack (2), fmt-string (4)
websqli-login (2), path-traversal (3)
cryptoclassic-chain (2), rsa-smalle (4)
forensicslog-hunt (3)
revcrackme (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.

Flag verification modes

same exec transport and compare. We own the deployment, so this is exact.

solve endpoint /api/v1/dojos/<dojo>/<module>/<challenge>/solve with a session cookie; statuses solved / already_solved / incorrect / ratelimited map straight onto verdicts.

Smoke test


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.

See also

[[fleet/pwn-college-dojo]]