cipher-security 2.0.4 → 2.0.5
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.
- package/bin/cipher.js +113 -18
- package/lib/commands.js +1 -3
- package/lib/gateway/commands.js +125 -50
- package/lib/gateway/index.js +0 -2
- package/lib/mcp/server.js +241 -14
- package/lib/pipeline/index.js +3 -1
- package/lib/pipeline/osint.js +488 -239
- package/lib/pipeline/scanner.js +67 -3
- package/package.json +1 -1
package/lib/mcp/server.js
CHANGED
|
@@ -10,9 +10,10 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import { createInterface } from 'node:readline';
|
|
13
|
-
import { readFileSync } from 'node:fs';
|
|
14
|
-
import { join, dirname } from 'node:path';
|
|
13
|
+
import { readFileSync, existsSync, readdirSync, statSync } from 'node:fs';
|
|
14
|
+
import { join, dirname, resolve } from 'node:path';
|
|
15
15
|
import { fileURLToPath } from 'node:url';
|
|
16
|
+
import { homedir } from 'node:os';
|
|
16
17
|
|
|
17
18
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
18
19
|
const _version = (() => {
|
|
@@ -21,6 +22,38 @@ const _version = (() => {
|
|
|
21
22
|
} catch { return 'unknown'; }
|
|
22
23
|
})();
|
|
23
24
|
|
|
25
|
+
/** Default memory directory — mirrors gateway/commands.js */
|
|
26
|
+
function defaultMemoryDir() {
|
|
27
|
+
return join(homedir(), '.cipher', 'memory', 'default');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Find repo root for skills directory */
|
|
31
|
+
function findRepoRoot() {
|
|
32
|
+
let dir = resolve(__dirname, '..', '..');
|
|
33
|
+
for (let i = 0; i < 10; i++) {
|
|
34
|
+
if (existsSync(join(dir, 'skills')) && (existsSync(join(dir, 'cli')) || existsSync(join(dir, 'package.json')))) {
|
|
35
|
+
return dir;
|
|
36
|
+
}
|
|
37
|
+
const parent = dirname(dir);
|
|
38
|
+
if (parent === dir) break;
|
|
39
|
+
dir = parent;
|
|
40
|
+
}
|
|
41
|
+
return resolve(__dirname, '..', '..');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function skillsDir() {
|
|
45
|
+
return join(findRepoRoot(), 'skills');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** MCP content block helper */
|
|
49
|
+
function text(obj) {
|
|
50
|
+
return { content: [{ type: 'text', text: JSON.stringify(obj) }] };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function mcpError(msg) {
|
|
54
|
+
return { content: [{ type: 'text', text: JSON.stringify({ error: msg }) }], isError: true };
|
|
55
|
+
}
|
|
56
|
+
|
|
24
57
|
/**
|
|
25
58
|
* MCP tool definitions — schema registry for all 14 tools.
|
|
26
59
|
*/
|
|
@@ -182,21 +215,215 @@ export class CipherMCPServer {
|
|
|
182
215
|
async _dispatchTool(toolName, params) {
|
|
183
216
|
try {
|
|
184
217
|
switch (toolName) {
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
218
|
+
// ── Memory tools ────────────────────────────────────────────
|
|
219
|
+
case 'cipher_store': {
|
|
220
|
+
const { CipherMemory, MemoryEntry, MemoryType } = await import('../memory/index.js');
|
|
221
|
+
const memory = new CipherMemory(defaultMemoryDir());
|
|
222
|
+
try {
|
|
223
|
+
const entry = new MemoryEntry({
|
|
224
|
+
content: params.content,
|
|
225
|
+
memoryType: Object.values(MemoryType).includes(params.memory_type) ? params.memory_type : MemoryType.NOTE,
|
|
226
|
+
severity: params.severity || '',
|
|
227
|
+
engagementId: params.engagement_id || '',
|
|
228
|
+
tags: params.tags || [],
|
|
229
|
+
});
|
|
230
|
+
const id = memory.store(entry);
|
|
231
|
+
return text({ id, type: params.memory_type, stored: true });
|
|
232
|
+
} finally { memory.close(); }
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
case 'cipher_search': {
|
|
236
|
+
const { AdaptiveRetriever, CipherMemory } = await import('../memory/index.js');
|
|
237
|
+
const memory = new CipherMemory(defaultMemoryDir());
|
|
238
|
+
try {
|
|
239
|
+
const retriever = new AdaptiveRetriever(memory);
|
|
240
|
+
const results = retriever.retrieve(params.query, params.engagement_id || '', params.limit || 10);
|
|
241
|
+
return text({
|
|
242
|
+
query: params.query,
|
|
243
|
+
count: results.length,
|
|
244
|
+
results: results.map(r => ({
|
|
245
|
+
content: r.content,
|
|
246
|
+
type: r.memoryType || '',
|
|
247
|
+
severity: r.severity || '',
|
|
248
|
+
targets: r.targets || [],
|
|
249
|
+
mitre: r.mitreAttack || [],
|
|
250
|
+
})),
|
|
251
|
+
});
|
|
252
|
+
} finally { memory.close(); }
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
case 'cipher_context': {
|
|
256
|
+
const { AdaptiveRetriever, CipherMemory } = await import('../memory/index.js');
|
|
257
|
+
const memory = new CipherMemory(defaultMemoryDir());
|
|
258
|
+
try {
|
|
259
|
+
const retriever = new AdaptiveRetriever(memory);
|
|
260
|
+
const results = retriever.retrieve(params.query, params.engagement_id || '', 10);
|
|
261
|
+
const contextText = results.map(r => r.content).join('\n\n');
|
|
262
|
+
// Simple token budget: ~4 chars per token
|
|
263
|
+
const maxChars = (params.max_tokens || 4000) * 4;
|
|
264
|
+
return text({ query: params.query, context: contextText.slice(0, maxChars) });
|
|
265
|
+
} finally { memory.close(); }
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
case 'cipher_consolidate': {
|
|
269
|
+
const { CipherMemory } = await import('../memory/index.js');
|
|
270
|
+
const memory = new CipherMemory(defaultMemoryDir());
|
|
271
|
+
try {
|
|
272
|
+
memory.symbolic.db.exec("INSERT INTO entries_fts(entries_fts) VALUES('rebuild')");
|
|
273
|
+
const stats = memory.consolidate();
|
|
274
|
+
return text({ consolidated: true, ...stats });
|
|
275
|
+
} finally { memory.close(); }
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
case 'cipher_stats': {
|
|
279
|
+
const { CipherMemory } = await import('../memory/index.js');
|
|
280
|
+
const memory = new CipherMemory(defaultMemoryDir());
|
|
281
|
+
try {
|
|
282
|
+
const memStats = memory.stats();
|
|
283
|
+
const sDir = skillsDir();
|
|
284
|
+
let skillCount = 0;
|
|
285
|
+
if (existsSync(sDir)) {
|
|
286
|
+
const walk = (d) => { try { for (const e of readdirSync(d)) { const p = join(d, e); try { if (statSync(p).isDirectory()) walk(p); else if (e === 'SKILL.md') skillCount++; } catch {} } } catch {} };
|
|
287
|
+
walk(sDir);
|
|
288
|
+
}
|
|
289
|
+
return text({ version: _version, memory: memStats, skills: skillCount });
|
|
290
|
+
} finally { memory.close(); }
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// ── Pipeline tools ──────────────────────────────────────────
|
|
294
|
+
case 'cipher_scan': {
|
|
295
|
+
const { NucleiRunner, ScanProfile } = await import('../pipeline/scanner.js');
|
|
296
|
+
const runner = new NucleiRunner();
|
|
297
|
+
const result = await runner.scan(params.target, { profile: ScanProfile.fromDomain(params.profile || 'standard') });
|
|
298
|
+
return text({
|
|
299
|
+
target: params.target,
|
|
300
|
+
profile: params.profile || 'standard',
|
|
301
|
+
findings: (result.findings || []).length,
|
|
302
|
+
results: (result.findings || []).map(f => typeof f.toDict === 'function' ? f.toDict() : f),
|
|
303
|
+
status: 'completed',
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
case 'cipher_crawl': {
|
|
308
|
+
const { KatanaRunner } = await import('../pipeline/scanner.js');
|
|
309
|
+
const runner = new KatanaRunner();
|
|
310
|
+
const result = await runner.crawl(params.target, { depth: params.depth || 3 });
|
|
311
|
+
return text({
|
|
312
|
+
target: params.target,
|
|
313
|
+
urls: result.urls || [],
|
|
314
|
+
count: (result.urls || []).length,
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
case 'cipher_full_scan': {
|
|
319
|
+
const { ScanPipeline } = await import('../pipeline/scanner.js');
|
|
320
|
+
const pipeline = new ScanPipeline();
|
|
321
|
+
const result = await pipeline.fullScan(params.target, {
|
|
322
|
+
profile: params.profile || 'pentest',
|
|
323
|
+
engagementId: params.engagement_id || '',
|
|
324
|
+
});
|
|
325
|
+
return text({
|
|
326
|
+
target: params.target,
|
|
327
|
+
findings: (result.findings || []).length,
|
|
328
|
+
urls_crawled: (result.crawlResult?.urls || []).length,
|
|
329
|
+
status: 'completed',
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
case 'cipher_analyze_diff': {
|
|
334
|
+
const { SecurityDiffAnalyzer } = await import('../pipeline/github-actions.js');
|
|
335
|
+
const analyzer = new SecurityDiffAnalyzer();
|
|
336
|
+
const analysis = analyzer.analyzeDiff(params.diff);
|
|
337
|
+
return text({
|
|
338
|
+
risk_level: analysis.riskLevel?.value ?? analysis.riskLevel ?? 'info',
|
|
339
|
+
files_changed: analysis.filesChanged?.length ?? 0,
|
|
340
|
+
auth_changes: analysis.authChanges?.length ?? 0,
|
|
341
|
+
sql_changes: analysis.sqlChanges?.length ?? 0,
|
|
342
|
+
crypto_changes: analysis.cryptoChanges?.length ?? 0,
|
|
343
|
+
secrets_found: analysis.secrets?.length ?? 0,
|
|
344
|
+
summary: analysis.summary || '',
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
case 'cipher_detect_secrets': {
|
|
349
|
+
const { SecurityDiffAnalyzer } = await import('../pipeline/github-actions.js');
|
|
350
|
+
const analyzer = new SecurityDiffAnalyzer();
|
|
351
|
+
const analysis = analyzer.analyzeDiff(params.diff);
|
|
352
|
+
return text({
|
|
353
|
+
secrets: (analysis.secrets || []).map(s => typeof s.toDict === 'function' ? s.toDict() : s),
|
|
354
|
+
count: (analysis.secrets || []).length,
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// ── Evolution tools ─────────────────────────────────────────
|
|
359
|
+
case 'cipher_score': {
|
|
360
|
+
const { ResponseScorer } = await import('../memory/index.js');
|
|
361
|
+
const scorer = new ResponseScorer();
|
|
362
|
+
const scored = scorer.score(params.query, params.response, params.mode || '');
|
|
363
|
+
return text({ score: scored.score, votes: scored.votes, feedback: scored.feedback });
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
case 'cipher_evolve': {
|
|
367
|
+
const { SkillEvolver } = await import('../memory/index.js');
|
|
368
|
+
const evolver = new SkillEvolver();
|
|
369
|
+
const result = evolver.recordOutcome(params.skill_path, params.success, params.score || 0);
|
|
370
|
+
return text({ skill: params.skill_path, evolved: true, ...result });
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// ── Skills tools ────────────────────────────────────────────
|
|
374
|
+
case 'cipher_skills_search': {
|
|
375
|
+
const sDir = skillsDir();
|
|
376
|
+
const query = (params.query || '').toLowerCase();
|
|
377
|
+
const results = [];
|
|
378
|
+
if (existsSync(sDir)) {
|
|
379
|
+
const walk = (dir, relBase) => {
|
|
380
|
+
if (results.length >= 20) return;
|
|
381
|
+
try {
|
|
382
|
+
for (const entry of readdirSync(dir)) {
|
|
383
|
+
if (results.length >= 20) return;
|
|
384
|
+
const fullPath = join(dir, entry);
|
|
385
|
+
try {
|
|
386
|
+
if (statSync(fullPath).isDirectory()) { walk(fullPath, join(relBase, entry)); }
|
|
387
|
+
else if (entry === 'SKILL.md') {
|
|
388
|
+
const name = dirname(join(relBase, entry)).split('/').pop() || '';
|
|
389
|
+
const rel = dirname(join(relBase, entry));
|
|
390
|
+
if (!query || name.toLowerCase().includes(query) || rel.toLowerCase().includes(query)) {
|
|
391
|
+
results.push({ name, path: rel });
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
} catch {}
|
|
395
|
+
}
|
|
396
|
+
} catch {}
|
|
397
|
+
};
|
|
398
|
+
walk(sDir, '');
|
|
399
|
+
}
|
|
400
|
+
return text({ query: params.query, count: results.length, results });
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
case 'cipher_skills_domains': {
|
|
404
|
+
const sDir = skillsDir();
|
|
405
|
+
const domainMap = {};
|
|
406
|
+
if (existsSync(sDir)) {
|
|
407
|
+
for (const d of readdirSync(sDir).sort()) {
|
|
408
|
+
const dirPath = join(sDir, d);
|
|
409
|
+
try { if (d.startsWith('.') || !statSync(dirPath).isDirectory()) continue; } catch { continue; }
|
|
410
|
+
const techniquesDir = join(dirPath, 'techniques');
|
|
411
|
+
if (existsSync(techniquesDir)) {
|
|
412
|
+
try {
|
|
413
|
+
domainMap[d] = readdirSync(techniquesDir).filter(t => { try { return statSync(join(techniquesDir, t)).isDirectory(); } catch { return false; } }).length;
|
|
414
|
+
} catch { domainMap[d] = 0; }
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
const total = Object.values(domainMap).reduce((a, b) => a + b, 0);
|
|
419
|
+
return text({ domains: domainMap, total_domains: Object.keys(domainMap).length, total_techniques: total });
|
|
420
|
+
}
|
|
421
|
+
|
|
195
422
|
default:
|
|
196
|
-
return
|
|
423
|
+
return mcpError(`Unknown tool: ${toolName}`);
|
|
197
424
|
}
|
|
198
425
|
} catch (err) {
|
|
199
|
-
return
|
|
426
|
+
return mcpError(err.message);
|
|
200
427
|
}
|
|
201
428
|
}
|
|
202
429
|
|
package/lib/pipeline/index.js
CHANGED
|
@@ -79,12 +79,14 @@ export {
|
|
|
79
79
|
DOMXSSScanner,
|
|
80
80
|
} from './dom-xss-scanner.js';
|
|
81
81
|
|
|
82
|
-
// OSINT — intelligence gathering
|
|
82
|
+
// OSINT — intelligence gathering (domains, IPs, usernames, emails, archives)
|
|
83
83
|
export {
|
|
84
84
|
isPrivateIP,
|
|
85
85
|
OSINTResult,
|
|
86
86
|
DomainIntelligence,
|
|
87
87
|
IPIntelligence,
|
|
88
|
+
PeopleIntelligence,
|
|
89
|
+
ArchiveIntelligence,
|
|
88
90
|
DocumentMetadata,
|
|
89
91
|
OSINTPipeline,
|
|
90
92
|
} from './osint.js';
|