cipher-security 2.0.8 → 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 (70) hide show
  1. package/bin/cipher.js +11 -1
  2. package/lib/agent-runtime/handlers/architect.js +199 -0
  3. package/lib/agent-runtime/handlers/base.js +240 -0
  4. package/lib/agent-runtime/handlers/blue.js +220 -0
  5. package/lib/agent-runtime/handlers/incident.js +161 -0
  6. package/lib/agent-runtime/handlers/privacy.js +190 -0
  7. package/lib/agent-runtime/handlers/purple.js +209 -0
  8. package/lib/agent-runtime/handlers/recon.js +174 -0
  9. package/lib/agent-runtime/handlers/red.js +246 -0
  10. package/lib/agent-runtime/handlers/researcher.js +170 -0
  11. package/lib/agent-runtime/handlers.js +35 -0
  12. package/lib/agent-runtime/index.js +196 -0
  13. package/lib/agent-runtime/parser.js +316 -0
  14. package/lib/analyze/consistency.js +566 -0
  15. package/lib/analyze/constitution.js +110 -0
  16. package/lib/analyze/sharding.js +251 -0
  17. package/lib/autonomous/agent-tool.js +165 -0
  18. package/lib/autonomous/feedback-loop.js +13 -6
  19. package/lib/autonomous/framework.js +17 -0
  20. package/lib/autonomous/handoff.js +506 -0
  21. package/lib/autonomous/modes/blue.js +26 -0
  22. package/lib/autonomous/modes/red.js +585 -0
  23. package/lib/autonomous/modes/researcher.js +322 -0
  24. package/lib/autonomous/researcher.js +12 -45
  25. package/lib/autonomous/runner.js +9 -537
  26. package/lib/benchmark/agent.js +88 -26
  27. package/lib/benchmark/baselines.js +3 -0
  28. package/lib/benchmark/claude-code-solver.js +254 -0
  29. package/lib/benchmark/cognitive.js +283 -0
  30. package/lib/benchmark/index.js +12 -2
  31. package/lib/benchmark/knowledge.js +281 -0
  32. package/lib/benchmark/llm.js +156 -15
  33. package/lib/benchmark/models.js +5 -2
  34. package/lib/benchmark/nyu-ctf.js +192 -0
  35. package/lib/benchmark/overthewire.js +347 -0
  36. package/lib/benchmark/picoctf.js +281 -0
  37. package/lib/benchmark/prompts.js +280 -0
  38. package/lib/benchmark/registry.js +219 -0
  39. package/lib/benchmark/remote-solver.js +356 -0
  40. package/lib/benchmark/remote-target.js +263 -0
  41. package/lib/benchmark/reporter.js +35 -0
  42. package/lib/benchmark/runner.js +174 -10
  43. package/lib/benchmark/sandbox.js +35 -0
  44. package/lib/benchmark/scorer.js +22 -4
  45. package/lib/benchmark/solver.js +34 -1
  46. package/lib/benchmark/tools.js +262 -16
  47. package/lib/commands.js +9 -0
  48. package/lib/execution/council.js +434 -0
  49. package/lib/execution/parallel.js +292 -0
  50. package/lib/gates/circuit-breaker.js +135 -0
  51. package/lib/gates/confidence.js +302 -0
  52. package/lib/gates/corrections.js +219 -0
  53. package/lib/gates/self-check.js +245 -0
  54. package/lib/gateway/commands.js +727 -0
  55. package/lib/guardrails/engine.js +364 -0
  56. package/lib/mcp/server.js +349 -3
  57. package/lib/memory/compressor.js +94 -7
  58. package/lib/pipeline/hooks.js +288 -0
  59. package/lib/pipeline/index.js +11 -0
  60. package/lib/review/budget.js +210 -0
  61. package/lib/review/engine.js +526 -0
  62. package/lib/review/layers/acceptance-auditor.js +279 -0
  63. package/lib/review/layers/blind-hunter.js +500 -0
  64. package/lib/review/layers/defense-in-depth.js +209 -0
  65. package/lib/review/layers/edge-case-hunter.js +266 -0
  66. package/lib/review/panel.js +519 -0
  67. package/lib/review/two-stage.js +244 -0
  68. package/lib/session/cost-tracker.js +203 -0
  69. package/lib/session/logger.js +349 -0
  70. package/package.json +1 -1
@@ -0,0 +1,209 @@
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
+ * Defense-in-Depth Validation Layer
7
+ *
8
+ * Detects functions/endpoints relying on single-layer security validation.
9
+ * Flags code that has one security control where multiple are expected.
10
+ *
11
+ * Examples:
12
+ * - Auth check without input validation
13
+ * - SQL parameterization without output encoding
14
+ * - File upload without type AND size validation
15
+ * - API endpoint without both auth AND rate limiting
16
+ *
17
+ * @module review/layers/defense-in-depth
18
+ */
19
+
20
+ import { ReviewFinding, Severity } from '../engine.js';
21
+
22
+ // ---------------------------------------------------------------------------
23
+ // Defense-in-depth patterns
24
+ // ---------------------------------------------------------------------------
25
+
26
+ /**
27
+ * Each pattern looks for the ABSENCE of a companion control.
28
+ * Structure: {presence, absence, ...} — if presence matches but absence doesn't,
29
+ * it's a defense-in-depth gap.
30
+ *
31
+ * @typedef {object} DepthPattern
32
+ * @property {string} id
33
+ * @property {string} title
34
+ * @property {RegExp} presence - Pattern that MUST be present (the single control)
35
+ * @property {RegExp} absence - Companion control that SHOULD be present nearby
36
+ * @property {number} searchRadius - Lines around the presence match to search for absence
37
+ * @property {string} severity
38
+ * @property {string[]} cweIds
39
+ * @property {string} description
40
+ * @property {string} remediation
41
+ * @property {string[]} languages
42
+ * @property {string} presentLayer
43
+ * @property {string} missingLayer
44
+ */
45
+
46
+ /** @type {DepthPattern[]} */
47
+ const DEPTH_PATTERNS = [
48
+ {
49
+ id: 'DID-001',
50
+ title: 'Auth without input validation',
51
+ presence: /(?:isAuthenticated|requireAuth|auth|authenticate|verifyToken|passport\.authenticate)\s*[\(,)]/gi,
52
+ absence: /(?:validate|schema|joi|zod|ajv|express-validator|celebrate|sanitize|check\(|body\()/i,
53
+ searchRadius: 15,
54
+ severity: Severity.MEDIUM,
55
+ cweIds: ['CWE-20'],
56
+ description: 'Authentication is present but input validation is missing. Authenticated users can still send malicious input.',
57
+ remediation: 'Add input validation (Joi, Zod, express-validator) alongside authentication middleware.',
58
+ languages: ['javascript', 'typescript'],
59
+ presentLayer: 'authentication',
60
+ missingLayer: 'input validation',
61
+ },
62
+ {
63
+ id: 'DID-002',
64
+ title: 'Input validation without output encoding',
65
+ presence: /(?:validate|schema|joi|zod|ajv|express-validator|celebrate|sanitize)/gi,
66
+ absence: /(?:escape|encode|htmlEntities|DOMPurify|sanitizeHtml|textContent|createTextNode)/i,
67
+ searchRadius: 20,
68
+ severity: Severity.LOW,
69
+ cweIds: ['CWE-116'],
70
+ description: 'Input validation present but output encoding is missing. Validation alone does not prevent stored XSS.',
71
+ remediation: 'Add output encoding/escaping when rendering user-supplied data.',
72
+ languages: ['javascript', 'typescript'],
73
+ presentLayer: 'input validation',
74
+ missingLayer: 'output encoding',
75
+ },
76
+ {
77
+ id: 'DID-003',
78
+ title: 'File upload without type AND size validation',
79
+ presence: /(?:multer|upload|formidable|busboy|multipart)/gi,
80
+ absence: /(?:fileFilter|limits|maxFileSize|allowedTypes|mimetype|content-type.*check|fileSize)/i,
81
+ searchRadius: 10,
82
+ severity: Severity.MEDIUM,
83
+ cweIds: ['CWE-434'],
84
+ description: 'File upload handler present but missing file type or size validation. Unrestricted upload enables web shell deployment.',
85
+ remediation: 'Add file type whitelist (by extension AND MIME type) and maximum file size limit.',
86
+ languages: ['javascript', 'typescript', 'python'],
87
+ presentLayer: 'file upload handling',
88
+ missingLayer: 'file type/size validation',
89
+ },
90
+ {
91
+ id: 'DID-004',
92
+ title: 'API endpoint without rate limiting',
93
+ presence: /(?:app|router)\.(?:post|put|patch|delete)\s*\(/gi,
94
+ absence: /(?:rateLimit|rateLimiter|limiter|throttle|slowDown|express-rate-limit)/i,
95
+ searchRadius: 5,
96
+ severity: Severity.LOW,
97
+ cweIds: ['CWE-770'],
98
+ description: 'Write endpoint without rate limiting. Enables abuse, resource exhaustion, and brute-force attacks.',
99
+ remediation: 'Add rate limiting middleware to mutation endpoints.',
100
+ languages: ['javascript', 'typescript'],
101
+ presentLayer: 'endpoint handler',
102
+ missingLayer: 'rate limiting',
103
+ },
104
+ {
105
+ id: 'DID-005',
106
+ title: 'Database operation without audit logging',
107
+ presence: /(?:\.(?:update|delete|remove|destroy|create|insert|save))\s*\(/gi,
108
+ absence: /(?:audit|log\.(?:info|warn)|logger|track|event|record|history)/i,
109
+ searchRadius: 8,
110
+ severity: Severity.LOW,
111
+ cweIds: ['CWE-778'],
112
+ description: 'Database mutation without audit logging. Changes cannot be traced to actors during incident response.',
113
+ remediation: 'Add audit logging capturing who, what, when for all data mutations.',
114
+ languages: ['javascript', 'typescript', 'python'],
115
+ presentLayer: 'data mutation',
116
+ missingLayer: 'audit logging',
117
+ },
118
+ {
119
+ id: 'DID-006',
120
+ title: 'Crypto operation without key management',
121
+ presence: /(?:createCipher|createSign|createHmac|encrypt|decrypt)\s*\(/gi,
122
+ absence: /(?:keyRotat|kms|vault|secretManager|keyManag|KEY_VERSION|rotateKey|getKey\()/i,
123
+ searchRadius: 20,
124
+ severity: Severity.MEDIUM,
125
+ cweIds: ['CWE-320'],
126
+ description: 'Cryptographic operation without key management/rotation mechanism. Static keys increase exposure window.',
127
+ remediation: 'Implement key rotation policy. Use a KMS, Vault, or at minimum versioned key references.',
128
+ languages: ['javascript', 'typescript', 'python'],
129
+ presentLayer: 'cryptographic operation',
130
+ missingLayer: 'key management/rotation',
131
+ },
132
+ {
133
+ id: 'DID-007',
134
+ title: 'Session management without CSRF protection',
135
+ presence: /(?:session|cookie|express-session|cookie-session)\s*[\(\{]/gi,
136
+ absence: /(?:csrf|csurf|csrfToken|_csrf|xsrf|sameSite.*(?:strict|lax))/i,
137
+ searchRadius: 15,
138
+ severity: Severity.MEDIUM,
139
+ cweIds: ['CWE-352'],
140
+ description: 'Session management without CSRF protection. Session-based auth is vulnerable to cross-site request forgery.',
141
+ remediation: 'Add CSRF token validation or set SameSite cookie attribute to Strict/Lax.',
142
+ languages: ['javascript', 'typescript'],
143
+ presentLayer: 'session management',
144
+ missingLayer: 'CSRF protection',
145
+ },
146
+ ];
147
+
148
+ // ---------------------------------------------------------------------------
149
+ // Defense-in-Depth review function
150
+ // ---------------------------------------------------------------------------
151
+
152
+ /**
153
+ * Run defense-in-depth validation against source files.
154
+ *
155
+ * @param {import('../engine.js').SourceFile[]} sources
156
+ * @param {object} [options]
157
+ * @returns {Promise<ReviewFinding[]>}
158
+ */
159
+ export async function defenseInDepthReview(sources, options = {}) {
160
+ const findings = [];
161
+
162
+ for (const source of sources) {
163
+ const lines = source.content.split('\n');
164
+
165
+ for (const pat of DEPTH_PATTERNS) {
166
+ if (!pat.languages.includes('*') && !pat.languages.includes(source.language)) {
167
+ continue;
168
+ }
169
+
170
+ for (let i = 0; i < lines.length; i++) {
171
+ const line = lines[i];
172
+ const trimmed = line.trimStart();
173
+ if (trimmed.startsWith('//') || trimmed.startsWith('#') || trimmed.startsWith('*')) continue;
174
+
175
+ pat.presence.lastIndex = 0;
176
+ const match = pat.presence.exec(line);
177
+ if (!match) continue;
178
+
179
+ // Search surrounding lines for the companion control
180
+ const start = Math.max(0, i - pat.searchRadius);
181
+ const end = Math.min(lines.length, i + pat.searchRadius + 1);
182
+ const context = lines.slice(start, end).join('\n');
183
+
184
+ pat.absence.lastIndex = 0;
185
+ if (pat.absence.test(context)) continue; // Companion found — no gap
186
+
187
+ findings.push(
188
+ new ReviewFinding({
189
+ title: pat.title,
190
+ severity: pat.severity,
191
+ layer: 'defense-in-depth',
192
+ file: source.path,
193
+ line: i + 1,
194
+ column: match.index + 1,
195
+ description: `${pat.description}\n Present: ${pat.presentLayer}\n Missing: ${pat.missingLayer}`,
196
+ proof: line.trim().slice(0, 200),
197
+ remediation: pat.remediation,
198
+ cweIds: [...pat.cweIds],
199
+ tags: ['defense-in-depth'],
200
+ language: source.language,
201
+ meta: { patternId: pat.id, presentLayer: pat.presentLayer, missingLayer: pat.missingLayer },
202
+ }),
203
+ );
204
+ }
205
+ }
206
+ }
207
+
208
+ return findings;
209
+ }
@@ -0,0 +1,266 @@
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
+ * Edge Case Hunter — Boundary condition and failure mode analysis layer.
7
+ *
8
+ * Focuses on code that handles (or fails to handle) edge cases:
9
+ * missing validation, unchecked returns, resource leaks, race conditions,
10
+ * error swallowing, and unhandled promise rejections.
11
+ *
12
+ * @module review/layers/edge-case-hunter
13
+ */
14
+
15
+ import { ReviewFinding, Severity } from '../engine.js';
16
+
17
+ // ---------------------------------------------------------------------------
18
+ // Pattern definitions
19
+ // ---------------------------------------------------------------------------
20
+
21
+ /** @type {import('./blind-hunter.js').VulnPattern[]} */
22
+ const PATTERNS = [
23
+ // ── Error Handling ────────────────────────────────────────────────────
24
+ {
25
+ id: 'EC-ERR-001',
26
+ title: 'Empty catch block — error swallowed',
27
+ pattern: /catch\s*\([^)]*\)\s*\{\s*\}/g,
28
+ severity: Severity.MEDIUM,
29
+ cweIds: ['CWE-390'],
30
+ description: 'Empty catch block silently swallows errors, hiding failures and making debugging impossible.',
31
+ remediation: 'Log the error, rethrow it, or handle it explicitly. At minimum: console.error(err).',
32
+ languages: ['javascript', 'typescript', 'java', 'csharp'],
33
+ tags: ['reliability'],
34
+ },
35
+ {
36
+ id: 'EC-ERR-002',
37
+ title: 'Catch-all with no logging',
38
+ pattern: /catch\s*\(\s*(?:_|e|err|error|ex)\s*\)\s*\{\s*(?:\/\/|return|next\(|res\.)/g,
39
+ severity: Severity.LOW,
40
+ cweIds: ['CWE-390'],
41
+ description: 'Catch block may not log the error before returning or continuing.',
42
+ remediation: 'Ensure errors are logged before being handled or suppressed.',
43
+ languages: ['javascript', 'typescript'],
44
+ tags: ['reliability', 'observability'],
45
+ },
46
+ {
47
+ id: 'EC-ERR-003',
48
+ title: 'Unhandled promise — missing .catch() or try/catch',
49
+ pattern: /(?:(?:new\s+Promise|\.then)\s*\([^)]*\))\s*(?:;|\n)(?!\s*\.catch)/g,
50
+ severity: Severity.MEDIUM,
51
+ cweIds: ['CWE-755'],
52
+ description: 'Promise chain without .catch() handler. Unhandled rejections crash Node.js.',
53
+ remediation: 'Add .catch() handler or use async/await with try/catch.',
54
+ languages: ['javascript', 'typescript'],
55
+ tags: ['reliability'],
56
+ },
57
+ {
58
+ id: 'EC-ERR-004',
59
+ title: 'Generic exception catch — bare except',
60
+ pattern: /except\s*:/g,
61
+ severity: Severity.MEDIUM,
62
+ cweIds: ['CWE-396'],
63
+ description: 'Bare except catches SystemExit, KeyboardInterrupt, and all other exceptions.',
64
+ remediation: 'Catch specific exception types: except ValueError, except IOError.',
65
+ languages: ['python'],
66
+ tags: ['reliability'],
67
+ },
68
+
69
+ // ── Input Validation ──────────────────────────────────────────────────
70
+ {
71
+ id: 'EC-VAL-001',
72
+ title: 'Missing type check — direct property access on parameter',
73
+ pattern: /(?:function|=>)\s*(?:\([^)]*\)|[a-zA-Z_$]+)\s*(?:=>|{)\s*\n\s*(?:return\s+)?(?:(?!if|typeof|instanceof|assert|\?\.))[a-zA-Z_$]+\.[a-zA-Z_$]+/g,
74
+ severity: Severity.LOW,
75
+ cweIds: ['CWE-20'],
76
+ description: 'Property accessed on parameter without type/null check. May throw on undefined/null.',
77
+ remediation: 'Add typeof/instanceof check, use optional chaining (?.), or validate input.',
78
+ languages: ['javascript', 'typescript'],
79
+ tags: ['reliability'],
80
+ },
81
+ {
82
+ id: 'EC-VAL-002',
83
+ title: 'Missing array bounds check',
84
+ pattern: /\[\s*(?:index|idx|i|pos|offset|n)\s*\]/g,
85
+ severity: Severity.LOW,
86
+ cweIds: ['CWE-129'],
87
+ description: 'Array accessed with variable index without bounds checking.',
88
+ remediation: 'Verify index is within [0, array.length) before access.',
89
+ languages: ['javascript', 'typescript', 'python', 'java', 'c', 'cpp'],
90
+ tags: ['reliability'],
91
+ exclude: /for\s*\(|\.forEach|\.map|\.filter|\.reduce|\.length/,
92
+ },
93
+ {
94
+ id: 'EC-VAL-003',
95
+ title: 'parseInt without radix',
96
+ pattern: /parseInt\s*\(\s*[^,)]+\s*\)/g,
97
+ severity: Severity.LOW,
98
+ cweIds: ['CWE-704'],
99
+ description: 'parseInt() without radix parameter. Strings starting with "0" may be parsed as octal.',
100
+ remediation: 'Always specify radix: parseInt(value, 10).',
101
+ languages: ['javascript', 'typescript'],
102
+ tags: ['reliability'],
103
+ exclude: /,\s*\d+\s*\)/,
104
+ },
105
+
106
+ // ── Resource Management ───────────────────────────────────────────────
107
+ {
108
+ id: 'EC-RES-001',
109
+ title: 'Resource leak — stream/handle opened without close',
110
+ pattern: /(?:createReadStream|createWriteStream|open|connect|createConnection)\s*\(/g,
111
+ severity: Severity.MEDIUM,
112
+ cweIds: ['CWE-404'],
113
+ description: 'Resource opened but may not be properly closed on error paths.',
114
+ remediation: 'Use try/finally or pipeline() to ensure cleanup. Consider using "using" declarations.',
115
+ languages: ['javascript', 'typescript'],
116
+ tags: ['reliability', 'performance'],
117
+ exclude: /\.pipe\(|pipeline\(|\.on\s*\(\s*['"]close|\.on\s*\(\s*['"]end|finally/,
118
+ },
119
+ {
120
+ id: 'EC-RES-002',
121
+ title: 'Resource leak — database connection without release',
122
+ pattern: /(?:pool\.query|pool\.connect|getConnection)\s*\(/g,
123
+ severity: Severity.MEDIUM,
124
+ cweIds: ['CWE-404'],
125
+ description: 'Database connection acquired but may not be released on error paths.',
126
+ remediation: 'Use try/finally to release connections, or use pool.query() which auto-releases.',
127
+ languages: ['javascript', 'typescript', 'python', 'java'],
128
+ tags: ['reliability', 'performance'],
129
+ exclude: /finally|\.release|\.end|\.close|using/,
130
+ },
131
+ {
132
+ id: 'EC-RES-003',
133
+ title: 'Event listener leak — addEventListener without removal',
134
+ pattern: /addEventListener\s*\(\s*['"][^'"]+['"]/g,
135
+ severity: Severity.LOW,
136
+ cweIds: ['CWE-401'],
137
+ description: 'Event listener added but may not be removed, causing memory leaks.',
138
+ remediation: 'Store reference and call removeEventListener in cleanup/destroy/unmount.',
139
+ languages: ['javascript', 'typescript'],
140
+ tags: ['performance', 'reliability'],
141
+ exclude: /removeEventListener|AbortController|signal|once\s*:/,
142
+ },
143
+ {
144
+ id: 'EC-RES-004',
145
+ title: 'Timer leak — setInterval without clearInterval',
146
+ pattern: /setInterval\s*\(/g,
147
+ severity: Severity.LOW,
148
+ cweIds: ['CWE-401'],
149
+ description: 'setInterval() without corresponding clearInterval() can leak timers.',
150
+ remediation: 'Store the interval ID and call clearInterval() in cleanup logic.',
151
+ languages: ['javascript', 'typescript'],
152
+ tags: ['performance', 'reliability'],
153
+ exclude: /clearInterval|intervalId|interval_id|timer/i,
154
+ },
155
+
156
+ // ── Race Conditions / Concurrency ─────────────────────────────────────
157
+ {
158
+ id: 'EC-RACE-001',
159
+ title: 'TOCTOU race — check-then-act on filesystem',
160
+ pattern: /(?:existsSync|exists|access)\s*\([^)]+\)[\s\S]{0,50}(?:readFile|writeFile|unlink|mkdir|rmSync)/g,
161
+ severity: Severity.MEDIUM,
162
+ cweIds: ['CWE-367'],
163
+ description: 'Time-of-check to time-of-use race: file existence check followed by file operation.',
164
+ remediation: 'Use atomic operations. Open the file directly and handle ENOENT/EEXIST errors.',
165
+ languages: ['javascript', 'typescript', 'python'],
166
+ tags: ['reliability', 'T1574'],
167
+ },
168
+ {
169
+ id: 'EC-RACE-002',
170
+ title: 'Shared state mutation without synchronization',
171
+ pattern: /(?:let|var)\s+\w+\s*=\s*(?:0|''|""|\[\]|\{\}|null|false)[\s\S]{0,200}(?:async|Promise|setTimeout|setInterval)/g,
172
+ severity: Severity.LOW,
173
+ cweIds: ['CWE-362'],
174
+ description: 'Mutable variable in outer scope accessed from async contexts without synchronization.',
175
+ remediation: 'Use atomic operations, mutex/lock patterns, or restructure to avoid shared mutable state.',
176
+ languages: ['javascript', 'typescript'],
177
+ tags: ['reliability'],
178
+ },
179
+
180
+ // ── Null/Undefined Safety ─────────────────────────────────────────────
181
+ {
182
+ id: 'EC-NULL-001',
183
+ title: 'Potential null dereference — .length without guard',
184
+ pattern: /(?:params|query|body|headers|result|data|response|items|rows|records)\s*\.\s*length/g,
185
+ severity: Severity.LOW,
186
+ cweIds: ['CWE-476'],
187
+ description: 'Accessing .length on a value that may be null or undefined.',
188
+ remediation: 'Add null check: if (value && value.length) or use optional chaining: value?.length.',
189
+ languages: ['javascript', 'typescript'],
190
+ tags: ['reliability'],
191
+ exclude: /\?\.|if\s*\(|&&|typeof|!= null|!== null|!= undefined|!== undefined/,
192
+ },
193
+
194
+ // ── Dangerous Defaults ────────────────────────────────────────────────
195
+ {
196
+ id: 'EC-DEF-001',
197
+ title: 'Mutable default argument',
198
+ pattern: /def\s+\w+\s*\([^)]*(?:=\s*\[\]|=\s*\{\}|=\s*set\(\))/g,
199
+ severity: Severity.MEDIUM,
200
+ cweIds: ['CWE-665'],
201
+ description: 'Mutable default argument in Python is shared across calls, causing unexpected behavior.',
202
+ remediation: 'Use None as default and create the mutable object inside the function body.',
203
+ languages: ['python'],
204
+ tags: ['reliability'],
205
+ },
206
+ ];
207
+
208
+ // ---------------------------------------------------------------------------
209
+ // Edge Case Hunter review function
210
+ // ---------------------------------------------------------------------------
211
+
212
+ /**
213
+ * Run Edge Case Hunter analysis against source files.
214
+ *
215
+ * @param {import('../engine.js').SourceFile[]} sources
216
+ * @param {object} [options]
217
+ * @returns {Promise<ReviewFinding[]>}
218
+ */
219
+ export async function edgeCaseReview(sources, options = {}) {
220
+ const findings = [];
221
+
222
+ for (const source of sources) {
223
+ const lines = source.content.split('\n');
224
+
225
+ for (const pat of PATTERNS) {
226
+ if (!pat.languages.includes('*') && !pat.languages.includes(source.language)) {
227
+ continue;
228
+ }
229
+
230
+ for (let i = 0; i < lines.length; i++) {
231
+ const line = lines[i];
232
+ const trimmed = line.trimStart();
233
+ if (trimmed.startsWith('//') || trimmed.startsWith('#') || trimmed.startsWith('*')) continue;
234
+
235
+ pat.pattern.lastIndex = 0;
236
+ const match = pat.pattern.exec(line);
237
+ if (!match) continue;
238
+
239
+ if (pat.exclude) {
240
+ pat.exclude.lastIndex = 0;
241
+ if (pat.exclude.test(line)) continue;
242
+ }
243
+
244
+ findings.push(
245
+ new ReviewFinding({
246
+ title: pat.title,
247
+ severity: pat.severity,
248
+ layer: 'edge-case-hunter',
249
+ file: source.path,
250
+ line: i + 1,
251
+ column: match.index + 1,
252
+ description: pat.description,
253
+ proof: line.trim().slice(0, 200),
254
+ remediation: pat.remediation,
255
+ cweIds: [...pat.cweIds],
256
+ tags: pat.tags ? [...pat.tags] : [],
257
+ language: source.language,
258
+ meta: { patternId: pat.id },
259
+ }),
260
+ );
261
+ }
262
+ }
263
+ }
264
+
265
+ return findings;
266
+ }