Self-hosted pwn.college dojo as the "real exploitation" wing of the environment fleet: full Linux challenge containers, platform-managed per-user flags, and an agent protocol driver that turns each challenge into a verified-trajectory episode. Code: fleet/pwn-college-dojo/ (deploy.md, runner.py, specs.py, smoke_test.py, scaffold/cyber-factory-dojo/).
The factory's moat is fleet + verifiers + refresh loop (see [[index]]). Procedural puzzles (see [[fleet/procedural-generator]]) train cheap, renewable behavior; the dojo adds depth — actual setuid binaries, actual network services, actual forensics artifacts inside a container the agent fully controls. Both feed the same trajectory store under data/trajectories/.
Everything below was checked against pwncollege/dojo@master — two corrections to folklore along the way: the license is BSD-2-Clause, not MIT, and there is no public ghcr.io/pwncollege/dojo image (registry probe denied; upstream builds from source: docker build -t pwncollege/dojo inside a --privileged container that fronts ports 22/80/443).
/flag (root-only) into every instance with student-specific content; challenges carry no authored flag values (Flags(type="dojo")). The solve endpoint POST /api/v1/dojos/<dojo>/<module>/<challenge_id>/solve validates per-user signed flags — stolen flags fail with "This flag is not yours!" — and rate-limits wrong submissions (10/min).
dojo.user_id/dojo.dojo_id/dojo.module_id/dojo.challenge_id; SSH auths against the site DB and execs into the container; workspace APIs proxy services; admins dojo enter <uid>.
dojo.yml (+ module/module.yml withinline challenge ids, DESCRIPTION.md per challenge), one Dockerfile building the dojo image; registered via /dojos/create (repository + deploy keys, 1 creation/day).
scaffold/cyber-factory-dojo/): five modules(pwn/web/crypto/forensics/rev), eight challenges, each won by reading the per-user /flag through a setuid-root helper or a real exploit. A randomize.py runs at every instance start (image CMD) and regenerates all challenge data — RSA primes, cipher chains, log campaigns, web creds, webroots, even a freshly compiled crackme — from OS entropy. The platform randomizes flags; we randomize everything else. No two episodes share bytes.
runner.py, stdlib): the jsonl-v1 driver — task spec in,actions out, exec in the container namespace (docker exec dojo docker exec <user-container> …), flag verification against the instance /flag or the solve API, and JSONL trajectory logging. Agents attach via stdin pipes or --agent CMD (subprocess launch in the fleet's standard style, see fleet/protocol.md — this is the interactive variant of cyber-factory/agent@1, kept open because dojo wins happen inside the container namespace). Dockerless smoke test passes the full protocol round-trip plus an end-to-end --agent episode.
specs.py): curated catalog over the scaffold with adifficulty dial, validated against the scaffold so catalog and containers can't drift.
deploy.md)per category (ticket in queue/tickets/)
trajectories = fleet ∧ teacher[[fleet/procedural-generator]] · [[index]] · competitors/landscape-2026 · upstream: github.com/pwncollege/dojo · pwn.college