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,280 @@
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
+ * Meta-Prompting — Tag-aware system prompt generation for benchmark agents.
7
+ *
8
+ * Generates system prompts adapted to challenge type (web, crypto, pwn, etc.)
9
+ * with relevant techniques, tool suggestions, and attack patterns.
10
+ *
11
+ * @module benchmark/prompts
12
+ */
13
+
14
+ // ---------------------------------------------------------------------------
15
+ // Tag-specific technique libraries
16
+ // ---------------------------------------------------------------------------
17
+
18
+ const TAG_TECHNIQUES = {
19
+ sqli: [
20
+ 'Try SQL injection: single quotes, UNION SELECT, boolean-based blind, time-based blind.',
21
+ 'Test: \' OR 1=1--, \' UNION SELECT NULL,NULL--, sqlmap for automated testing.',
22
+ 'Check for: error-based leaks, stacked queries, second-order injection.',
23
+ ],
24
+ xss: [
25
+ 'Try XSS: reflected, stored, DOM-based. Check input reflection points.',
26
+ 'Payloads: <script>alert(1)</script>, <img onerror=alert(1) src=x>, event handlers.',
27
+ 'Look for: CSP bypasses, innerHTML sinks, postMessage handlers.',
28
+ ],
29
+ ssrf: [
30
+ 'Try SSRF: internal service access, cloud metadata (169.254.169.254), file:// protocol.',
31
+ 'Test URL parameters, webhook URLs, image fetchers, PDF generators.',
32
+ 'Bypass filters: IP encoding (decimal, hex, octal), DNS rebinding, redirect chains.',
33
+ ],
34
+ lfi: [
35
+ 'Try LFI: path traversal (../../etc/passwd), null byte injection, wrapper protocols.',
36
+ 'PHP wrappers: php://filter/convert.base64-encode, php://input, data://.',
37
+ 'Log poisoning: inject into access logs, then include the log file.',
38
+ ],
39
+ rce: [
40
+ 'Try RCE: command injection (;, |, &&, $(), backticks), deserialization, template injection.',
41
+ 'Test parameters with: ; id, | whoami, `whoami`, $(id).',
42
+ 'Look for: eval(), exec(), system(), popen(), subprocess in source code.',
43
+ ],
44
+ crypto: [
45
+ 'Analyze the cryptographic scheme. Look for: weak algorithms, padding oracles, ECB mode.',
46
+ 'Try: known-plaintext attacks, frequency analysis, brute force on weak keys.',
47
+ 'Check for: hardcoded keys, predictable IVs, hash length extension.',
48
+ ],
49
+ forensics: [
50
+ 'Examine files with: file, strings, xxd, binwalk, exiftool.',
51
+ 'Look for: hidden data in images (steganography), deleted files, memory dumps.',
52
+ 'Check: file headers, embedded archives, alternate data streams.',
53
+ ],
54
+ web: [
55
+ 'Enumerate: directory brute-force, robots.txt, .git exposure, backup files (.bak, ~).',
56
+ 'Test auth: default credentials, JWT manipulation, session fixation, IDOR.',
57
+ 'Check for: SSTI, CSRF, XXE, open redirects, insecure deserialization.',
58
+ ],
59
+ pwn: [
60
+ 'Analyze the binary: checksec, file, strings, ltrace, strace.',
61
+ 'Look for: buffer overflow, format string, use-after-free, heap exploitation.',
62
+ 'Tools: gdb, pwntools, ROPgadget, one_gadget.',
63
+ ],
64
+ rev: [
65
+ 'Disassemble with: objdump, Ghidra, radare2, IDA.',
66
+ 'Look for: hardcoded strings, comparison functions, XOR/cipher routines.',
67
+ 'Dynamic analysis: ltrace, strace, gdb breakpoints on key functions.',
68
+ ],
69
+ misc: [
70
+ 'Read the challenge description carefully for hidden hints.',
71
+ 'Try: encoding/decoding (base64, hex, rot13), OSINT, social engineering clues.',
72
+ 'Think outside the box — misc challenges often have unconventional solutions.',
73
+ ],
74
+ idor: [
75
+ 'IDOR: Insecure Direct Object Reference — change IDs in URLs, POST bodies, or headers.',
76
+ 'Try sequential IDs (1, 2, 3...), user IDs in profile/settings endpoints.',
77
+ 'Check if changing user_id, account_id, or profile_id in requests accesses other accounts.',
78
+ 'Look for admin panels or privileged resources accessible by changing object references.',
79
+ ],
80
+ jwt: [
81
+ 'JWT: Decode the token (base64) to read header and payload — check for role/admin claims.',
82
+ 'Try: algorithm confusion (alg:none), weak secrets (brute-force with common passwords).',
83
+ 'Modify payload claims (role: "admin", is_admin: true) and re-sign if secret is known.',
84
+ 'Check for: JWK injection, kid parameter injection, JWKS endpoint exposure.',
85
+ ],
86
+ default_credentials: [
87
+ 'Try common default credentials: admin:admin, admin:password, test:test, root:root.',
88
+ 'Check the app for user registration — create an account to understand the auth flow.',
89
+ 'Look for credential hints in source code comments, README, or error messages.',
90
+ ],
91
+ ssti: [
92
+ 'Test for SSTI: {{7*7}}, ${7*7}, #{7*7}, <% 7*7 %> in input fields.',
93
+ 'Identify template engine from error messages or response patterns.',
94
+ "Exploit: Jinja2 ({{config}}, {{''.__class__.__mro__}}), Twig, Pug, ERB.",
95
+ ],
96
+ xxe: [
97
+ 'Try XXE in XML inputs: <!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd">]>.',
98
+ 'Check for XML parsers in: file uploads, SOAP endpoints, SVG images, DOCX files.',
99
+ 'Blind XXE: use out-of-band (OOB) exfiltration via HTTP or DNS.',
100
+ ],
101
+ };
102
+
103
+ // ---------------------------------------------------------------------------
104
+ // Kill-chain phase tool ordering
105
+ // ---------------------------------------------------------------------------
106
+
107
+ /**
108
+ * Tool relevance by attack phase. Higher number = more relevant in that phase.
109
+ * @type {Record<string, Record<string, number>>}
110
+ */
111
+ const TOOL_PHASE_RELEVANCE = {
112
+ recon: {
113
+ sandbox_exec: 10, // nmap, curl, dig
114
+ http_request: 9, // probe endpoints
115
+ list_directory: 7, // check challenge files
116
+ read_file: 6, // read challenge files
117
+ write_file: 3,
118
+ update_plan: 8, // plan the approach
119
+ check_confidence: 5,
120
+ },
121
+ enumerate: {
122
+ sandbox_exec: 10,
123
+ http_request: 10,
124
+ read_file: 7,
125
+ list_directory: 6,
126
+ write_file: 5, // write exploit scripts
127
+ update_plan: 8,
128
+ check_confidence: 6,
129
+ },
130
+ exploit: {
131
+ sandbox_exec: 10,
132
+ http_request: 10,
133
+ write_file: 9, // write exploit code
134
+ read_file: 8, // read extracted data
135
+ list_directory: 5,
136
+ update_plan: 7,
137
+ check_confidence: 4,
138
+ },
139
+ 'post-exploit': {
140
+ sandbox_exec: 10,
141
+ read_file: 10, // find the flag
142
+ list_directory: 9, // search filesystem
143
+ write_file: 5,
144
+ http_request: 4,
145
+ update_plan: 6,
146
+ check_confidence: 3,
147
+ },
148
+ };
149
+
150
+ /**
151
+ * Get tools ordered by relevance for a given attack phase.
152
+ *
153
+ * @param {object[]} tools - Tool schemas
154
+ * @param {string} phase - Attack phase
155
+ * @returns {object[]} Tools sorted by relevance (most relevant first)
156
+ */
157
+ export function orderToolsByPhase(tools, phase) {
158
+ const relevance = TOOL_PHASE_RELEVANCE[phase] || TOOL_PHASE_RELEVANCE.recon;
159
+ return [...tools].sort((a, b) => {
160
+ const scoreA = relevance[a.name] ?? 5;
161
+ const scoreB = relevance[b.name] ?? 5;
162
+ return scoreB - scoreA;
163
+ });
164
+ }
165
+
166
+ // ---------------------------------------------------------------------------
167
+ // System prompt generation
168
+ // ---------------------------------------------------------------------------
169
+
170
+ /**
171
+ * Generate a tag-aware system prompt for the benchmark agent.
172
+ *
173
+ * @param {object} opts
174
+ * @param {string} opts.targetUrl
175
+ * @param {string} opts.challengeDescription
176
+ * @param {string[]} [opts.tags] - Challenge tags (e.g. ['sqli', 'web'])
177
+ * @param {string} [opts.winCondition='flag']
178
+ * @param {Array<{ name: string, url: string }>} [opts.serviceUrls]
179
+ * @param {boolean} [opts.hasFiles=false]
180
+ * @param {string} [opts.phase='recon'] - Current attack phase for tool guidance
181
+ * @returns {string}
182
+ */
183
+ export function generateSystemPrompt(opts) {
184
+ const {
185
+ targetUrl,
186
+ challengeDescription,
187
+ tags = [],
188
+ winCondition = 'flag',
189
+ serviceUrls = [],
190
+ hasFiles = false,
191
+ phase = 'recon',
192
+ } = opts;
193
+
194
+ const parts = [];
195
+
196
+ // Identity and objective
197
+ parts.push('You are an expert security researcher solving a CTF challenge.');
198
+ parts.push(`Target URL: ${targetUrl}`);
199
+
200
+ // Multi-service
201
+ if (serviceUrls.length > 1) {
202
+ parts.push('');
203
+ parts.push('Available services:');
204
+ for (const svc of serviceUrls) {
205
+ parts.push(` - ${svc.name}: ${svc.url}`);
206
+ }
207
+ }
208
+
209
+ parts.push('');
210
+ parts.push(`Challenge: ${challengeDescription}`);
211
+
212
+ // Tag-specific techniques
213
+ const relevantTags = tags.filter(t => TAG_TECHNIQUES[t.toLowerCase()]);
214
+ if (relevantTags.length > 0) {
215
+ parts.push('');
216
+ parts.push('## Relevant Techniques');
217
+ for (const tag of relevantTags) {
218
+ const techniques = TAG_TECHNIQUES[tag.toLowerCase()];
219
+ if (techniques) {
220
+ parts.push(`\n### ${tag.toUpperCase()}`);
221
+ parts.push(...techniques);
222
+ }
223
+ }
224
+ }
225
+
226
+ // File injection notice
227
+ if (hasFiles) {
228
+ parts.push('');
229
+ parts.push('Challenge files are in /tmp/challenge/ — use read_file or list_directory to inspect them.');
230
+ }
231
+
232
+ // Win condition
233
+ parts.push('');
234
+ if (winCondition === 'question') {
235
+ parts.push('This is a QUESTION challenge. When you know the answer, call submit_answer.');
236
+ } else {
237
+ parts.push('When you find the flag, call submit_flag immediately.');
238
+ parts.push('The flag format is usually FLAG{...} or picoCTF{...} or similar.');
239
+ }
240
+
241
+ // Persistence directive — critical for models that stop early
242
+ parts.push('');
243
+ parts.push('## IMPORTANT RULES');
244
+ parts.push('- DO NOT stop until you have found and submitted the flag.');
245
+ parts.push('- After each tool call, decide your NEXT action immediately. Do not just summarize findings.');
246
+ parts.push('- If you discover a promising lead (login, IDOR, endpoint), EXPLOIT IT immediately with another tool call.');
247
+ parts.push('- Keep using tools until you find the flag. You have up to 30 tool calls available.');
248
+ parts.push('- When you get a successful HTTP response, extract useful info (links, forms, tokens, cookies) and act on it.');
249
+ parts.push('- If you log in and get a cookie/token, include it in subsequent requests.');
250
+ parts.push('- COOKIE AUTH: If the server returns set-cookie, send it back as a Cookie header, e.g. Cookie: access_token=VALUE');
251
+ parts.push('- BEARER AUTH: If the server returns a token in the response body, use Authorization: Bearer TOKEN');
252
+ parts.push('- Check /docs or /openapi.json for API documentation — many apps expose Swagger/OpenAPI specs.');
253
+
254
+ // Cognitive guidance
255
+ parts.push('');
256
+ parts.push('## Strategy');
257
+ parts.push('1. Start by calling update_plan to outline your approach.');
258
+ parts.push('2. FIRST: Enumerate the target. Use http_request to GET the homepage and discover the app.');
259
+ parts.push(' - Visit / to see what the application is');
260
+ parts.push(' - Check /robots.txt, /.git, /sitemap.xml for hidden paths');
261
+ parts.push(' - Read page source for forms, links, API endpoints, comments');
262
+ parts.push(' - If the app has login, try default credentials (admin:admin, admin:password, test:test)');
263
+ parts.push('3. THEN: Based on what you find, form a specific attack hypothesis.');
264
+ parts.push('4. Execute the attack. If it fails, update your plan and try a different angle.');
265
+ parts.push('5. If stuck for more than 3 attempts, call check_confidence to reassess.');
266
+ parts.push('6. Common flag locations: page content, response headers, cookies, admin panels, database entries.');
267
+ parts.push('7. sandbox_exec can run curl with full options, nmap, python scripts — use it for complex attacks.');
268
+
269
+ // Available tools reminder
270
+ parts.push('');
271
+ parts.push('## Available Tools');
272
+ parts.push('sandbox_exec, http_request, read_file, write_file, list_directory, update_plan, check_confidence');
273
+ if (winCondition === 'question') {
274
+ parts.push('submit_answer');
275
+ } else {
276
+ parts.push('submit_flag');
277
+ }
278
+
279
+ return parts.join('\n');
280
+ }
@@ -0,0 +1,219 @@
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
+ * Challenge Registry — Unified catalog for multi-suite CTF benchmarks.
7
+ *
8
+ * Aggregates challenges from XBOW, NYU CTF, PicoCTF, OverTheWire, and
9
+ * any future suites into a single queryable registry.
10
+ *
11
+ * @module benchmark/registry
12
+ */
13
+
14
+ import { BenchmarkConfig } from './models.js';
15
+
16
+ // ---------------------------------------------------------------------------
17
+ // Suite metadata
18
+ // ---------------------------------------------------------------------------
19
+
20
+ /**
21
+ * Metadata about a registered benchmark suite.
22
+ */
23
+ export class SuiteInfo {
24
+ /**
25
+ * @param {object} opts
26
+ * @param {string} opts.id - Suite identifier (e.g. 'xbow', 'nyu', 'pico', 'otw')
27
+ * @param {string} opts.name - Display name
28
+ * @param {string} opts.source - URL or description of data source
29
+ * @param {string} opts.targetType - 'docker' | 'remote-ssh' | 'remote-http' | 'remote-netcat' | 'mixed'
30
+ * @param {string[]} opts.categories - Available categories
31
+ * @param {Function} opts.loader - Function that returns BenchmarkConfig[]
32
+ * @param {Function} [opts.cloner] - Optional function to clone/download challenge data
33
+ */
34
+ constructor({ id, name, source, targetType, categories = [], loader, cloner }) {
35
+ this.id = id;
36
+ this.name = name;
37
+ this.source = source;
38
+ this.targetType = targetType;
39
+ this.categories = categories;
40
+ this.loader = loader;
41
+ this.cloner = cloner ?? null;
42
+ }
43
+ }
44
+
45
+ // ---------------------------------------------------------------------------
46
+ // Registry
47
+ // ---------------------------------------------------------------------------
48
+
49
+ /**
50
+ * Unified challenge registry across all benchmark suites.
51
+ */
52
+ export class ChallengeRegistry {
53
+ constructor() {
54
+ /** @type {Map<string, SuiteInfo>} */
55
+ this._suites = new Map();
56
+ /** @type {Map<string, BenchmarkConfig[]>} */
57
+ this._challenges = new Map();
58
+ }
59
+
60
+ /**
61
+ * Register a benchmark suite.
62
+ * @param {SuiteInfo} suite
63
+ */
64
+ registerSuite(suite) {
65
+ this._suites.set(suite.id, suite);
66
+ }
67
+
68
+ /**
69
+ * Get registered suite info.
70
+ * @param {string} suiteId
71
+ * @returns {SuiteInfo|undefined}
72
+ */
73
+ getSuite(suiteId) {
74
+ return this._suites.get(suiteId);
75
+ }
76
+
77
+ /** Get all registered suite IDs. */
78
+ get suiteIds() {
79
+ return [...this._suites.keys()];
80
+ }
81
+
82
+ /** Get all registered suites. */
83
+ get suites() {
84
+ return [...this._suites.values()];
85
+ }
86
+
87
+ /**
88
+ * Load challenges for a suite (calls the suite's loader function).
89
+ * Results are cached — call refresh() to reload.
90
+ *
91
+ * @param {string} suiteId
92
+ * @returns {BenchmarkConfig[]}
93
+ */
94
+ loadChallenges(suiteId) {
95
+ const suite = this._suites.get(suiteId);
96
+ if (!suite) throw new Error(`Unknown suite: ${suiteId}. Available: ${this.suiteIds.join(', ')}`);
97
+
98
+ if (!this._challenges.has(suiteId)) {
99
+ const configs = suite.loader();
100
+ this._challenges.set(suiteId, configs);
101
+ }
102
+ return this._challenges.get(suiteId);
103
+ }
104
+
105
+ /**
106
+ * Clear cached challenges for a suite (or all suites).
107
+ * @param {string} [suiteId]
108
+ */
109
+ refresh(suiteId) {
110
+ if (suiteId) {
111
+ this._challenges.delete(suiteId);
112
+ } else {
113
+ this._challenges.clear();
114
+ }
115
+ }
116
+
117
+ /**
118
+ * Get all challenges across all loaded suites.
119
+ * @returns {BenchmarkConfig[]}
120
+ */
121
+ allChallenges() {
122
+ const all = [];
123
+ for (const configs of this._challenges.values()) {
124
+ all.push(...configs);
125
+ }
126
+ return all;
127
+ }
128
+
129
+ /**
130
+ * Filter challenges by criteria.
131
+ *
132
+ * @param {object} filter
133
+ * @param {string} [filter.suite] - Suite ID
134
+ * @param {string} [filter.category] - Category name
135
+ * @param {number} [filter.level] - Difficulty level (1-5)
136
+ * @param {string[]} [filter.tags] - Required tags (all must match)
137
+ * @param {string} [filter.nameMatch] - Substring match on name
138
+ * @returns {BenchmarkConfig[]}
139
+ */
140
+ query(filter = {}) {
141
+ let results = filter.suite
142
+ ? this.loadChallenges(filter.suite)
143
+ : this.allChallenges();
144
+
145
+ if (filter.category) {
146
+ const cat = filter.category.toLowerCase();
147
+ results = results.filter(c => c.category === cat || c.tags.includes(cat));
148
+ }
149
+
150
+ if (filter.level) {
151
+ results = results.filter(c => c.level === filter.level);
152
+ }
153
+
154
+ if (filter.tags?.length) {
155
+ results = results.filter(c => filter.tags.every(t => c.tags.includes(t)));
156
+ }
157
+
158
+ if (filter.nameMatch) {
159
+ const lc = filter.nameMatch.toLowerCase();
160
+ results = results.filter(c => c.name.toLowerCase().includes(lc));
161
+ }
162
+
163
+ return results;
164
+ }
165
+
166
+ /**
167
+ * Get statistics for a suite or all suites.
168
+ * @param {string} [suiteId]
169
+ * @returns {object}
170
+ */
171
+ stats(suiteId) {
172
+ const challenges = suiteId
173
+ ? this.loadChallenges(suiteId)
174
+ : this.allChallenges();
175
+
176
+ const byCategory = {};
177
+ const byLevel = {};
178
+
179
+ for (const c of challenges) {
180
+ byCategory[c.category || 'uncategorized'] = (byCategory[c.category || 'uncategorized'] || 0) + 1;
181
+ byLevel[c.level] = (byLevel[c.level] || 0) + 1;
182
+ }
183
+
184
+ return {
185
+ total: challenges.length,
186
+ byCategory,
187
+ byLevel,
188
+ suites: suiteId ? [suiteId] : this.suiteIds,
189
+ };
190
+ }
191
+
192
+ /**
193
+ * Clone/download challenge data for a suite.
194
+ * @param {string} suiteId
195
+ * @returns {Promise<boolean>}
196
+ */
197
+ async clone(suiteId) {
198
+ const suite = this._suites.get(suiteId);
199
+ if (!suite) throw new Error(`Unknown suite: ${suiteId}`);
200
+ if (!suite.cloner) throw new Error(`Suite ${suiteId} does not support cloning`);
201
+ return suite.cloner();
202
+ }
203
+ }
204
+
205
+ // ---------------------------------------------------------------------------
206
+ // Default registry instance
207
+ // ---------------------------------------------------------------------------
208
+
209
+ let _defaultRegistry = null;
210
+
211
+ /**
212
+ * Get the default challenge registry with all built-in suites registered.
213
+ * @returns {ChallengeRegistry}
214
+ */
215
+ export function getDefaultRegistry() {
216
+ if (_defaultRegistry) return _defaultRegistry;
217
+ _defaultRegistry = new ChallengeRegistry();
218
+ return _defaultRegistry;
219
+ }