ctf-flightdeck 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ctf_flightdeck-0.1.0/LICENSE +21 -0
- ctf_flightdeck-0.1.0/PKG-INFO +307 -0
- ctf_flightdeck-0.1.0/README.md +275 -0
- ctf_flightdeck-0.1.0/pyproject.toml +63 -0
- ctf_flightdeck-0.1.0/pyproject.toml.orig +59 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/__init__.py +8 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/__main__.py +3 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/agent/__init__.py +1 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/agent/anthropic_client.py +631 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/agent/client.py +578 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/agent/driver.py +876 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/agent/external.py +2319 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/agent/fallback.py +207 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/agent/fastmode.py +105 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/agent/l1stage.py +107 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/agent/prompts.py +180 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/agent/tools.py +724 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/agent/workspace.py +1207 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/apicheck.py +410 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/build.py +855 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/build_cli.py +154 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/cli.py +741 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/clock.py +16 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/config.py +896 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/console/__init__.py +2 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/console/app.py +604 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/console/commands.py +128 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/console/fleet.py +539 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/console/focus.py +243 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/console/room.py +85 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/console/screens.py +369 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/console/takeover.py +54 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/console/terminal.py +65 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/console/theme.py +64 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/console/views.py +876 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/console/widgets.py +184 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/core/__init__.py +3 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/core/attention.py +95 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/core/deck.py +1620 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/core/driver.py +128 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/core/interventions.py +131 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/core/model.py +370 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/core/summary.py +364 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/credentials.py +217 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/detect/__init__.py +61 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/detect/base.py +137 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/detect/detectors.py +545 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/detect/hints.py +351 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/detect/hints.toml +334 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/detect/judge.py +184 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/detect/watch.py +256 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/eventlog.py +174 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/flagformat.py +101 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/fmt.py +40 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/gaps.py +582 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/gaps_cli.py +128 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/hardware.py +477 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/keys.py +102 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/l1/__init__.py +17 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/l1/_boot.py +128 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/l1/_capture.py +125 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/l1/cli.py +297 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/l1/container.py +202 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/l1/importer.py +349 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/l1/manifest.py +509 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/l1/result.py +259 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/l1/router.py +243 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/l1/runner.py +705 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/lane/Dockerfile +15 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/lane/__init__.py +28 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/leftovers.py +1047 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/light.py +1041 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/localmodel.py +575 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/localmodel_cases.jsonl +58 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/localmodel_cli.py +521 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/mock/__init__.py +1 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/mock/scenarios.py +1054 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/mock/screenshots.py +229 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/mock/script.py +242 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/mock/world.py +97 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/model_catalog.py +640 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/models.toml +229 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/models_cli.py +485 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/needle.py +156 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/platform/__init__.py +9 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/platform/ctfd.py +1808 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/platform/ctfd_cli.py +114 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/platform/manual.py +545 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/postflight.py +1515 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/postflight_cli.py +97 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/practice/README.md +86 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/practice/answers.sha256.json +16 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/practice/board.json +4 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/practice/crab-walk/challenge.json +11 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/practice/crab-walk/postcard.txt +8 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/practice/fine-print/challenge.json +11 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/practice/fine-print/harbour_dawn.png +0 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/practice/half-and-half/challenge.json +11 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/practice/half-and-half/halves.txt +6 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/practice/harbour-memo/challenge.json +11 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/practice/harbour-memo/memo.enc +3 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/practice/mille-feuille/challenge.json +11 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/practice/mille-feuille/layers.txt +1 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/practice/nesting-dolls/challenge.json +11 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/practice/nesting-dolls/dolls.zip +0 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/practice/pancake-stack/challenge.json +12 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/practice/pancake-stack/program.bin +0 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/practice/pancake-stack/vm.py +85 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/practice/postmarked/challenge.json +12 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/practice/postmarked/envelope.txt +7 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/practice/postmarked/letter.txt +9 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/practice/quiet-night/access_log +497 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/practice/quiet-night/challenge.json +11 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/practice/signal-lamp/challenge.json +11 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/practice/signal-lamp/lamp_log.csv +99 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/practice/three-wishes/challenge.json +12 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/practice/three-wishes/encrypt.py +44 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/practice/three-wishes/output.txt +3 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/practice/turnstile/challenge.json +11 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/practice/turnstile/turnstile.py +35 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/practice/welcome-mat/challenge.json +13 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/practice/welcome-mat/index.html +22 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/practice/welcome-mat/portal.js +42 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/practice/welcome-mat/style.css +5 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/practice/wrong-jar/challenge.json +11 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/practice/wrong-jar/minutes.pdf +0 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/practice_cli.py +115 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/practice_mode.py +1119 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/practice_set.py +16 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/preflight.py +853 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/preflight_cli.py +131 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/pricing.py +42 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/run.py +1296 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/runrecord.py +514 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/setup.py +868 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/team.py +892 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/team_cli.py +69 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/text.py +71 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/transcript.py +104 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/writeup.py +1136 -0
- ctf_flightdeck-0.1.0/src/ctf_flightdeck/writeup_cli.py +256 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 The ctf-flightdeck contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ctf-flightdeck
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A terminal harness for playing CTFs with AI agents, where the player runs the loop.
|
|
5
|
+
Keywords: ctf,capture-the-flag,security,ai-agents,llm,tui,terminal
|
|
6
|
+
Author: Maximus Fisher
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
|
+
Classifier: Environment :: Console
|
|
11
|
+
Classifier: Intended Audience :: Information Technology
|
|
12
|
+
Classifier: Operating System :: MacOS
|
|
13
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Topic :: Security
|
|
18
|
+
Requires-Dist: anthropic>=2.8.0,<3
|
|
19
|
+
Requires-Dist: httpx>=0.28
|
|
20
|
+
Requires-Dist: textual>=8.2.8
|
|
21
|
+
Requires-Dist: typer>=0.29.0
|
|
22
|
+
Requires-Dist: pytest>=9.2.0 ; extra == 'build'
|
|
23
|
+
Requires-Dist: cactus-compute==4.1.0 ; extra == 'cactus'
|
|
24
|
+
Requires-Python: >=3.12
|
|
25
|
+
Project-URL: Homepage, https://github.com/MFisher14/ctf-flightdeck
|
|
26
|
+
Project-URL: Documentation, https://github.com/MFisher14/ctf-flightdeck#readme
|
|
27
|
+
Project-URL: Issues, https://github.com/MFisher14/ctf-flightdeck/issues
|
|
28
|
+
Project-URL: Changelog, https://github.com/MFisher14/ctf-flightdeck/blob/main/CHANGELOG.md
|
|
29
|
+
Provides-Extra: build
|
|
30
|
+
Provides-Extra: cactus
|
|
31
|
+
Description-Content-Type: text/markdown
|
|
32
|
+
|
|
33
|
+
# ctf-flightdeck
|
|
34
|
+
|
|
35
|
+
A terminal harness for playing CTFs with AI agents, where **you** run the loop.
|
|
36
|
+
|
|
37
|
+
flightdeck pulls the board, runs your own no-model solvers first, starts agent lanes in parallel,
|
|
38
|
+
and submits flags, while you watch every lane on one console and step in when one goes the wrong
|
|
39
|
+
way: a hint, a redirect, a fork, a kill, or taking over its shell yourself.
|
|
40
|
+
|
|
41
|
+

|
|
42
|
+
|
|
43
|
+
*The fleet view, from `flightdeck mock`: scripted lanes on invented challenges. More in the
|
|
44
|
+
[screenshot gallery](docs/SCREENSHOTS.md).*
|
|
45
|
+
|
|
46
|
+
Bring what you have: an API key (Anthropic, OpenRouter, OpenAI or a local model server), a
|
|
47
|
+
coding-agent CLI (Claude Code, Codex), a folder of solver scripts.
|
|
48
|
+
|
|
49
|
+
> **Status: v0.1.0, the first public release (alpha).** The console, agent lanes, your L1
|
|
50
|
+
> solvers, CTFd events, team mode, alerts, practice scoring and post-flight reports are built.
|
|
51
|
+
> Next: a model per small job (0.2.0), then Autopilot. See [`docs/ROADMAP.md`](docs/ROADMAP.md)
|
|
52
|
+
> and [`CHANGELOG.md`](CHANGELOG.md).
|
|
53
|
+
|
|
54
|
+
## Quickstart
|
|
55
|
+
|
|
56
|
+
Needs Python 3.12+ on macOS or Linux (Windows through WSL), and Docker for the safest setup.
|
|
57
|
+
|
|
58
|
+
```sh
|
|
59
|
+
pipx install ctf-flightdeck # or: uv tool install ctf-flightdeck (from a clone: uv tool install .)
|
|
60
|
+
flightdeck init # a few questions; writes your config and says what to do next
|
|
61
|
+
flightdeck practice # 14 small practice challenges that ship with flightdeck
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
`flightdeck` on its own says what to do next on your machine, and `flightdeck doctor` checks
|
|
65
|
+
everything (Python, the config, the key variables, a local model server and its models, the
|
|
66
|
+
light model, Docker, the lane image, your terminal's size) and says how to fix each problem.
|
|
67
|
+
Before the console opens, `play` and `practice` ask the model API whether it takes your key and
|
|
68
|
+
serves every model in your ladder, and stop with what to fix if not. No setup at all:
|
|
69
|
+
`flightdeck mock` opens the console on scripted data and spends nothing, and
|
|
70
|
+
`uvx ctf-flightdeck mock` does it without installing anything.
|
|
71
|
+
|
|
72
|
+
## Keys and model APIs
|
|
73
|
+
|
|
74
|
+
Your config names the environment variable that holds each key, never the key. flightdeck reads
|
|
75
|
+
the variable from your environment, else from `~/.config/ctf-flightdeck/keys.env` (or the file
|
|
76
|
+
`$FLIGHTDECK_KEYS` names), which must be readable by you only:
|
|
77
|
+
|
|
78
|
+
```sh
|
|
79
|
+
mkdir -p ~/.config/ctf-flightdeck && touch ~/.config/ctf-flightdeck/keys.env && chmod 600 ~/.config/ctf-flightdeck/keys.env
|
|
80
|
+
# then add lines like: ANTHROPIC_API_KEY=... OPENROUTER_API_KEY=...
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
A lane's commands get an environment built from an allowlist, so flightdeck hands them none of
|
|
84
|
+
these values (but see `--host` below). A coding-agent CLI (`--agent`) gets its own credential and
|
|
85
|
+
no other, and the commands it runs can read that one, in Docker too: give it a key you can
|
|
86
|
+
revoke, with a low spending limit.
|
|
87
|
+
|
|
88
|
+
With an Anthropic key, `flightdeck init --provider anthropic` writes Claude Haiku, Sonnet and
|
|
89
|
+
Opus 5.5 with their list prices, so the dollar caps work from the start. If `OPENROUTER_API_KEY`
|
|
90
|
+
is set too, it offers OpenRouter as the fallback for a lane whose calls to Anthropic keep
|
|
91
|
+
failing. OpenRouter, OpenAI, Ollama, an MLX server or any OpenAI-compatible API work as well.
|
|
92
|
+
For Ollama or an MLX server, `init` suggests a model, and for any server on your machine it lets
|
|
93
|
+
a lane wait up to 10 minutes for a reply of up to 8192 tokens, since a local model can take
|
|
94
|
+
minutes to answer. The fallback, what happens when a model declines, fast mode, time and reply
|
|
95
|
+
limits, and prices: [`docs/MODEL-APIS.md`](docs/MODEL-APIS.md).
|
|
96
|
+
|
|
97
|
+
## Models
|
|
98
|
+
|
|
99
|
+
Which models fit your machine? `flightdeck models` looks at it (chip, memory, NVIDIA GPUs, and
|
|
100
|
+
the model servers running on it; it contacts nothing but 127.0.0.1) and lists, for lanes and for
|
|
101
|
+
the light model, the models that fit and why: open-weight models on your machine first where
|
|
102
|
+
they run well, then model APIs. `flightdeck models qwen3.5-9b` prints how to get one and the
|
|
103
|
+
config lines that use it. It recommends; it never changes your config. How the models did:
|
|
104
|
+
[`docs/MODELS.md`](docs/MODELS.md).
|
|
105
|
+
|
|
106
|
+
## Play a board
|
|
107
|
+
|
|
108
|
+
```sh
|
|
109
|
+
flightdeck play ~/ctf/some-board --max-lanes 4
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
A board is a folder of challenge folders, each with a `challenge.json` and its handout files
|
|
113
|
+
(format in [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md)). Lanes start cheapest-first; the rest
|
|
114
|
+
queue. Every lane is a model conversation, capped per lane by `token_cap` and `dollar_cap` (an
|
|
115
|
+
alert at 80%, your approval at 100%).
|
|
116
|
+
|
|
117
|
+
A flag a lane finds waits for you: `a` or `1` submits it, `d` or `0` declines. Under auto submit
|
|
118
|
+
(`submit = "auto"`, or `play --submit auto`) a flag the model submits goes at once, unless it is
|
|
119
|
+
written in the challenge's own description or handout files (often a decoy): that one still
|
|
120
|
+
waits for you. A flag found wrong is never asked about again.
|
|
121
|
+
|
|
122
|
+
The console raises an alert when a lane spends big on a low-value challenge, ignores what the
|
|
123
|
+
description hints at, heads the wrong way on web, loops or stalls. Lanes that need you sort
|
|
124
|
+
first, and the line under the list says why and numbers the answers: one key answers.
|
|
125
|
+
|
|
126
|
+

|
|
127
|
+
|
|
128
|
+
*An alert with its quick answers, from `flightdeck mock` (invented challenges).*
|
|
129
|
+
|
|
130
|
+
**Enter** opens a lane full screen: its transcript, the files it changed with diffs, and a chat
|
|
131
|
+
box (plain text is a message to the lane; `/redirect`, `/approve`, `/diff` and the rest are
|
|
132
|
+
commands; `esc` goes back).
|
|
133
|
+
|
|
134
|
+

|
|
135
|
+
|
|
136
|
+
*A lane full screen, from `flightdeck mock` (invented challenges).*
|
|
137
|
+
|
|
138
|
+
`t` takes over a lane's shell (its container in Docker): exit it to hand the lane back, and the
|
|
139
|
+
lane hears what you changed. The shell starts clean: it reads no startup file, and with `--host`
|
|
140
|
+
its HOME is a new folder only you can read, outside the workspace, so nothing the lane wrote runs
|
|
141
|
+
before you type. `e` escalates a lane to a stronger model with a summary of what it did, `f`
|
|
142
|
+
forks it, `?` shows every key.
|
|
143
|
+
|
|
144
|
+
When a model API failure would hit every lane (a refused key, an unknown model, no credits), the
|
|
145
|
+
queue is held and the header says why. Once you have fixed it, `/unhold` starts the queued lanes
|
|
146
|
+
(a key or model id changed in your config needs a new run).
|
|
147
|
+
|
|
148
|
+
Docker lanes get no network unless their challenge has a live instance (a web origin, or a TCP
|
|
149
|
+
service like `nc host port`: `tcp://host:port` in a challenge's `urls`). `--host` runs lane
|
|
150
|
+
commands as you, on this machine: a lane can read your files, your keys file and flightdeck's
|
|
151
|
+
own environment, and send what it reads to its model's API, which nothing can take back. The
|
|
152
|
+
run log, the console, transcripts, reports and writeup drafts show a key's name but not its
|
|
153
|
+
value (see [After a run](#after-a-run)), but that is cleanup, not a fence: use Docker, a
|
|
154
|
+
disposable VM, or a separate low-limit key.
|
|
155
|
+
|
|
156
|
+
Every run writes an append-only `events.jsonl` and each lane's transcript; a board with answers
|
|
157
|
+
(like the practice set) ends with a scorecard. `q` asks first while lanes are open, and a run
|
|
158
|
+
cannot be resumed. If flightdeck itself is killed (kill -9, a power loss), what its lanes started
|
|
159
|
+
can keep running: the next `play` or `practice` removes it and says what it removed,
|
|
160
|
+
`flightdeck doctor` lists it and `flightdeck doctor --clean` removes it.
|
|
161
|
+
|
|
162
|
+
## Play an event
|
|
163
|
+
|
|
164
|
+
Write down what the event's rules allow, check it, and play by it:
|
|
165
|
+
|
|
166
|
+
```sh
|
|
167
|
+
flightdeck preflight --example > event.toml # then edit it from the event's rules
|
|
168
|
+
flightdeck preflight event.toml # what flightdeck will do at each stage, and any problem
|
|
169
|
+
export CTFD_TOKEN=... # the variable event.toml names (never the token itself)
|
|
170
|
+
flightdeck play ~/ctf/event-board --event event.toml
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
With a CTFd event, flightdeck pulls the board into that folder, adds new waves as they appear,
|
|
174
|
+
submits the flags you approve through CTFd and follows your team's solves. A slow CTFd never
|
|
175
|
+
holds up the console: lanes carry on while their flags are on their way (the fleet shows
|
|
176
|
+
`sending`). A challenge whose tries CTFd counts always waits for you, under `auto` too, and each
|
|
177
|
+
approval says how many tries are left. Each stage (pull, L1, agents, submit, live instance) runs
|
|
178
|
+
as the profile's policy says: `auto`, `confirm` (you say yes first), `manual` or `off`, never
|
|
179
|
+
looser than the rules. Under `confirm` for live instances, a lane asks before it may reach its
|
|
180
|
+
challenge's instance and has no network until you allow it.
|
|
181
|
+
|
|
182
|
+
Team mode shares claims, live-box holds and solves through a folder your teammates sync (a network
|
|
183
|
+
drive, Syncthing): `flightdeck team init DIR` once, then `--team-dir DIR`. Your lanes never work a
|
|
184
|
+
challenge a teammate solved.
|
|
185
|
+
|
|
186
|
+
`--agent claude` (or `codex`) runs lanes as that coding-agent CLI, with its own login and models
|
|
187
|
+
(`[agents]` in your config), behind the same controls. Check it first with
|
|
188
|
+
`flightdeck doctor --agent claude`. A CLI says what a turn cost only when the turn ends, so its
|
|
189
|
+
lane's cost ends in `…` until then; codex reports no cost at all, so give its models a
|
|
190
|
+
`[prices."<model>"]` table for their lanes to show dollars.
|
|
191
|
+
|
|
192
|
+
## Your solvers first (L1)
|
|
193
|
+
|
|
194
|
+
Point flightdeck at a folder of your no-model solvers and each of flightdeck's own lanes runs the
|
|
195
|
+
ones that fit its challenge before its first model call (coding-agent lanes do not use the pack
|
|
196
|
+
yet), so a solve can cost no tokens at all. A flag an engine returns as its result waits for your
|
|
197
|
+
approval, or under auto submit goes to the platform at once; a flag that is only a match in what
|
|
198
|
+
an engine printed (a plain script that `l1 import` took in, say) always waits for you. When lanes
|
|
199
|
+
run in Docker, so do your solvers: each in a container of the lane image, your pack mounted
|
|
200
|
+
read-only, with no network unless the live-instance policy is `auto` and the challenge has an
|
|
201
|
+
instance. With `--host` they run on this machine, as you.
|
|
202
|
+
|
|
203
|
+
```sh
|
|
204
|
+
flightdeck l1 import ~/ctf/solvers # manifests for a folder of plain scripts (the scripts are never changed)
|
|
205
|
+
flightdeck l1 route ~/ctf/solvers ~/ctf/some-board/crab-walk # which engines a challenge reaches, and why (or why not)
|
|
206
|
+
flightdeck play ~/ctf/some-board --pack ~/ctf/solvers # or set [l1] pack in your config
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
## After a run
|
|
210
|
+
|
|
211
|
+
```sh
|
|
212
|
+
flightdeck report RUN_DIR # where lanes stalled, which interventions helped, cost per solve
|
|
213
|
+
flightdeck writeups RUN_DIR # a writeup draft per solved challenge (--redact hides the flags)
|
|
214
|
+
flightdeck score RUN_DIR # the run scored against the board's answers
|
|
215
|
+
flightdeck gaps RUN_DIR # the challenges your L1 solvers did not solve, and why
|
|
216
|
+
flightdeck build gap CHALLENGE_DIR --pack ~/ctf/solvers # a new engine stub with a test that must pass
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
When a run ends, `play` prints its run folder with the report, writeups and gaps commands for it
|
|
220
|
+
(and score, when the board has answers), ready to paste. Writeup drafts and lane transcripts
|
|
221
|
+
take out what looks like a key or token, with or without `--redact`: the value of an upper-case
|
|
222
|
+
`NAME=value` or `NAME: value` whose name holds KEY, TOKEN, SECRET, PASSWORD, PASSWD, COOKIE or
|
|
223
|
+
AUTH, of an Authorization or Cookie header, and of a key with a well-known prefix (`sk-ant-`,
|
|
224
|
+
`ghp_` and the like) becomes `[redacted]`. Anything else stays, lower-case names included (a
|
|
225
|
+
`password` is everyday CTF content), so read a draft before you share it.
|
|
226
|
+
|
|
227
|
+
## Light model
|
|
228
|
+
|
|
229
|
+
A few small jobs can use a small model. Today that is the hint judge: when a lane seems to ignore
|
|
230
|
+
what its description hints at, the alert shows at once, and the light model checks the lane's
|
|
231
|
+
steps; if it says the lane is using the technique after all, the alert comes down. By default
|
|
232
|
+
there is no light model, only the rules. Five choices, in `[light]`:
|
|
233
|
+
|
|
234
|
+
- `rules`: no model (the default).
|
|
235
|
+
- `openai`: any OpenAI-compatible server. **Recommended: an open-weight model on your own
|
|
236
|
+
machine**, served by Ollama (`http://127.0.0.1:11434/v1`) or an MLX server
|
|
237
|
+
(`http://127.0.0.1:8080/v1`), with `key_env = ""`. No provider's safety classifier stands
|
|
238
|
+
between it and security work, and it costs nothing. flightdeck asks a model on your machine to
|
|
239
|
+
answer without thinking first (`thinking = false`, the default there): one that reasons first
|
|
240
|
+
spends a small job's few hundred tokens thinking and answers nothing.
|
|
241
|
+
- `anthropic`: a Claude model (`claude-haiku-5-5`, say).
|
|
242
|
+
- `cactus`: Cactus Needle, on this machine (the `[cactus]` extra).
|
|
243
|
+
- `decider`: Strands Decider, a decision model, served on your own machine. It writes no text: it
|
|
244
|
+
picks one of the options and says how confident it is. An answer below `threshold` (0.9 by
|
|
245
|
+
default; its README says answers at 0.9 or more are right about 95% of the time on short tasks
|
|
246
|
+
it has never seen) is unsure, and the rules' answer stands. No key, and nothing to pay.
|
|
247
|
+
`flightdeck init --light decider` writes it and says how to start its server:
|
|
248
|
+
`pip install strands-decider`, then
|
|
249
|
+
`strands-decider serve StrandsAgents/strands-decider-2B-qwen3.5-v1-2610 --port 8000` (on Apple
|
|
250
|
+
silicon add `--device mlx`, with its `mlx` extra: `pip install "strands-decider[mlx]>=0.3"`).
|
|
251
|
+
|
|
252
|
+
Measure one before you switch it on: which models to try, and how they did, is in
|
|
253
|
+
[`docs/MODELS.md`](docs/MODELS.md). `eval-local` decides:
|
|
254
|
+
|
|
255
|
+
```sh
|
|
256
|
+
flightdeck eval-local --engine openai --base-url http://127.0.0.1:11434/v1 --model <model> \
|
|
257
|
+
--compare anthropic:claude-haiku-5-5 --compare cactus
|
|
258
|
+
flightdeck eval-local --engine decider --base-url http://127.0.0.1:8000 # or a column: --compare decider@URL
|
|
259
|
+
flightdeck init --light ollama --light-model <model> # or edit [light]; at a terminal, init suggests a model
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
eval-local runs each engine and the rules over the bundled cases (naming the hinted technique,
|
|
263
|
+
judging a lane, summarising one) and prints them side by side: accuracy, refusals and failures,
|
|
264
|
+
time and cost per job. A paid API is announced first with its number of calls: one per case,
|
|
265
|
+
never a retry. `--thinking on|off` says whether a model on an OpenAI-compatible server may think
|
|
266
|
+
first; by default it is off on your machine and on elsewhere, unless your `[light] thinking`
|
|
267
|
+
says otherwise for that server. The decider's unsure answers get a row of their own and count
|
|
268
|
+
neither right nor wrong, so its accuracy says how often an answer it was sure of is right; its
|
|
269
|
+
summary cases show as n/a, since a decision model writes no text.
|
|
270
|
+
|
|
271
|
+
The light model never holds anything up. When it refuses, fails, times out, answers outside the
|
|
272
|
+
options or (the decider) is unsure, the rules' answer stands, and the run says so once. A refused
|
|
273
|
+
request is never sent on to another model or provider: you see the refusal and decide.
|
|
274
|
+
|
|
275
|
+
## Try the mock
|
|
276
|
+
|
|
277
|
+
```sh
|
|
278
|
+
flightdeck mock # the default scenario: every alert, then a 43-challenge wave
|
|
279
|
+
flightdeck mock --scenario wave # also: sstv, overthinking, web, team, flag
|
|
280
|
+
flightdeck mock --screenshot DIR # headless: SVGs of the key moments
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
The fleet's keys act on the selected lane: `p` pause, `h` hint, `r` redirect, `f` fork, `k` kill,
|
|
284
|
+
`e` escalate, `t` take over, `a` approve, `d` decline, `1`-`9`/`0` quick answers, `c` claim
|
|
285
|
+
(team), `/` commands, `s` sort, `?` help, `q` quit; **Enter** opens the lane full screen. In the
|
|
286
|
+
mock, `space` freezes the clock and `.` steps it. All challenges, teammates and flags there are
|
|
287
|
+
invented.
|
|
288
|
+
|
|
289
|
+
## Optional extras
|
|
290
|
+
|
|
291
|
+
- `ctf-flightdeck[cactus]`: Cactus Needle, a small on-device model, one of the light model's
|
|
292
|
+
choices (`flightdeck eval-local --engine cactus` measures it against the rules first). Its
|
|
293
|
+
telemetry is turned off. Nothing uses it unless you ask.
|
|
294
|
+
- `ctf-flightdeck[build]`: pytest, for `flightdeck build check`.
|
|
295
|
+
|
|
296
|
+
## Docs
|
|
297
|
+
|
|
298
|
+
[Vision](docs/VISION.md) · [Decisions](docs/DECISIONS.md) · [Roadmap](docs/ROADMAP.md) ·
|
|
299
|
+
[Console spec](docs/CONSOLE-SPEC.md) · [Architecture](docs/ARCHITECTURE.md) ·
|
|
300
|
+
[Model APIs](docs/MODEL-APIS.md) · [Models](docs/MODELS.md) ·
|
|
301
|
+
[Screenshots](docs/SCREENSHOTS.md) · [Changelog](CHANGELOG.md)
|
|
302
|
+
|
|
303
|
+
Development: `uv run pytest`. `ctfdeck` is an alias for `flightdeck`.
|
|
304
|
+
|
|
305
|
+
## License
|
|
306
|
+
|
|
307
|
+
MIT. See [LICENSE](LICENSE).
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
# ctf-flightdeck
|
|
2
|
+
|
|
3
|
+
A terminal harness for playing CTFs with AI agents, where **you** run the loop.
|
|
4
|
+
|
|
5
|
+
flightdeck pulls the board, runs your own no-model solvers first, starts agent lanes in parallel,
|
|
6
|
+
and submits flags, while you watch every lane on one console and step in when one goes the wrong
|
|
7
|
+
way: a hint, a redirect, a fork, a kill, or taking over its shell yourself.
|
|
8
|
+
|
|
9
|
+

|
|
10
|
+
|
|
11
|
+
*The fleet view, from `flightdeck mock`: scripted lanes on invented challenges. More in the
|
|
12
|
+
[screenshot gallery](docs/SCREENSHOTS.md).*
|
|
13
|
+
|
|
14
|
+
Bring what you have: an API key (Anthropic, OpenRouter, OpenAI or a local model server), a
|
|
15
|
+
coding-agent CLI (Claude Code, Codex), a folder of solver scripts.
|
|
16
|
+
|
|
17
|
+
> **Status: v0.1.0, the first public release (alpha).** The console, agent lanes, your L1
|
|
18
|
+
> solvers, CTFd events, team mode, alerts, practice scoring and post-flight reports are built.
|
|
19
|
+
> Next: a model per small job (0.2.0), then Autopilot. See [`docs/ROADMAP.md`](docs/ROADMAP.md)
|
|
20
|
+
> and [`CHANGELOG.md`](CHANGELOG.md).
|
|
21
|
+
|
|
22
|
+
## Quickstart
|
|
23
|
+
|
|
24
|
+
Needs Python 3.12+ on macOS or Linux (Windows through WSL), and Docker for the safest setup.
|
|
25
|
+
|
|
26
|
+
```sh
|
|
27
|
+
pipx install ctf-flightdeck # or: uv tool install ctf-flightdeck (from a clone: uv tool install .)
|
|
28
|
+
flightdeck init # a few questions; writes your config and says what to do next
|
|
29
|
+
flightdeck practice # 14 small practice challenges that ship with flightdeck
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
`flightdeck` on its own says what to do next on your machine, and `flightdeck doctor` checks
|
|
33
|
+
everything (Python, the config, the key variables, a local model server and its models, the
|
|
34
|
+
light model, Docker, the lane image, your terminal's size) and says how to fix each problem.
|
|
35
|
+
Before the console opens, `play` and `practice` ask the model API whether it takes your key and
|
|
36
|
+
serves every model in your ladder, and stop with what to fix if not. No setup at all:
|
|
37
|
+
`flightdeck mock` opens the console on scripted data and spends nothing, and
|
|
38
|
+
`uvx ctf-flightdeck mock` does it without installing anything.
|
|
39
|
+
|
|
40
|
+
## Keys and model APIs
|
|
41
|
+
|
|
42
|
+
Your config names the environment variable that holds each key, never the key. flightdeck reads
|
|
43
|
+
the variable from your environment, else from `~/.config/ctf-flightdeck/keys.env` (or the file
|
|
44
|
+
`$FLIGHTDECK_KEYS` names), which must be readable by you only:
|
|
45
|
+
|
|
46
|
+
```sh
|
|
47
|
+
mkdir -p ~/.config/ctf-flightdeck && touch ~/.config/ctf-flightdeck/keys.env && chmod 600 ~/.config/ctf-flightdeck/keys.env
|
|
48
|
+
# then add lines like: ANTHROPIC_API_KEY=... OPENROUTER_API_KEY=...
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
A lane's commands get an environment built from an allowlist, so flightdeck hands them none of
|
|
52
|
+
these values (but see `--host` below). A coding-agent CLI (`--agent`) gets its own credential and
|
|
53
|
+
no other, and the commands it runs can read that one, in Docker too: give it a key you can
|
|
54
|
+
revoke, with a low spending limit.
|
|
55
|
+
|
|
56
|
+
With an Anthropic key, `flightdeck init --provider anthropic` writes Claude Haiku, Sonnet and
|
|
57
|
+
Opus 5.5 with their list prices, so the dollar caps work from the start. If `OPENROUTER_API_KEY`
|
|
58
|
+
is set too, it offers OpenRouter as the fallback for a lane whose calls to Anthropic keep
|
|
59
|
+
failing. OpenRouter, OpenAI, Ollama, an MLX server or any OpenAI-compatible API work as well.
|
|
60
|
+
For Ollama or an MLX server, `init` suggests a model, and for any server on your machine it lets
|
|
61
|
+
a lane wait up to 10 minutes for a reply of up to 8192 tokens, since a local model can take
|
|
62
|
+
minutes to answer. The fallback, what happens when a model declines, fast mode, time and reply
|
|
63
|
+
limits, and prices: [`docs/MODEL-APIS.md`](docs/MODEL-APIS.md).
|
|
64
|
+
|
|
65
|
+
## Models
|
|
66
|
+
|
|
67
|
+
Which models fit your machine? `flightdeck models` looks at it (chip, memory, NVIDIA GPUs, and
|
|
68
|
+
the model servers running on it; it contacts nothing but 127.0.0.1) and lists, for lanes and for
|
|
69
|
+
the light model, the models that fit and why: open-weight models on your machine first where
|
|
70
|
+
they run well, then model APIs. `flightdeck models qwen3.5-9b` prints how to get one and the
|
|
71
|
+
config lines that use it. It recommends; it never changes your config. How the models did:
|
|
72
|
+
[`docs/MODELS.md`](docs/MODELS.md).
|
|
73
|
+
|
|
74
|
+
## Play a board
|
|
75
|
+
|
|
76
|
+
```sh
|
|
77
|
+
flightdeck play ~/ctf/some-board --max-lanes 4
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
A board is a folder of challenge folders, each with a `challenge.json` and its handout files
|
|
81
|
+
(format in [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md)). Lanes start cheapest-first; the rest
|
|
82
|
+
queue. Every lane is a model conversation, capped per lane by `token_cap` and `dollar_cap` (an
|
|
83
|
+
alert at 80%, your approval at 100%).
|
|
84
|
+
|
|
85
|
+
A flag a lane finds waits for you: `a` or `1` submits it, `d` or `0` declines. Under auto submit
|
|
86
|
+
(`submit = "auto"`, or `play --submit auto`) a flag the model submits goes at once, unless it is
|
|
87
|
+
written in the challenge's own description or handout files (often a decoy): that one still
|
|
88
|
+
waits for you. A flag found wrong is never asked about again.
|
|
89
|
+
|
|
90
|
+
The console raises an alert when a lane spends big on a low-value challenge, ignores what the
|
|
91
|
+
description hints at, heads the wrong way on web, loops or stalls. Lanes that need you sort
|
|
92
|
+
first, and the line under the list says why and numbers the answers: one key answers.
|
|
93
|
+
|
|
94
|
+

|
|
95
|
+
|
|
96
|
+
*An alert with its quick answers, from `flightdeck mock` (invented challenges).*
|
|
97
|
+
|
|
98
|
+
**Enter** opens a lane full screen: its transcript, the files it changed with diffs, and a chat
|
|
99
|
+
box (plain text is a message to the lane; `/redirect`, `/approve`, `/diff` and the rest are
|
|
100
|
+
commands; `esc` goes back).
|
|
101
|
+
|
|
102
|
+

|
|
103
|
+
|
|
104
|
+
*A lane full screen, from `flightdeck mock` (invented challenges).*
|
|
105
|
+
|
|
106
|
+
`t` takes over a lane's shell (its container in Docker): exit it to hand the lane back, and the
|
|
107
|
+
lane hears what you changed. The shell starts clean: it reads no startup file, and with `--host`
|
|
108
|
+
its HOME is a new folder only you can read, outside the workspace, so nothing the lane wrote runs
|
|
109
|
+
before you type. `e` escalates a lane to a stronger model with a summary of what it did, `f`
|
|
110
|
+
forks it, `?` shows every key.
|
|
111
|
+
|
|
112
|
+
When a model API failure would hit every lane (a refused key, an unknown model, no credits), the
|
|
113
|
+
queue is held and the header says why. Once you have fixed it, `/unhold` starts the queued lanes
|
|
114
|
+
(a key or model id changed in your config needs a new run).
|
|
115
|
+
|
|
116
|
+
Docker lanes get no network unless their challenge has a live instance (a web origin, or a TCP
|
|
117
|
+
service like `nc host port`: `tcp://host:port` in a challenge's `urls`). `--host` runs lane
|
|
118
|
+
commands as you, on this machine: a lane can read your files, your keys file and flightdeck's
|
|
119
|
+
own environment, and send what it reads to its model's API, which nothing can take back. The
|
|
120
|
+
run log, the console, transcripts, reports and writeup drafts show a key's name but not its
|
|
121
|
+
value (see [After a run](#after-a-run)), but that is cleanup, not a fence: use Docker, a
|
|
122
|
+
disposable VM, or a separate low-limit key.
|
|
123
|
+
|
|
124
|
+
Every run writes an append-only `events.jsonl` and each lane's transcript; a board with answers
|
|
125
|
+
(like the practice set) ends with a scorecard. `q` asks first while lanes are open, and a run
|
|
126
|
+
cannot be resumed. If flightdeck itself is killed (kill -9, a power loss), what its lanes started
|
|
127
|
+
can keep running: the next `play` or `practice` removes it and says what it removed,
|
|
128
|
+
`flightdeck doctor` lists it and `flightdeck doctor --clean` removes it.
|
|
129
|
+
|
|
130
|
+
## Play an event
|
|
131
|
+
|
|
132
|
+
Write down what the event's rules allow, check it, and play by it:
|
|
133
|
+
|
|
134
|
+
```sh
|
|
135
|
+
flightdeck preflight --example > event.toml # then edit it from the event's rules
|
|
136
|
+
flightdeck preflight event.toml # what flightdeck will do at each stage, and any problem
|
|
137
|
+
export CTFD_TOKEN=... # the variable event.toml names (never the token itself)
|
|
138
|
+
flightdeck play ~/ctf/event-board --event event.toml
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
With a CTFd event, flightdeck pulls the board into that folder, adds new waves as they appear,
|
|
142
|
+
submits the flags you approve through CTFd and follows your team's solves. A slow CTFd never
|
|
143
|
+
holds up the console: lanes carry on while their flags are on their way (the fleet shows
|
|
144
|
+
`sending`). A challenge whose tries CTFd counts always waits for you, under `auto` too, and each
|
|
145
|
+
approval says how many tries are left. Each stage (pull, L1, agents, submit, live instance) runs
|
|
146
|
+
as the profile's policy says: `auto`, `confirm` (you say yes first), `manual` or `off`, never
|
|
147
|
+
looser than the rules. Under `confirm` for live instances, a lane asks before it may reach its
|
|
148
|
+
challenge's instance and has no network until you allow it.
|
|
149
|
+
|
|
150
|
+
Team mode shares claims, live-box holds and solves through a folder your teammates sync (a network
|
|
151
|
+
drive, Syncthing): `flightdeck team init DIR` once, then `--team-dir DIR`. Your lanes never work a
|
|
152
|
+
challenge a teammate solved.
|
|
153
|
+
|
|
154
|
+
`--agent claude` (or `codex`) runs lanes as that coding-agent CLI, with its own login and models
|
|
155
|
+
(`[agents]` in your config), behind the same controls. Check it first with
|
|
156
|
+
`flightdeck doctor --agent claude`. A CLI says what a turn cost only when the turn ends, so its
|
|
157
|
+
lane's cost ends in `…` until then; codex reports no cost at all, so give its models a
|
|
158
|
+
`[prices."<model>"]` table for their lanes to show dollars.
|
|
159
|
+
|
|
160
|
+
## Your solvers first (L1)
|
|
161
|
+
|
|
162
|
+
Point flightdeck at a folder of your no-model solvers and each of flightdeck's own lanes runs the
|
|
163
|
+
ones that fit its challenge before its first model call (coding-agent lanes do not use the pack
|
|
164
|
+
yet), so a solve can cost no tokens at all. A flag an engine returns as its result waits for your
|
|
165
|
+
approval, or under auto submit goes to the platform at once; a flag that is only a match in what
|
|
166
|
+
an engine printed (a plain script that `l1 import` took in, say) always waits for you. When lanes
|
|
167
|
+
run in Docker, so do your solvers: each in a container of the lane image, your pack mounted
|
|
168
|
+
read-only, with no network unless the live-instance policy is `auto` and the challenge has an
|
|
169
|
+
instance. With `--host` they run on this machine, as you.
|
|
170
|
+
|
|
171
|
+
```sh
|
|
172
|
+
flightdeck l1 import ~/ctf/solvers # manifests for a folder of plain scripts (the scripts are never changed)
|
|
173
|
+
flightdeck l1 route ~/ctf/solvers ~/ctf/some-board/crab-walk # which engines a challenge reaches, and why (or why not)
|
|
174
|
+
flightdeck play ~/ctf/some-board --pack ~/ctf/solvers # or set [l1] pack in your config
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## After a run
|
|
178
|
+
|
|
179
|
+
```sh
|
|
180
|
+
flightdeck report RUN_DIR # where lanes stalled, which interventions helped, cost per solve
|
|
181
|
+
flightdeck writeups RUN_DIR # a writeup draft per solved challenge (--redact hides the flags)
|
|
182
|
+
flightdeck score RUN_DIR # the run scored against the board's answers
|
|
183
|
+
flightdeck gaps RUN_DIR # the challenges your L1 solvers did not solve, and why
|
|
184
|
+
flightdeck build gap CHALLENGE_DIR --pack ~/ctf/solvers # a new engine stub with a test that must pass
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
When a run ends, `play` prints its run folder with the report, writeups and gaps commands for it
|
|
188
|
+
(and score, when the board has answers), ready to paste. Writeup drafts and lane transcripts
|
|
189
|
+
take out what looks like a key or token, with or without `--redact`: the value of an upper-case
|
|
190
|
+
`NAME=value` or `NAME: value` whose name holds KEY, TOKEN, SECRET, PASSWORD, PASSWD, COOKIE or
|
|
191
|
+
AUTH, of an Authorization or Cookie header, and of a key with a well-known prefix (`sk-ant-`,
|
|
192
|
+
`ghp_` and the like) becomes `[redacted]`. Anything else stays, lower-case names included (a
|
|
193
|
+
`password` is everyday CTF content), so read a draft before you share it.
|
|
194
|
+
|
|
195
|
+
## Light model
|
|
196
|
+
|
|
197
|
+
A few small jobs can use a small model. Today that is the hint judge: when a lane seems to ignore
|
|
198
|
+
what its description hints at, the alert shows at once, and the light model checks the lane's
|
|
199
|
+
steps; if it says the lane is using the technique after all, the alert comes down. By default
|
|
200
|
+
there is no light model, only the rules. Five choices, in `[light]`:
|
|
201
|
+
|
|
202
|
+
- `rules`: no model (the default).
|
|
203
|
+
- `openai`: any OpenAI-compatible server. **Recommended: an open-weight model on your own
|
|
204
|
+
machine**, served by Ollama (`http://127.0.0.1:11434/v1`) or an MLX server
|
|
205
|
+
(`http://127.0.0.1:8080/v1`), with `key_env = ""`. No provider's safety classifier stands
|
|
206
|
+
between it and security work, and it costs nothing. flightdeck asks a model on your machine to
|
|
207
|
+
answer without thinking first (`thinking = false`, the default there): one that reasons first
|
|
208
|
+
spends a small job's few hundred tokens thinking and answers nothing.
|
|
209
|
+
- `anthropic`: a Claude model (`claude-haiku-5-5`, say).
|
|
210
|
+
- `cactus`: Cactus Needle, on this machine (the `[cactus]` extra).
|
|
211
|
+
- `decider`: Strands Decider, a decision model, served on your own machine. It writes no text: it
|
|
212
|
+
picks one of the options and says how confident it is. An answer below `threshold` (0.9 by
|
|
213
|
+
default; its README says answers at 0.9 or more are right about 95% of the time on short tasks
|
|
214
|
+
it has never seen) is unsure, and the rules' answer stands. No key, and nothing to pay.
|
|
215
|
+
`flightdeck init --light decider` writes it and says how to start its server:
|
|
216
|
+
`pip install strands-decider`, then
|
|
217
|
+
`strands-decider serve StrandsAgents/strands-decider-2B-qwen3.5-v1-2610 --port 8000` (on Apple
|
|
218
|
+
silicon add `--device mlx`, with its `mlx` extra: `pip install "strands-decider[mlx]>=0.3"`).
|
|
219
|
+
|
|
220
|
+
Measure one before you switch it on: which models to try, and how they did, is in
|
|
221
|
+
[`docs/MODELS.md`](docs/MODELS.md). `eval-local` decides:
|
|
222
|
+
|
|
223
|
+
```sh
|
|
224
|
+
flightdeck eval-local --engine openai --base-url http://127.0.0.1:11434/v1 --model <model> \
|
|
225
|
+
--compare anthropic:claude-haiku-5-5 --compare cactus
|
|
226
|
+
flightdeck eval-local --engine decider --base-url http://127.0.0.1:8000 # or a column: --compare decider@URL
|
|
227
|
+
flightdeck init --light ollama --light-model <model> # or edit [light]; at a terminal, init suggests a model
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
eval-local runs each engine and the rules over the bundled cases (naming the hinted technique,
|
|
231
|
+
judging a lane, summarising one) and prints them side by side: accuracy, refusals and failures,
|
|
232
|
+
time and cost per job. A paid API is announced first with its number of calls: one per case,
|
|
233
|
+
never a retry. `--thinking on|off` says whether a model on an OpenAI-compatible server may think
|
|
234
|
+
first; by default it is off on your machine and on elsewhere, unless your `[light] thinking`
|
|
235
|
+
says otherwise for that server. The decider's unsure answers get a row of their own and count
|
|
236
|
+
neither right nor wrong, so its accuracy says how often an answer it was sure of is right; its
|
|
237
|
+
summary cases show as n/a, since a decision model writes no text.
|
|
238
|
+
|
|
239
|
+
The light model never holds anything up. When it refuses, fails, times out, answers outside the
|
|
240
|
+
options or (the decider) is unsure, the rules' answer stands, and the run says so once. A refused
|
|
241
|
+
request is never sent on to another model or provider: you see the refusal and decide.
|
|
242
|
+
|
|
243
|
+
## Try the mock
|
|
244
|
+
|
|
245
|
+
```sh
|
|
246
|
+
flightdeck mock # the default scenario: every alert, then a 43-challenge wave
|
|
247
|
+
flightdeck mock --scenario wave # also: sstv, overthinking, web, team, flag
|
|
248
|
+
flightdeck mock --screenshot DIR # headless: SVGs of the key moments
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
The fleet's keys act on the selected lane: `p` pause, `h` hint, `r` redirect, `f` fork, `k` kill,
|
|
252
|
+
`e` escalate, `t` take over, `a` approve, `d` decline, `1`-`9`/`0` quick answers, `c` claim
|
|
253
|
+
(team), `/` commands, `s` sort, `?` help, `q` quit; **Enter** opens the lane full screen. In the
|
|
254
|
+
mock, `space` freezes the clock and `.` steps it. All challenges, teammates and flags there are
|
|
255
|
+
invented.
|
|
256
|
+
|
|
257
|
+
## Optional extras
|
|
258
|
+
|
|
259
|
+
- `ctf-flightdeck[cactus]`: Cactus Needle, a small on-device model, one of the light model's
|
|
260
|
+
choices (`flightdeck eval-local --engine cactus` measures it against the rules first). Its
|
|
261
|
+
telemetry is turned off. Nothing uses it unless you ask.
|
|
262
|
+
- `ctf-flightdeck[build]`: pytest, for `flightdeck build check`.
|
|
263
|
+
|
|
264
|
+
## Docs
|
|
265
|
+
|
|
266
|
+
[Vision](docs/VISION.md) · [Decisions](docs/DECISIONS.md) · [Roadmap](docs/ROADMAP.md) ·
|
|
267
|
+
[Console spec](docs/CONSOLE-SPEC.md) · [Architecture](docs/ARCHITECTURE.md) ·
|
|
268
|
+
[Model APIs](docs/MODEL-APIS.md) · [Models](docs/MODELS.md) ·
|
|
269
|
+
[Screenshots](docs/SCREENSHOTS.md) · [Changelog](CHANGELOG.md)
|
|
270
|
+
|
|
271
|
+
Development: `uv run pytest`. `ctfdeck` is an alias for `flightdeck`.
|
|
272
|
+
|
|
273
|
+
## License
|
|
274
|
+
|
|
275
|
+
MIT. See [LICENSE](LICENSE).
|