A reusable, self-contained workspace for solving Capture-the-Flag challenges: a sandboxed analysis toolbox, an MCP-driven autonomous solver suite (Claude + Codex), a consistent per-challenge folder structure, a curated reference library, and an archive of solved challenges with their writeups (Markdown only).
Lineage: Grey Cat 2026 Quals (archived) → BoroCTF training run (archived) → SekaiCTF 2026
(concluded 2026-06-29; the event the solver suite was built for and folded into this repo) → Grey Cat
2026 Finals (concluded; wrapped up + archived 2026-07-01) → No Hack No CTF 2026 + R3CTF 2026
(concluded 6 Jul 2026) → Junior.Crypt 2026 (GRODNO::CTF; concluded 2026-07-12, wrapped 2026-07-20).
Each competition's flag format + rules live in its own competitions/<comp>/README.md.
Everything needed to triage, solve, verify, and document a challenge lives in this one repo. The
only things you install are Docker/Colima and the Python virtualenvs — brought up by the
sandbox/ scripts; the sandbox itself is boot-on-ask (it starts the VM on first use).
sandbox/— a Dockerized, amd64 analysis toolbox (ctftools) plus its scripts. Boot-on-ask:sandbox/box <dir>starts Colima on first use and drops you into a fresh container with the challenge mounted at/work;sandbox/reap/haltclean up. Host toolchains (Ghidra, .NET, Foundry, …) come fromsource sandbox/env.sh. Theinstall-*.shset builds the host venvs + per-category tooling.toolkit/— the host-side solver machinery: reference search (refsearch, semantic + lexical), the deterministic triage extractor (static tier prior; re-tiered from the Sweep's real outcomes, ADR-0033), the cost governor, the multi-session launcher, the platform-adapter (intake), plus solver helpers (pow.py,coppersmith_fast.sage,run-timeout), Ghidra headless scripts, and challenge-folder templates.mcp/— the MCP servers: our sandbox-exec server (run_in_sandbox— the only blessed exec path — +verify_flag, the nonce gate that structurally prevents reporting an unverified flag), alongside vendored, audited analysis servers (radare2, slither, headless pyghidra, frida).competitions/<comp>/— one folder per challenge, the live status board, and the solved-challenge archive (each solved challenge carries its full writeup set).reference/— a curated library: per-category resources, accumulated lessons, worked practice solves, an archive of authored challenges, and imported public writeups — searchable from one command.
- Don't attack the competition platform / scoreboard — no exploitation, auth bypass, DoS,
scanning, brute force, or messaging organisers. Intake is fine (reading descriptions +
downloading handouts). Flag submission follows each comp's
flag_submissiontoggle (default: the operator submits). Connecting to a challenge's own service (itsnc host:port, its web app) to solve it is the whole point and is always fine. - Run untrusted challenge code and binaries only inside the sandbox, never on the host. CTF
artifacts are hostile by default — that is exactly what the disposable container is for
(structurally enforced by the
run_in_sandboxMCP tool).
The actual on-disk workspace layout. As of 2026-07-03 the AI operating layer is now published
(agent team, architecture, docs, playbooks); runtime state, large/regenerable trees, and
machine-specific config stay local (gitignored) — flagged [local] below.
.
├── CLAUDE.md · AGENTS.md · ARCHITECTURE.md · CONTEXT.md # operating guides · system design (L0–L9b) · glossary
├── .claude/ # Claude harness — agents/ (15 specialists) · skills/ · hooks/ · rules/ (settings*.json [local])
├── .codex/ # Codex twins — agents/*.toml · hooks/ (config.toml + hooks.json [local])
├── .agents/ # generated skills mirror (for Codex)
├── docs/ # component deep-dives (agents/sweep/governor/triage/launcher/platform/…) + ADRs 0001–0037 + archive/
├── competitions/
│ ├── junior-crypt-2026/ # Junior.Crypt 2026 — concluded, wrapped 2026-07-20 (40 solved · 9 stuck)
│ ├── r3ctf-2026/ # R3CTF 2026 — concluded 6 Jul (1 solved · 12 stuck)
│ ├── nhnc-2026/ # No Hack No CTF 2026 — concluded 6 Jul (27 solved · 5 stuck)
│ ├── greycat-2026-finals/ # concluded — archived 2026-07-01 (19 solved · 13 stuck)
│ ├── sekaictf-2026/ # concluded event — README (toggles), STATUS board, challenges/
│ ├── greycat-2026-qualifiers/ # archived reference event
│ └── boroctf/ # archived training run
│ └── challenges/{<Category>/<slug>/, solved/<Category>/<slug>/}
├── sandbox/ # boot-on-ask amd64 toolbox — box/reap/halt · setup.sh/install-*.sh · Dockerfile[.ml] · env.sh · INSTALLED.md
├── toolkit/ # the solver machinery:
│ ├── triage / governor / launch / solve-wave / sweep # tier prior · cost control · multi-session launcher · autonomous sweep (ADR-0033)
│ ├── stall-guard / auto-steer / compose-prompt / finalize-stale # anti-grind lane-switch + loop hygiene
│ ├── prune-artifacts / audit-challenge # lean-after-solve prune + folder-conformance
│ ├── platform-adapter / coordinator # CTFd/rCTF/manual intake (+ optional poll-and-solve, off)
│ ├── refsearch / rag / gen-status / gen-codex-mirror / lint-* / check-manifest / pow.py / coppersmith_fast.sage / run-timeout
│ └── ghidra_scripts/ · templates/ # headless Ghidra scripts · meta.yml/README/solve.py/writeup scaffolds
├── mcp/
│ ├── sandbox_exec/server.py # our MCP: run_in_sandbox + verify_flag (+ checksec/disasm/…)
│ └── vendor/ [local] # audited third-party servers (radare2 / slither / pyghidra / frida)
├── reference/ # curated library — playbooks/ published; corpus/writeups/wordlists/.rag [local]
├── README.md · .gitignore
│
└── [local] on disk, gitignored, NOT published:
├── .venv/ # unified solver+AI host venv (pwntools/angr/z3/sage/torch/… — rebuilt by sandbox/install-*.sh)
├── tools/ # host-native toolchains (~1.3G: Ghidra/.NET/Foundry/… — reinstalled, not tracked)
├── .models/ · pyghidra-projects/ · .governor/ · .launch/ # local models · Ghidra projects · runtime state & caches
└── .mcp.json · .claude/settings*.json · .codex/{config.toml,hooks.json} · competitions/*/{.env,.token} # machine config + secrets
# 1. One-time: build the toolbox image + host venvs + per-category tooling.
./sandbox/setup.sh # then the install-*.sh lanes as needed (host / sage / ghidra / ai)
source sandbox/env.sh # put host toolchains on PATH (or add to ~/.zshrc)
# 2. Drop into the toolbox with a challenge mounted at /work (boots Colima on first use).
sandbox/box competitions/sekaictf-2026/challenges/<Category>/<slug>
# 3. Inside the container — analyze the binary, debug, exploit:
checksec ./binary
qdb ./binary # debug: native ptrace is blocked under Rosetta -> qdb uses qemu-user's gdbstub
# 4. Search the curated reference library from the host for prior art / techniques.
toolkit/refsearch coppersmith
toolkit/refsearch -c pwn "tcache poisoning"The host is Apple-Silicon macOS (arm64), but CTF challenge binaries are almost always
x86-64 Linux. They run inside an amd64 Colima VM (Docker engine, VZ + Rosetta) so the
emulation is fast. The VM is boot-on-ask — sandbox/box (and the run_in_sandbox MCP tool)
start it on first use; there is no manual bring-up step.
Two Apple-Silicon gotchas, both worked around in-house:
- Rosetta blocks
ptrace→ nativegdbcan't debug an emulated amd64 binary. Useqdbinside the toolbox (qdb ./binary), which debugs via qemu-user's gdbstub. Running/exploiting stays on Rosetta (fast); debugging uses qemu (works, slower). - Rosetta SIGILLs FLINT's large-multiply (AVX) kernel → Sage's
small_roots/ defund crash on big polynomials. Use thecop-run/msolve-wrapqemu wrappers in the sandbox, ortoolkit/coppersmith_fast.sagenatively (which dodges FLINT entirely for univariate Coppersmith).
Surfaces: ctftools (the everyday amd64 toolbox image, built from sandbox/Dockerfile) +
ctftools-ml (a heavy-ML layer, on demand) + host .venv (the unified native-arm64 venv:
pwntools, angr, z3, cvc5, OR-Tools, pycryptodome, sympy, … + the AI suite — torch, fickling, picklescan,
ART, …) + a host SageMath miniforge env. Each sandbox/box call is a fresh docker run --rm
container — nothing persists, so add anything reusable to the Dockerfile and rebuild.
Full tool manifest and teardown: sandbox/INSTALLED.md.
Each challenge lives in challenges/<Category>/<slug>/ (category-first; <Category> = the
organiser's label for this comp):
meta.yml— a rigid, closed 6-field schema (linter-enforced, no prose), in order:name,category(the organiser's per-competition label — not a fixed global list, seeCONTEXT.md),status(new | solving | solved | stuck),tier(T0–T4),flag,solved_by(user | teammate, required only whensolved). No timestamps (ADR-0012).README.md— 4 sections (Challenge · Handout · Analysis · Dead ends);Challenge+Handoutrequired, the rest optional (ADR-0013).handout/— the given files, treated as read-only.solve/— per-attempt foldersattempt-<harness>-NN/(harness ∈claude | codex | gpt), each with afindings.md. One attempt = one solve session by one driver; a fan-out's subagents are internal to that one attempt.- On a verified solve the folder gains
writeup/archive/(the detailed Markdown writeup, always produced) and — per the comp'ssubmission_writeuptoggle —writeup/submission/(the short organiser ticket)..tex/.pdfrender retired (ADR-0003).
Active work stays under challenges/<Category>/<slug>/; on a verified solve the folder is archived
(slug unchanged) under challenges/solved/<Category>/<slug>/, so the active listing always
equals the remaining work. Provenance lives in meta.yml solved_by: + STATUS.md, not the path.
Intake (build the folder from the platform-adapter / templates) → triage (the deterministic
extractor sets a low-biased tier prior) → re-tier (the Sweep's real session/attempt outcomes set the tier, ADR-0033) →
solve (a specialist works an attempt folder; untrusted code runs only in the sandbox; heavy
compute is detached via the governor) → self-verify (the verify_flag nonce gate; the candidate
flag goes in meta.yml while status stays solving, and the operator is pinged) → operator
confirms acceptance (status → solved) → writeups (Archive always, Submission per toggle) →
archive the folder + regenerate STATUS.md.
Commit etiquette: the default is to edit in place and never commit — the tree may be shared by parallel drivers (Claude + Codex, even on the same challenge), isolated by per-attempt folders, with append-only shared files. Commit/push only when the operator explicitly asks.
- No Hack No CTF 2026 (currently live; ICEDTEA, runs 2026-07-04→06). Config:
competitions/nhnc-2026/README.md. - Grey Cat 2026 Finals (concluded; wrapped up + archived 2026-07-01 — 19 solved · 13 stuck). Board:
competitions/greycat-2026-finals/STATUS.md. - SekaiCTF 2026 (concluded). Board:
competitions/sekaictf-2026/STATUS.md. - Grey Cat 2026 Qualifiers (concluded; archived reference event). Board:
competitions/greycat-2026-qualifiers/STATUS.md— 34 solved / 35. - BoroCTF (concluded; archived training run). Board:
competitions/boroctf/STATUS.md.
Verified solves under challenges/solved/<Category>/<slug>/ carry the Markdown Archive Writeup
(writeup/archive/, always) and — per submission_writeup — the Submission Writeup
(writeup/submission/); meta.yml solved_by: records who landed each.
A curated, searchable library of prior art:
reference/README.md (the category-first index) ·
reference/playbooks/ (per-discipline cheat sheets) ·
reference/resources.md · reference/lessons.md.
toolkit/refsearch <query...> # search all of reference/ (semantic, lexical fallback)
toolkit/refsearch -c crypto <query...> # narrow to one categoryPrerequisites: macOS on Apple Silicon (arm64) · Colima + Docker CLI (the amd64 VM, VZ + Rosetta)
· a host python3 for the venvs.
Bring-up: ./sandbox/setup.sh (idempotent — builds the ctftools image + host .venv), then the
sandbox/install-*.sh lanes (host solvers, SageMath, Ghidra, AI suite, the P2.5 per-category
tooling) as needed. Re-run setup.sh to rebuild after editing the Dockerfile. Full teardown:
sandbox/INSTALLED.md.
Regenerated, not committed (gitignored): the host .venv/ (unified — solver stack + AI suite), large host-native tools
under tools/ (e.g. Ghidra), the reference/ corpus + RAG index, and oversized challenge handouts
(memory dumps / multi-GB images, ignored case-by-case and noted in each challenge's README.md).