cipher-security 2.1.0 → 2.2.0

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.
Files changed (54) hide show
  1. package/bin/cipher.js +10 -0
  2. package/lib/analyze/consistency.js +566 -0
  3. package/lib/analyze/constitution.js +110 -0
  4. package/lib/analyze/sharding.js +251 -0
  5. package/lib/autonomous/agent-tool.js +165 -0
  6. package/lib/autonomous/framework.js +17 -0
  7. package/lib/autonomous/handoff.js +506 -0
  8. package/lib/autonomous/modes/blue.js +26 -0
  9. package/lib/autonomous/modes/red.js +28 -0
  10. package/lib/benchmark/agent.js +88 -26
  11. package/lib/benchmark/baselines.js +3 -0
  12. package/lib/benchmark/claude-code-solver.js +254 -0
  13. package/lib/benchmark/cognitive.js +283 -0
  14. package/lib/benchmark/index.js +12 -2
  15. package/lib/benchmark/knowledge.js +281 -0
  16. package/lib/benchmark/llm.js +156 -15
  17. package/lib/benchmark/models.js +5 -2
  18. package/lib/benchmark/nyu-ctf.js +192 -0
  19. package/lib/benchmark/overthewire.js +347 -0
  20. package/lib/benchmark/picoctf.js +281 -0
  21. package/lib/benchmark/prompts.js +280 -0
  22. package/lib/benchmark/registry.js +219 -0
  23. package/lib/benchmark/remote-solver.js +356 -0
  24. package/lib/benchmark/remote-target.js +263 -0
  25. package/lib/benchmark/reporter.js +35 -0
  26. package/lib/benchmark/runner.js +174 -10
  27. package/lib/benchmark/sandbox.js +35 -0
  28. package/lib/benchmark/scorer.js +22 -4
  29. package/lib/benchmark/solver.js +34 -1
  30. package/lib/benchmark/tools.js +262 -16
  31. package/lib/commands.js +9 -0
  32. package/lib/execution/council.js +434 -0
  33. package/lib/execution/parallel.js +292 -0
  34. package/lib/gates/circuit-breaker.js +135 -0
  35. package/lib/gates/confidence.js +302 -0
  36. package/lib/gates/corrections.js +219 -0
  37. package/lib/gates/self-check.js +245 -0
  38. package/lib/gateway/commands.js +727 -0
  39. package/lib/guardrails/engine.js +364 -0
  40. package/lib/mcp/server.js +349 -3
  41. package/lib/memory/compressor.js +94 -7
  42. package/lib/pipeline/hooks.js +288 -0
  43. package/lib/pipeline/index.js +11 -0
  44. package/lib/review/budget.js +210 -0
  45. package/lib/review/engine.js +526 -0
  46. package/lib/review/layers/acceptance-auditor.js +279 -0
  47. package/lib/review/layers/blind-hunter.js +500 -0
  48. package/lib/review/layers/defense-in-depth.js +209 -0
  49. package/lib/review/layers/edge-case-hunter.js +266 -0
  50. package/lib/review/panel.js +519 -0
  51. package/lib/review/two-stage.js +244 -0
  52. package/lib/session/cost-tracker.js +203 -0
  53. package/lib/session/logger.js +349 -0
  54. package/package.json +1 -1
@@ -0,0 +1,347 @@
1
+ // Copyright (c) 2026 defconxt. All rights reserved.
2
+ // Licensed under AGPL-3.0 — see LICENSE file for details.
3
+ // CIPHER is a trademark of defconxt.
4
+
5
+ /**
6
+ * OverTheWire — Challenge loader for the OverTheWire wargames.
7
+ *
8
+ * OverTheWire hosts SSH-based wargames with progressive difficulty levels.
9
+ * Each level requires solving the current challenge to obtain the password
10
+ * for the next level.
11
+ *
12
+ * Supported wargames:
13
+ * - Bandit (34 levels) — Linux basics, SSH, file operations
14
+ * - Natas (34 levels) — Server-side web security
15
+ * - Leviathan (8 levels) — Binary exploitation basics
16
+ * - Krypton (7 levels) — Classical cryptography
17
+ * - Narnia (10 levels) — Basic exploitation
18
+ *
19
+ * @module benchmark/overthewire
20
+ */
21
+
22
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
23
+ import { join } from 'node:path';
24
+ import { homedir } from 'node:os';
25
+ import { BenchmarkConfig } from './models.js';
26
+
27
+ // ---------------------------------------------------------------------------
28
+ // Constants
29
+ // ---------------------------------------------------------------------------
30
+
31
+ export const OTW_DATA_DIR = join(homedir(), '.cipher', 'benchmarks', 'overthewire');
32
+ export const OTW_PROGRESS_FILE = join(OTW_DATA_DIR, 'progress.json');
33
+
34
+ export const OTW_HOST = 'bandit.labs.overthewire.org';
35
+ export const OTW_BASE_DOMAIN = 'overthewire.org';
36
+
37
+ /** OverTheWire wargame definitions. */
38
+ export const OTW_WARGAMES = {
39
+ bandit: {
40
+ name: 'Bandit',
41
+ host: 'bandit.labs.overthewire.org',
42
+ port: 2220,
43
+ levels: 34,
44
+ category: 'general-skills',
45
+ description: 'Linux command line basics — files, permissions, SSH, networking',
46
+ startPassword: 'bandit0',
47
+ levelDescriptions: [
48
+ 'Read the readme file in the home directory.',
49
+ 'Find the password in a file with dashes in the name.',
50
+ 'Find the password in a hidden file.',
51
+ 'Find the human-readable file among several files.',
52
+ 'Find the only human-readable, non-executable, 1033-byte file.',
53
+ 'Find the file owned by a specific user and group.',
54
+ 'Find the file somewhere on the server with specific properties.',
55
+ 'Find the password in a file among lines of text.',
56
+ 'Find the unique line that occurs only once in the file.',
57
+ 'Find the base64-encoded password in data.txt.',
58
+ 'Decode the ROT13 encoded password.',
59
+ 'Decode the hexdump to extract the password.',
60
+ 'Decompress a series of compressed files.',
61
+ 'Use the SSH key to connect to the next level.',
62
+ 'Retrieve the password using the setuid binary with a port argument.',
63
+ 'Record the password by submitting the current password to a port.',
64
+ 'Read the password from /etc/bandit_pass/ using the setuid binary.',
65
+ 'Use the setuid binary with a correct command to read the password.',
66
+ 'Find the cron job and trace it to the password.',
67
+ 'Analyze the cron job script to find the password.',
68
+ 'Analyze the cron job that generates a password based on date.',
69
+ 'Use a shell escape in a setuid program.',
70
+ 'Use the daemon listening on localhost to get the next password.',
71
+ 'Brute-force or script the daemon with port and password.',
72
+ 'Crack the encrypted key using SSH agent forwarding.',
73
+ 'Use the git repository to find the password.',
74
+ 'Navigate git history to find the password.',
75
+ 'Explore git tags and branches for the password.',
76
+ 'Exploit a setuid script that doesn\'t sanitize input properly.',
77
+ 'Read the password with a race condition.',
78
+ 'Write a shell script to exploit the setuid binary.',
79
+ 'Network service exploitation.',
80
+ 'Advanced git exploration.',
81
+ 'Final bandit level.',
82
+ ],
83
+ },
84
+
85
+ natas: {
86
+ name: 'Natas',
87
+ host: 'natas0.natas.labs.overthewire.org',
88
+ port: 80,
89
+ levels: 34,
90
+ category: 'web-exploitation',
91
+ description: 'Server-side web security — source disclosure, injection, auth bypass',
92
+ startPassword: 'natas0',
93
+ levelDescriptions: [
94
+ 'View page source to find the password.',
95
+ 'Find the password in the page source.',
96
+ 'Find the hidden page listed in robots.txt.',
97
+ 'Find the password in the page source of a hidden page.',
98
+ 'Bypass server-side access control.',
99
+ 'Include a remote/local file to read the password.',
100
+ 'Exploit an input validation flaw.',
101
+ 'Use directory traversal to read files.',
102
+ 'Exploit a weak session or authentication mechanism.',
103
+ 'Find secret data in server source code.',
104
+ ],
105
+ },
106
+
107
+ leviathan: {
108
+ name: 'Leviathan',
109
+ host: 'leviathan.labs.overthewire.org',
110
+ port: 2223,
111
+ levels: 8,
112
+ category: 'binary-exploitation',
113
+ description: 'Basic exploitation — setuid, ltrace, simple buffer overflows',
114
+ startPassword: '',
115
+ levelDescriptions: [
116
+ 'Find the hidden directory and decode the bookmarks file.',
117
+ 'Use ltrace to find the password checked by the setuid binary.',
118
+ 'Exploit the setuid binary to read the password file.',
119
+ 'Trick the binary with a symlink.',
120
+ 'Disassemble or trace the binary for the password.',
121
+ 'Run the setuid binary correctly to get the password.',
122
+ 'Use the binary with a specific trick.',
123
+ 'Final leviathan level.',
124
+ ],
125
+ },
126
+
127
+ krypton: {
128
+ name: 'Krypton',
129
+ host: 'krypton.labs.overthewire.org',
130
+ port: 2231,
131
+ levels: 7,
132
+ category: 'cryptography',
133
+ description: 'Classical cryptography — Caesar, Vigenère, frequency analysis',
134
+ startPassword: 'KRYPTONISGREAT',
135
+ levelDescriptions: [
136
+ 'Decode base64.',
137
+ 'Decrypt ROT13 / Caesar cipher.',
138
+ 'Break a Caesar cipher with known plaintext.',
139
+ 'Break a Vigenère cipher.',
140
+ 'Exploit a stream cipher.',
141
+ 'Break a more complex cipher.',
142
+ 'Final krypton level.',
143
+ ],
144
+ },
145
+
146
+ narnia: {
147
+ name: 'Narnia',
148
+ host: 'narnia.labs.overthewire.org',
149
+ port: 2226,
150
+ levels: 10,
151
+ category: 'binary-exploitation',
152
+ description: 'Basic exploitation — buffer overflows, format strings, shellcode',
153
+ startPassword: '',
154
+ levelDescriptions: [
155
+ 'Set environment variable to the right value.',
156
+ 'Buffer overflow to overwrite a variable.',
157
+ 'Buffer overflow to redirect execution.',
158
+ 'Buffer overflow with shellcode.',
159
+ 'Format string vulnerability.',
160
+ 'Stack-based exploitation.',
161
+ 'Advanced buffer overflow.',
162
+ 'Return-to-libc.',
163
+ 'Advanced exploitation.',
164
+ 'Final narnia level.',
165
+ ],
166
+ },
167
+ };
168
+
169
+ // ---------------------------------------------------------------------------
170
+ // Level-to-difficulty mapping
171
+ // ---------------------------------------------------------------------------
172
+
173
+ /**
174
+ * Map a wargame level number to difficulty 1-5.
175
+ * Earlier levels are easier, later levels are harder.
176
+ *
177
+ * @param {number} level - Level number (0-indexed)
178
+ * @param {number} totalLevels - Total levels in the wargame
179
+ * @returns {number} Difficulty 1-5
180
+ */
181
+ function levelToDifficulty(level, totalLevels) {
182
+ const pct = level / (totalLevels - 1 || 1);
183
+ if (pct <= 0.2) return 1;
184
+ if (pct <= 0.4) return 2;
185
+ if (pct <= 0.6) return 3;
186
+ if (pct <= 0.8) return 4;
187
+ return 5;
188
+ }
189
+
190
+ // ---------------------------------------------------------------------------
191
+ // Loader
192
+ // ---------------------------------------------------------------------------
193
+
194
+ /**
195
+ * Generate BenchmarkConfig entries for a single wargame.
196
+ *
197
+ * @param {string} wargameId - Key in OTW_WARGAMES
198
+ * @returns {BenchmarkConfig[]}
199
+ */
200
+ export function loadOtwWargame(wargameId) {
201
+ const wg = OTW_WARGAMES[wargameId];
202
+ if (!wg) throw new Error(`Unknown wargame: ${wargameId}. Available: ${Object.keys(OTW_WARGAMES).join(', ')}`);
203
+
204
+ const configs = [];
205
+ for (let i = 0; i < wg.levels; i++) {
206
+ const username = `${wargameId}${i}`;
207
+ const description = wg.levelDescriptions?.[i] || `Level ${i} of ${wg.name}`;
208
+
209
+ configs.push(new BenchmarkConfig({
210
+ name: `${wg.name} Level ${i}`,
211
+ description,
212
+ level: levelToDifficulty(i, wg.levels),
213
+ tags: [wg.category, 'overthewire', wargameId],
214
+ winCondition: 'flag', // The "flag" is the next level's password
215
+ files: [],
216
+ path: OTW_DATA_DIR,
217
+ category: wg.category,
218
+ }));
219
+ }
220
+
221
+ return configs;
222
+ }
223
+
224
+ /**
225
+ * Load all OverTheWire challenges across all wargames.
226
+ * @returns {BenchmarkConfig[]}
227
+ */
228
+ export function enumerateOtwChallenges() {
229
+ const all = [];
230
+ for (const id of Object.keys(OTW_WARGAMES)) {
231
+ all.push(...loadOtwWargame(id));
232
+ }
233
+ return all;
234
+ }
235
+
236
+ /**
237
+ * Get SSH connection info for an OverTheWire level.
238
+ *
239
+ * @param {string} wargameId
240
+ * @param {number} level
241
+ * @returns {{ host: string, port: number, username: string, password: string|null }}
242
+ */
243
+ export function getOtwConnectionInfo(wargameId, level) {
244
+ const wg = OTW_WARGAMES[wargameId];
245
+ if (!wg) throw new Error(`Unknown wargame: ${wargameId}`);
246
+
247
+ const username = `${wargameId}${level}`;
248
+ const progress = loadProgress();
249
+ const password = level === 0
250
+ ? wg.startPassword
251
+ : progress[wargameId]?.[level] ?? null;
252
+
253
+ // Natas uses per-level hostnames
254
+ let host = wg.host;
255
+ if (wargameId === 'natas') {
256
+ host = `natas${level}.natas.labs.overthewire.org`;
257
+ }
258
+
259
+ return { host, port: wg.port, username, password };
260
+ }
261
+
262
+ // ---------------------------------------------------------------------------
263
+ // Progress tracking
264
+ // ---------------------------------------------------------------------------
265
+
266
+ /**
267
+ * Load saved progress (discovered passwords per level).
268
+ * @returns {object}
269
+ */
270
+ export function loadProgress() {
271
+ if (!existsSync(OTW_PROGRESS_FILE)) return {};
272
+ try {
273
+ return JSON.parse(readFileSync(OTW_PROGRESS_FILE, 'utf8'));
274
+ } catch {
275
+ return {};
276
+ }
277
+ }
278
+
279
+ /**
280
+ * Save a discovered password for a level.
281
+ *
282
+ * @param {string} wargameId
283
+ * @param {number} level
284
+ * @param {string} password
285
+ */
286
+ export function saveProgress(wargameId, level, password) {
287
+ mkdirSync(OTW_DATA_DIR, { recursive: true });
288
+ const progress = loadProgress();
289
+ if (!progress[wargameId]) progress[wargameId] = {};
290
+ progress[wargameId][level] = password;
291
+ writeFileSync(OTW_PROGRESS_FILE, JSON.stringify(progress, null, 2));
292
+ }
293
+
294
+ /**
295
+ * Get progress stats across all wargames.
296
+ * @returns {object}
297
+ */
298
+ export function getProgressStats() {
299
+ const progress = loadProgress();
300
+ const stats = {};
301
+
302
+ for (const [id, wg] of Object.entries(OTW_WARGAMES)) {
303
+ const solved = progress[id] ? Object.keys(progress[id]).length : 0;
304
+ stats[id] = {
305
+ name: wg.name,
306
+ total: wg.levels,
307
+ solved,
308
+ pct: wg.levels > 0 ? Math.round((solved / wg.levels) * 100) : 0,
309
+ category: wg.category,
310
+ };
311
+ }
312
+
313
+ return stats;
314
+ }
315
+
316
+ /**
317
+ * Initialize OverTheWire data directory.
318
+ * @returns {boolean}
319
+ */
320
+ export function cloneOtw() {
321
+ mkdirSync(OTW_DATA_DIR, { recursive: true });
322
+ // Save initial progress file if it doesn't exist
323
+ if (!existsSync(OTW_PROGRESS_FILE)) {
324
+ writeFileSync(OTW_PROGRESS_FILE, '{}');
325
+ }
326
+ return true;
327
+ }
328
+
329
+ /**
330
+ * Get catalog stats for all OTW wargames.
331
+ * @returns {object}
332
+ */
333
+ export function getOtwCatalogStats() {
334
+ const challenges = enumerateOtwChallenges();
335
+ const byCategory = {};
336
+ const byLevel = {};
337
+ const byWargame = {};
338
+
339
+ for (const c of challenges) {
340
+ byCategory[c.category] = (byCategory[c.category] || 0) + 1;
341
+ byLevel[c.level] = (byLevel[c.level] || 0) + 1;
342
+ const wg = c.tags.find(t => OTW_WARGAMES[t]);
343
+ if (wg) byWargame[wg] = (byWargame[wg] || 0) + 1;
344
+ }
345
+
346
+ return { total: challenges.length, byCategory, byLevel, byWargame };
347
+ }
@@ -0,0 +1,281 @@
1
+ // Copyright (c) 2026 defconxt. All rights reserved.
2
+ // Licensed under AGPL-3.0 — see LICENSE file for details.
3
+ // CIPHER is a trademark of defconxt.
4
+
5
+ /**
6
+ * PicoCTF — Challenge loader for CMU's picoCTF benchmark suite.
7
+ *
8
+ * PicoCTF challenges are hosted remotely on picoctf.org / picoGym.
9
+ * This loader builds a challenge catalog from:
10
+ * 1. A bundled catalog of known challenges with metadata
11
+ * 2. Community writeup repos for solution verification
12
+ *
13
+ * Categories: Cryptography, Forensics, Binary Exploitation,
14
+ * Web Exploitation, Reverse Engineering, General Skills
15
+ *
16
+ * Flag format: picoCTF{...}
17
+ *
18
+ * @module benchmark/picoctf
19
+ */
20
+
21
+ import { existsSync, readdirSync, readFileSync, writeFileSync, mkdirSync, statSync } from 'node:fs';
22
+ import { join, basename } from 'node:path';
23
+ import { homedir } from 'node:os';
24
+ import { execSync } from 'node:child_process';
25
+ import { BenchmarkConfig, CompetitorBaseline } from './models.js';
26
+
27
+ // ---------------------------------------------------------------------------
28
+ // Constants
29
+ // ---------------------------------------------------------------------------
30
+
31
+ export const PICOCTF_DATA_DIR = join(homedir(), '.cipher', 'benchmarks', 'picoctf');
32
+ export const PICOCTF_CATALOG_FILE = join(PICOCTF_DATA_DIR, 'catalog.json');
33
+
34
+ /** PicoCTF categories mapped to our standard taxonomy. */
35
+ export const PICO_CATEGORIES = [
36
+ 'cryptography',
37
+ 'forensics',
38
+ 'binary-exploitation',
39
+ 'web-exploitation',
40
+ 'reverse-engineering',
41
+ 'general-skills',
42
+ ];
43
+
44
+ /** Category aliases (picoCTF uses various naming conventions). */
45
+ const CATEGORY_ALIASES = {
46
+ 'crypto': 'cryptography',
47
+ 'cryptography': 'cryptography',
48
+ 'forensics': 'forensics',
49
+ 'pwn': 'binary-exploitation',
50
+ 'binary exploitation': 'binary-exploitation',
51
+ 'binary_exploitation': 'binary-exploitation',
52
+ 'binaryexploitation': 'binary-exploitation',
53
+ 'web': 'web-exploitation',
54
+ 'web exploitation': 'web-exploitation',
55
+ 'web_exploitation': 'web-exploitation',
56
+ 'webexploitation': 'web-exploitation',
57
+ 'rev': 'reverse-engineering',
58
+ 'reverse engineering': 'reverse-engineering',
59
+ 'reverse_engineering': 'reverse-engineering',
60
+ 'reverseengineering': 'reverse-engineering',
61
+ 'general skills': 'general-skills',
62
+ 'general_skills': 'general-skills',
63
+ 'generalskills': 'general-skills',
64
+ 'misc': 'general-skills',
65
+ };
66
+
67
+ /**
68
+ * Difficulty mapping from point values to 1-5 scale.
69
+ * PicoCTF uses point values: 10-50 easy, 50-150 medium, 150-300 hard, 300-500 very hard.
70
+ */
71
+ function pointsToLevel(points) {
72
+ if (points <= 50) return 1;
73
+ if (points <= 150) return 2;
74
+ if (points <= 300) return 3;
75
+ if (points <= 500) return 4;
76
+ return 5;
77
+ }
78
+
79
+ /** Normalize a category string. */
80
+ function normalizeCategory(raw) {
81
+ return CATEGORY_ALIASES[raw.toLowerCase().trim()] || 'general-skills';
82
+ }
83
+
84
+ // ---------------------------------------------------------------------------
85
+ // PicoCTF challenge entry
86
+ // ---------------------------------------------------------------------------
87
+
88
+ /**
89
+ * @typedef {object} PicoCTFChallenge
90
+ * @property {string} name - Challenge name
91
+ * @property {string} category - Normalized category
92
+ * @property {number} points - Point value
93
+ * @property {string} event - Event name (e.g. 'picoCTF 2024')
94
+ * @property {string} description - Challenge description
95
+ * @property {string} [host] - Remote host (if network challenge)
96
+ * @property {number} [port] - Remote port
97
+ * @property {string} targetType - 'ssh' | 'netcat' | 'http' | 'static' | 'unknown'
98
+ * @property {string[]} hints - Challenge hints
99
+ * @property {string[]} files - Downloadable file URLs
100
+ * @property {string} [flag] - Known flag (for validation)
101
+ */
102
+
103
+ // ---------------------------------------------------------------------------
104
+ // Bundled challenge catalog
105
+ // ---------------------------------------------------------------------------
106
+
107
+ /**
108
+ * Built-in catalog of PicoCTF challenges.
109
+ * This is a representative subset covering all categories and difficulty levels.
110
+ * The full picoGym has 400+ challenges; this catalog focuses on ones suitable
111
+ * for automated agent benchmarking.
112
+ *
113
+ * Organized by event year → challenges.
114
+ */
115
+ export const PICOCTF_CATALOG = [
116
+ // --- General Skills (beginner) ---
117
+ { name: 'Obedient Cat', event: 'picoCTF 2021', category: 'general-skills', points: 5, targetType: 'static', description: 'Download and read a file to find the flag.' },
118
+ { name: 'Python Wrangling', event: 'picoCTF 2021', category: 'general-skills', points: 10, targetType: 'static', description: 'Use a Python script with a password to decrypt the flag.' },
119
+ { name: 'Wave a Flag', event: 'picoCTF 2021', category: 'general-skills', points: 10, targetType: 'static', description: 'Execute a program to get the flag.' },
120
+ { name: 'Nice Netcat', event: 'picoCTF 2021', category: 'general-skills', points: 15, targetType: 'netcat', description: 'Connect with netcat and decode ASCII values.' },
121
+ { name: 'Static aint always noise', event: 'picoCTF 2021', category: 'general-skills', points: 20, targetType: 'static', description: 'Use a disassembly script to find the flag in a binary.' },
122
+ { name: 'Tab, Tab, Attack', event: 'picoCTF 2021', category: 'general-skills', points: 20, targetType: 'static', description: 'Navigate deeply nested directories to find executable.' },
123
+ { name: 'Magikarp Ground Mission', event: 'picoCTF 2021', category: 'general-skills', points: 30, targetType: 'ssh', description: 'SSH into a machine and find flag pieces.' },
124
+ { name: 'Serpentine', event: 'picoCTF 2022', category: 'general-skills', points: 100, targetType: 'static', description: 'Modify Python source to bypass menu and call flag function.' },
125
+ { name: 'Based', event: 'picoCTF 2019', category: 'general-skills', points: 200, targetType: 'netcat', description: 'Convert between binary, octal, and hex encodings.' },
126
+ { name: 'First Find', event: 'picoCTF 2024', category: 'general-skills', points: 100, targetType: 'static', description: 'Unzip and find a specific file in a directory structure.' },
127
+
128
+ // --- Cryptography ---
129
+ { name: 'Mod 26', event: 'picoCTF 2021', category: 'cryptography', points: 10, targetType: 'static', description: 'Apply ROT13 to decode the flag.' },
130
+ { name: 'Mind your Ps and Qs', event: 'picoCTF 2021', category: 'cryptography', points: 20, targetType: 'static', description: 'Factor small RSA modulus and decrypt.' },
131
+ { name: 'Easy1', event: 'picoCTF 2019', category: 'cryptography', points: 100, targetType: 'static', description: 'Decrypt a one-time pad cipher with a given key table.' },
132
+ { name: 'New Caesar', event: 'picoCTF 2021', category: 'cryptography', points: 60, targetType: 'static', description: 'Reverse a custom Base16 + shift cipher.' },
133
+ { name: 'Mini RSA', event: 'picoCTF 2021', category: 'cryptography', points: 70, targetType: 'static', description: 'Small public exponent RSA attack.' },
134
+ { name: 'Substitution0', event: 'picoCTF 2022', category: 'cryptography', points: 100, targetType: 'static', description: 'Decode a simple substitution cipher.' },
135
+ { name: 'transposition-trial', event: 'picoCTF 2022', category: 'cryptography', points: 100, targetType: 'static', description: 'Reverse a transposition cipher.' },
136
+ { name: 'C3', event: 'picoCTF 2024', category: 'cryptography', points: 200, targetType: 'static', description: 'Custom cipher with Python encoder/decoder.' },
137
+ { name: 'rsa_oracle', event: 'picoCTF 2023', category: 'cryptography', points: 300, targetType: 'netcat', description: 'Use RSA oracle for chosen-ciphertext attack.' },
138
+
139
+ // --- Web Exploitation ---
140
+ { name: 'GET aHEAD', event: 'picoCTF 2021', category: 'web-exploitation', points: 20, targetType: 'http', description: 'Use HTTP HEAD request to find the flag.' },
141
+ { name: 'Cookies', event: 'picoCTF 2021', category: 'web-exploitation', points: 40, targetType: 'http', description: 'Manipulate cookie values to find the flag.' },
142
+ { name: 'Insp3ct0r', event: 'picoCTF 2019', category: 'web-exploitation', points: 50, targetType: 'http', description: 'Inspect page source, CSS, and JS for flag parts.' },
143
+ { name: 'Scavenger Hunt', event: 'picoCTF 2021', category: 'web-exploitation', points: 50, targetType: 'http', description: 'Find flag pieces in HTML, CSS, JS, robots.txt, .htaccess.' },
144
+ { name: 'SQL Direct', event: 'picoCTF 2022', category: 'web-exploitation', points: 200, targetType: 'netcat', description: 'Connect to PostgreSQL and query for the flag.' },
145
+ { name: 'Web Gauntlet', event: 'picoCTF 2020', category: 'web-exploitation', points: 170, targetType: 'http', description: 'SQL injection with increasing filter bypass rounds.' },
146
+ { name: 'JAuth', event: 'picoCTF 2022', category: 'web-exploitation', points: 300, targetType: 'http', description: 'JWT none algorithm bypass for privilege escalation.' },
147
+ { name: 'Java Code Analysis', event: 'picoCTF 2023', category: 'web-exploitation', points: 300, targetType: 'http', description: 'Analyze Java source to find JWT weak secret.' },
148
+ { name: 'Crack the Gate 1', event: 'picoCTF 2025', category: 'web-exploitation', points: 100, targetType: 'http', description: 'Find dev backdoor header in page source.' },
149
+
150
+ // --- Forensics ---
151
+ { name: 'information', event: 'picoCTF 2021', category: 'forensics', points: 10, targetType: 'static', description: 'Extract flag from image EXIF metadata.' },
152
+ { name: 'Matryoshka doll', event: 'picoCTF 2021', category: 'forensics', points: 30, targetType: 'static', description: 'Recursively extract hidden files from images.' },
153
+ { name: 'Wireshark doo dooo', event: 'picoCTF 2021', category: 'forensics', points: 50, targetType: 'static', description: 'Analyze a pcap file to find the flag.' },
154
+ { name: 'Trivial Flag Transfer Protocol', event: 'picoCTF 2021', category: 'forensics', points: 90, targetType: 'static', description: 'Extract files from TFTP pcap and decode.' },
155
+ { name: 'St3g0', event: 'picoCTF 2022', category: 'forensics', points: 200, targetType: 'static', description: 'Extract hidden data from PNG using steganography.' },
156
+ { name: 'Sleuthkit Intro', event: 'picoCTF 2022', category: 'forensics', points: 100, targetType: 'static', description: 'Use mmls and other Sleuthkit tools on a disk image.' },
157
+ { name: 'Disk, disk, sleuth!', event: 'picoCTF 2021', category: 'forensics', points: 110, targetType: 'static', description: 'Analyze a disk image with Sleuthkit.' },
158
+
159
+ // --- Reverse Engineering ---
160
+ { name: 'Transformation', event: 'picoCTF 2021', category: 'reverse-engineering', points: 20, targetType: 'static', description: 'Reverse a Unicode encoding transformation.' },
161
+ { name: 'vault-door-training', event: 'picoCTF 2019', category: 'reverse-engineering', points: 50, targetType: 'static', description: 'Read Java source to find hardcoded password.' },
162
+ { name: 'GDB baby step 1', event: 'picoCTF 2022', category: 'reverse-engineering', points: 100, targetType: 'static', description: 'Use GDB to find value in eax register.' },
163
+ { name: 'patchme.py', event: 'picoCTF 2022', category: 'reverse-engineering', points: 100, targetType: 'static', description: 'Read Python source to find the correct password.' },
164
+ { name: 'Safe Opener', event: 'picoCTF 2022', category: 'reverse-engineering', points: 100, targetType: 'static', description: 'Decode Base64 password from Java source.' },
165
+ { name: 'Reverse', event: 'picoCTF 2024', category: 'reverse-engineering', points: 100, targetType: 'static', description: 'Use strings or disassemble binary to find flag.' },
166
+ { name: 'asm1', event: 'picoCTF 2019', category: 'reverse-engineering', points: 200, targetType: 'static', description: 'Trace x86 assembly to compute return value.' },
167
+ { name: 'Keygenme', event: 'picoCTF 2022', category: 'reverse-engineering', points: 400, targetType: 'static', description: 'Reverse a key validation algorithm in a binary.' },
168
+
169
+ // --- Binary Exploitation ---
170
+ { name: 'Stonks', event: 'picoCTF 2021', category: 'binary-exploitation', points: 20, targetType: 'netcat', description: 'Format string vulnerability to leak flag from stack.' },
171
+ { name: 'buffer overflow 0', event: 'picoCTF 2022', category: 'binary-exploitation', points: 100, targetType: 'netcat', description: 'Basic stack buffer overflow to trigger win function.' },
172
+ { name: 'buffer overflow 1', event: 'picoCTF 2022', category: 'binary-exploitation', points: 200, targetType: 'netcat', description: 'Buffer overflow to overwrite return address.' },
173
+ { name: 'RPS', event: 'picoCTF 2022', category: 'binary-exploitation', points: 200, targetType: 'netcat', description: 'Win 5 consecutive games via binary analysis.' },
174
+ { name: 'x-sixty-what', event: 'picoCTF 2022', category: 'binary-exploitation', points: 200, targetType: 'netcat', description: '64-bit buffer overflow to call flag function.' },
175
+ { name: 'format string 0', event: 'picoCTF 2024', category: 'binary-exploitation', points: 50, targetType: 'netcat', description: 'Exploit a format string vulnerability.' },
176
+ { name: 'heap 0', event: 'picoCTF 2024', category: 'binary-exploitation', points: 50, targetType: 'netcat', description: 'Heap overflow to overwrite adjacent data.' },
177
+ ];
178
+
179
+ // ---------------------------------------------------------------------------
180
+ // Loader
181
+ // ---------------------------------------------------------------------------
182
+
183
+ /**
184
+ * Load a PicoCTF challenge entry into a BenchmarkConfig.
185
+ *
186
+ * @param {PicoCTFChallenge} entry
187
+ * @returns {BenchmarkConfig}
188
+ */
189
+ export function loadPicoChallenge(entry) {
190
+ const category = normalizeCategory(entry.category);
191
+ const level = entry.level ?? pointsToLevel(entry.points ?? 100);
192
+ const tags = [category, 'picoctf'];
193
+ if (entry.event) tags.push(entry.event.toLowerCase().replace(/\s+/g, '-'));
194
+
195
+ return new BenchmarkConfig({
196
+ name: entry.name,
197
+ description: entry.description || '',
198
+ level: Math.min(5, Math.max(1, level)),
199
+ tags,
200
+ winCondition: 'flag',
201
+ files: (entry.files || []).map(f => typeof f === 'string' ? { name: f } : f),
202
+ path: entry.path || PICOCTF_DATA_DIR,
203
+ category,
204
+ });
205
+ }
206
+
207
+ /**
208
+ * Load all challenges from the bundled catalog.
209
+ * @returns {BenchmarkConfig[]}
210
+ */
211
+ export function enumeratePicoChallenges() {
212
+ // First try loading from disk catalog (may have been enriched)
213
+ if (existsSync(PICOCTF_CATALOG_FILE)) {
214
+ try {
215
+ const catalog = JSON.parse(readFileSync(PICOCTF_CATALOG_FILE, 'utf8'));
216
+ return catalog.map(loadPicoChallenge);
217
+ } catch { /* fall through to bundled */ }
218
+ }
219
+
220
+ // Use bundled catalog
221
+ return PICOCTF_CATALOG.map(loadPicoChallenge);
222
+ }
223
+
224
+ /**
225
+ * Save an enriched catalog to disk.
226
+ * @param {PicoCTFChallenge[]} challenges
227
+ */
228
+ export function savePicoCatalog(challenges) {
229
+ mkdirSync(PICOCTF_DATA_DIR, { recursive: true });
230
+ writeFileSync(PICOCTF_CATALOG_FILE, JSON.stringify(challenges, null, 2));
231
+ }
232
+
233
+ /**
234
+ * Initialize PicoCTF data directory and save bundled catalog.
235
+ * @returns {boolean}
236
+ */
237
+ export function clonePicoCTF() {
238
+ mkdirSync(PICOCTF_DATA_DIR, { recursive: true });
239
+ savePicoCatalog(PICOCTF_CATALOG);
240
+ return true;
241
+ }
242
+
243
+ /**
244
+ * Get target connection info for a PicoCTF challenge.
245
+ *
246
+ * @param {PicoCTFChallenge} entry
247
+ * @returns {{ type: string, host: string, port: number }|null}
248
+ */
249
+ export function getPicoTargetInfo(entry) {
250
+ if (entry.targetType === 'static') return null;
251
+ return {
252
+ type: entry.targetType || 'unknown',
253
+ host: entry.host || 'saturn.picoctf.net',
254
+ port: entry.port || 0, // Port assigned dynamically per-instance
255
+ };
256
+ }
257
+
258
+ /**
259
+ * Get challenge stats by category and difficulty.
260
+ * @returns {object}
261
+ */
262
+ export function getPicoCatalogStats() {
263
+ const challenges = enumeratePicoChallenges();
264
+ const byCategory = {};
265
+ const byLevel = {};
266
+ const byTargetType = {};
267
+ const byEvent = {};
268
+
269
+ for (const c of challenges) {
270
+ byCategory[c.category] = (byCategory[c.category] || 0) + 1;
271
+ byLevel[c.level] = (byLevel[c.level] || 0) + 1;
272
+ // Count target types from original catalog entries
273
+ const entry = PICOCTF_CATALOG.find(e => e.name === c.name);
274
+ if (entry) {
275
+ byTargetType[entry.targetType] = (byTargetType[entry.targetType] || 0) + 1;
276
+ byEvent[entry.event] = (byEvent[entry.event] || 0) + 1;
277
+ }
278
+ }
279
+
280
+ return { total: challenges.length, byCategory, byLevel, byTargetType, byEvent };
281
+ }