cipher-security 5.0.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 (75) hide show
  1. package/bin/cipher.js +465 -0
  2. package/lib/api/billing.js +321 -0
  3. package/lib/api/compliance.js +693 -0
  4. package/lib/api/controls.js +1401 -0
  5. package/lib/api/index.js +49 -0
  6. package/lib/api/marketplace.js +467 -0
  7. package/lib/api/openai-proxy.js +383 -0
  8. package/lib/api/server.js +685 -0
  9. package/lib/autonomous/feedback-loop.js +554 -0
  10. package/lib/autonomous/framework.js +512 -0
  11. package/lib/autonomous/index.js +97 -0
  12. package/lib/autonomous/leaderboard.js +594 -0
  13. package/lib/autonomous/modes/architect.js +412 -0
  14. package/lib/autonomous/modes/blue.js +386 -0
  15. package/lib/autonomous/modes/incident.js +684 -0
  16. package/lib/autonomous/modes/privacy.js +369 -0
  17. package/lib/autonomous/modes/purple.js +294 -0
  18. package/lib/autonomous/modes/recon.js +250 -0
  19. package/lib/autonomous/parallel.js +587 -0
  20. package/lib/autonomous/researcher.js +583 -0
  21. package/lib/autonomous/runner.js +955 -0
  22. package/lib/autonomous/scheduler.js +615 -0
  23. package/lib/autonomous/task-parser.js +127 -0
  24. package/lib/autonomous/validators/forensic.js +266 -0
  25. package/lib/autonomous/validators/osint.js +216 -0
  26. package/lib/autonomous/validators/privacy.js +296 -0
  27. package/lib/autonomous/validators/purple.js +298 -0
  28. package/lib/autonomous/validators/sigma.js +248 -0
  29. package/lib/autonomous/validators/threat-model.js +363 -0
  30. package/lib/benchmark/agent.js +119 -0
  31. package/lib/benchmark/baselines.js +43 -0
  32. package/lib/benchmark/builder.js +143 -0
  33. package/lib/benchmark/config.js +35 -0
  34. package/lib/benchmark/coordinator.js +91 -0
  35. package/lib/benchmark/index.js +20 -0
  36. package/lib/benchmark/llm.js +58 -0
  37. package/lib/benchmark/models.js +137 -0
  38. package/lib/benchmark/reporter.js +103 -0
  39. package/lib/benchmark/runner.js +103 -0
  40. package/lib/benchmark/sandbox.js +96 -0
  41. package/lib/benchmark/scorer.js +32 -0
  42. package/lib/benchmark/solver.js +166 -0
  43. package/lib/benchmark/tools.js +62 -0
  44. package/lib/bot/bot.js +130 -0
  45. package/lib/commands.js +99 -0
  46. package/lib/complexity.js +377 -0
  47. package/lib/config.js +213 -0
  48. package/lib/gateway/client.js +309 -0
  49. package/lib/gateway/commands.js +830 -0
  50. package/lib/gateway/config-validate.js +109 -0
  51. package/lib/gateway/gateway.js +367 -0
  52. package/lib/gateway/index.js +62 -0
  53. package/lib/gateway/mode.js +309 -0
  54. package/lib/gateway/plugins.js +222 -0
  55. package/lib/gateway/prompt.js +214 -0
  56. package/lib/mcp/server.js +262 -0
  57. package/lib/memory/compressor.js +425 -0
  58. package/lib/memory/engine.js +763 -0
  59. package/lib/memory/evolution.js +668 -0
  60. package/lib/memory/index.js +58 -0
  61. package/lib/memory/orchestrator.js +506 -0
  62. package/lib/memory/retriever.js +515 -0
  63. package/lib/memory/synthesizer.js +333 -0
  64. package/lib/pipeline/async-scanner.js +510 -0
  65. package/lib/pipeline/binary-analysis.js +1043 -0
  66. package/lib/pipeline/dom-xss-scanner.js +435 -0
  67. package/lib/pipeline/github-actions.js +792 -0
  68. package/lib/pipeline/index.js +124 -0
  69. package/lib/pipeline/osint.js +498 -0
  70. package/lib/pipeline/sarif.js +373 -0
  71. package/lib/pipeline/scanner.js +880 -0
  72. package/lib/pipeline/template-manager.js +525 -0
  73. package/lib/pipeline/xss-scanner.js +353 -0
  74. package/lib/setup-wizard.js +229 -0
  75. package/package.json +30 -0
package/bin/cipher.js ADDED
@@ -0,0 +1,465 @@
1
+ #!/usr/bin/env node
2
+ // Copyright (c) 2026 defconxt. All rights reserved.
3
+ // Licensed under AGPL-3.0 — see LICENSE file for details.
4
+ // CIPHER is a trademark of defconxt.
5
+
6
+ /**
7
+ * cipher — CLI entry point for the CIPHER security engineering platform.
8
+ *
9
+ * Fast path: --version / -V / --help / -h use only node:fs + node:path
10
+ * (zero dynamic imports, <200ms cold start).
11
+ *
12
+ * Command path: dynamically imports python-bridge.js, spawns the Python
13
+ * gateway, routes JSON-RPC commands, prints results, and exits.
14
+ */
15
+
16
+ import { readFileSync } from 'node:fs';
17
+ import { resolve, dirname } from 'node:path';
18
+ import { fileURLToPath } from 'node:url';
19
+ import { spawn } from 'node:child_process';
20
+
21
+ // ---------------------------------------------------------------------------
22
+ // Fast path — no dynamic imports, must complete in <200ms
23
+ // ---------------------------------------------------------------------------
24
+
25
+ const args = process.argv.slice(2);
26
+
27
+ if (args[0] === '--version' || args[0] === '-V') {
28
+ const __dirname = dirname(fileURLToPath(import.meta.url));
29
+ const pkg = JSON.parse(readFileSync(resolve(__dirname, '..', 'package.json'), 'utf8'));
30
+ console.log(pkg.version);
31
+ process.exit(0);
32
+ }
33
+
34
+ if (args[0] === '--help' || args[0] === '-h') {
35
+ console.log(`CIPHER — AI Security Engineering Platform
36
+
37
+ Usage: cipher [command] [options]
38
+ cipher <query> Freeform security query
39
+
40
+ Commands:
41
+ query Run a security query
42
+ ingest Ingest security data
43
+ status Show system status
44
+ doctor Diagnose installation health
45
+ setup Run setup wizard
46
+ setup-signal Configure Signal integration
47
+ dashboard Open the dashboard
48
+ web Start the web interface
49
+ version Print version information
50
+ plugin Manage plugins
51
+ scan Run a security scan
52
+ search Search security data
53
+ store Manage data stores
54
+ diff Compare security states
55
+ workflow Manage workflows
56
+ stats Show statistics
57
+ domains Manage domains
58
+ skills Manage skills
59
+ score Show security score
60
+ marketplace Browse marketplace
61
+ compliance Run compliance checks
62
+ leaderboard Show leaderboard
63
+ feedback Submit feedback
64
+ memory-export Export memory
65
+ memory-import Import memory
66
+ sarif SARIF report tools
67
+ osint OSINT intelligence tools
68
+ bot Manage bot integrations
69
+ mcp MCP server tools
70
+ api API management
71
+
72
+ Options:
73
+ --version, -V Print version and exit
74
+ --help, -h Print this help and exit
75
+
76
+ Environment:
77
+ CIPHER_DEBUG=1 Enable verbose debug logging
78
+
79
+ Documentation: https://github.com/defconxt/CIPHER`);
80
+ process.exit(0);
81
+ }
82
+
83
+ // ---------------------------------------------------------------------------
84
+ // No arguments: check if config exists → wizard or help
85
+ // ---------------------------------------------------------------------------
86
+
87
+ if (args.length === 0) {
88
+ try {
89
+ const { configExists } = await import('../lib/config.js');
90
+ if (!configExists()) {
91
+ const { runSetupWizard } = await import('../lib/setup-wizard.js');
92
+ await runSetupWizard();
93
+ process.exit(0);
94
+ }
95
+ } catch (err) {
96
+ console.error(`Setup wizard failed: ${err.message || err}`);
97
+ process.exit(1);
98
+ }
99
+ // Config exists but no command — show help hint
100
+ console.error('No command specified. Run `cipher --help` for usage.');
101
+ process.exit(1);
102
+ }
103
+
104
+ // ---------------------------------------------------------------------------
105
+ // Node.js-native commands — handled before the Python bridge is loaded
106
+ // ---------------------------------------------------------------------------
107
+
108
+ const nativeCommands = new Set(['setup']);
109
+
110
+ if (nativeCommands.has(args[0])) {
111
+ try {
112
+ const { runSetupWizard } = await import('../lib/setup-wizard.js');
113
+ await runSetupWizard();
114
+ process.exit(0);
115
+ } catch (err) {
116
+ console.error(`Setup wizard failed: ${err.message || err}`);
117
+ process.exit(1);
118
+ }
119
+ }
120
+
121
+ // ---------------------------------------------------------------------------
122
+ // Extract query-specific flags before general command parsing
123
+ // ---------------------------------------------------------------------------
124
+
125
+ let queryBackendOverride = null;
126
+ let queryNoStream = false;
127
+ let autonomousMode = null;
128
+
129
+ /** Set of valid autonomous mode names (lowercased). */
130
+ const MODE_NAMES = new Set(['red', 'blue', 'incident', 'purple', 'recon', 'privacy', 'architect']);
131
+
132
+ /** Clean args with --backend, --no-stream, and --autonomous extracted */
133
+ const cleanedArgs = [];
134
+ for (let i = 0; i < args.length; i++) {
135
+ if (args[i] === '--backend' && i + 1 < args.length) {
136
+ queryBackendOverride = args[i + 1];
137
+ i++; // skip value
138
+ } else if (args[i] === '--no-stream') {
139
+ queryNoStream = true;
140
+ } else if (args[i] === '--autonomous') {
141
+ // --autonomous flag found — look for a mode name in the remaining args.
142
+ // The mode name can be adjacent (cipher --autonomous blue "task")
143
+ // or positional-first (cipher blue --autonomous "task").
144
+ // We'll collect it after the loop from cleanedArgs.
145
+ autonomousMode = '__pending__';
146
+ } else {
147
+ cleanedArgs.push(args[i]);
148
+ }
149
+ }
150
+
151
+ // If --autonomous was found, extract the mode name from cleaned positional args
152
+ if (autonomousMode === '__pending__') {
153
+ // Check the first positional arg (non-flag) for a mode name
154
+ const firstPositional = cleanedArgs.find(a => !a.startsWith('-'));
155
+ if (firstPositional && MODE_NAMES.has(firstPositional.toLowerCase())) {
156
+ autonomousMode = firstPositional.toLowerCase();
157
+ // Remove the mode name from cleanedArgs so parseCommand() doesn't see it
158
+ const idx = cleanedArgs.indexOf(firstPositional);
159
+ cleanedArgs.splice(idx, 1);
160
+ } else {
161
+ console.error(
162
+ `Error: --autonomous requires a valid mode name.\n` +
163
+ `Available modes: ${[...MODE_NAMES].sort().join(', ')}\n\n` +
164
+ `Usage: cipher <mode> --autonomous "task description"\n` +
165
+ `Example: cipher blue --autonomous "Generate Sigma rules for T1059.001"`
166
+ );
167
+ process.exit(1);
168
+ }
169
+ }
170
+
171
+ // ---------------------------------------------------------------------------
172
+ // Command routing — dynamic imports below this point
173
+ // ---------------------------------------------------------------------------
174
+
175
+ const knownCommands = new Set([
176
+ // Gateway
177
+ 'query', 'ingest', 'status', 'doctor', 'setup-signal',
178
+ 'dashboard', 'web', 'version', 'plugin',
179
+ // Pipeline
180
+ 'scan', 'search', 'store', 'diff', 'workflow', 'stats', 'domains',
181
+ 'skills', 'score', 'marketplace', 'compliance', 'leaderboard',
182
+ 'feedback', 'memory-export', 'memory-import', 'sarif', 'osint',
183
+ // Services
184
+ 'bot', 'mcp', 'api',
185
+ ]);
186
+
187
+ /**
188
+ * Parse argv into a command name and remaining args.
189
+ * First non-flag argument is checked against knownCommands.
190
+ * If not a known command, treat all non-flag words as a bare query.
191
+ *
192
+ * @param {string[]} argv
193
+ * @returns {{ command: string, commandArgs: string[] }}
194
+ */
195
+ function parseCommand(argv) {
196
+ const flags = [];
197
+ const positional = [];
198
+
199
+ for (const arg of argv) {
200
+ if (arg.startsWith('-')) {
201
+ flags.push(arg);
202
+ } else {
203
+ positional.push(arg);
204
+ }
205
+ }
206
+
207
+ if (positional.length === 0) {
208
+ // No positional args — show help
209
+ console.error('No command specified. Run `cipher --help` for usage.');
210
+ process.exit(1);
211
+ }
212
+
213
+ const first = positional[0];
214
+
215
+ if (knownCommands.has(first)) {
216
+ return {
217
+ command: first,
218
+ commandArgs: [...positional.slice(1), ...flags],
219
+ };
220
+ }
221
+
222
+ // Bare query — join all positional words as query text
223
+ return {
224
+ command: 'query',
225
+ commandArgs: [...positional, ...flags],
226
+ };
227
+ }
228
+
229
+ // ---------------------------------------------------------------------------
230
+ // Bridge result formatter
231
+ // ---------------------------------------------------------------------------
232
+
233
+ /**
234
+ * Format and print a bridge command result, then exit if needed.
235
+ *
236
+ * Result shapes from bridge.py:
237
+ * 1. {output, exit_code, error: true} — command error: print output to stderr, exit with code
238
+ * 2. {output, exit_code} — text-mode result: print output directly
239
+ * 3. Plain object (no output field) — structured JSON: pretty-print as JSON
240
+ * 4. String — legacy: print directly
241
+ * 5. null/undefined — no output
242
+ *
243
+ * @param {*} result - Bridge command result
244
+ */
245
+ function formatBridgeResult(result) {
246
+ if (result === null || result === undefined) {
247
+ return;
248
+ }
249
+
250
+ if (typeof result === 'string') {
251
+ console.log(result);
252
+ return;
253
+ }
254
+
255
+ if (typeof result === 'object') {
256
+ // Error result — print to stderr and exit with error code
257
+ if (result.error === true) {
258
+ if (result.output) {
259
+ process.stderr.write(result.output + '\n');
260
+ }
261
+ process.exit(result.exit_code || 1);
262
+ }
263
+
264
+ // Text-mode result — print output directly (pre-formatted from CliRunner)
265
+ if ('output' in result) {
266
+ if (result.output) {
267
+ console.log(result.output);
268
+ }
269
+ if (result.exit_code && result.exit_code !== 0) {
270
+ process.exit(result.exit_code);
271
+ }
272
+ return;
273
+ }
274
+
275
+ // Structured JSON result — pretty-print
276
+ console.log(JSON.stringify(result, null, 2));
277
+ return;
278
+ }
279
+
280
+ // Fallback for unexpected types
281
+ console.log(String(result));
282
+ }
283
+
284
+ // ---------------------------------------------------------------------------
285
+ // Dispatch helpers + autonomous/standard routing
286
+ // ---------------------------------------------------------------------------
287
+
288
+ const { command, commandArgs } = parseCommand(cleanedArgs);
289
+
290
+ const debug = process.env.CIPHER_DEBUG === '1';
291
+
292
+
293
+ // ---------------------------------------------------------------------------
294
+ // Autonomous mode dispatch — bypasses normal command routing entirely
295
+ // ---------------------------------------------------------------------------
296
+
297
+ if (autonomousMode) {
298
+ // Build task text from remaining non-flag args
299
+ const taskText = commandArgs.filter(a => !a.startsWith('-')).join(' ')
300
+ || (command === 'query' ? commandArgs.join(' ') : '');
301
+
302
+ if (debug) {
303
+ process.stderr.write(
304
+ `[bridge:node] Autonomous dispatch: mode=${autonomousMode} task="${taskText}"\n`
305
+ );
306
+ }
307
+
308
+ try {
309
+ const { runAutonomous } = await import('../lib/autonomous/runner.js');
310
+ const { parseTaskInput } = await import('../lib/autonomous/task-parser.js');
311
+
312
+ const taskInput = parseTaskInput(autonomousMode.toUpperCase(), taskText);
313
+
314
+ const result = await runAutonomous(
315
+ autonomousMode,
316
+ taskInput,
317
+ queryBackendOverride,
318
+ );
319
+
320
+ // Format result
321
+ if (result.error) {
322
+ process.stderr.write(`Error: ${result.error}\n`);
323
+ process.exit(1);
324
+ }
325
+
326
+ // Print output
327
+ if (result.outputText) {
328
+ process.stdout.write(result.outputText + '\n');
329
+ }
330
+
331
+ // Print validation warnings to stderr
332
+ if (result.validation && !result.validation.valid) {
333
+ process.stderr.write('Validation warnings:\n');
334
+ for (const err of (result.validation.errors || [])) {
335
+ process.stderr.write(` - ${err}\n`);
336
+ }
337
+ }
338
+ } catch (err) {
339
+ const msg = err.message || String(err);
340
+ process.stderr.write(`Error: ${msg}\n`);
341
+ process.exit(1);
342
+ }
343
+
344
+ process.exit(0);
345
+ }
346
+
347
+ // ---------------------------------------------------------------------------
348
+ // Standard dispatch: passthrough vs native
349
+ // ---------------------------------------------------------------------------
350
+
351
+ const { getCommandMode } = await import('../lib/commands.js');
352
+ const mode = getCommandMode(command);
353
+
354
+ if (mode === 'native') {
355
+ // ── Native dispatch — Node.js handler functions (no Python) ────────────
356
+ if (debug) {
357
+ process.stderr.write(`[bridge:node] Dispatching command=${command} mode=native\n`);
358
+ }
359
+
360
+ /**
361
+ * Convert kebab-case command name to handler function name.
362
+ * 'memory-export' → 'handleMemoryExport'
363
+ * 'stats' → 'handleStats'
364
+ *
365
+ * @param {string} cmd
366
+ * @returns {string}
367
+ */
368
+ function toHandlerName(cmd) {
369
+ return 'handle' + cmd
370
+ .split('-')
371
+ .map(w => w[0].toUpperCase() + w.slice(1))
372
+ .join('');
373
+ }
374
+
375
+ try {
376
+ // ── API server command: long-running HTTP server (no Python) ──────────
377
+ if (command === 'api') {
378
+ const { createAPIServer, APIConfig } = await import('../lib/api/server.js');
379
+ const apiPort = commandArgs.find((a, i) => commandArgs[i - 1] === '--port') || '8443';
380
+ const apiHost = commandArgs.find((a, i) => commandArgs[i - 1] === '--host') || '127.0.0.1';
381
+ const noAuth = commandArgs.includes('--no-auth');
382
+ const cfg = new APIConfig({
383
+ port: parseInt(apiPort, 10),
384
+ host: apiHost,
385
+ requireAuth: !noAuth,
386
+ });
387
+ const api = createAPIServer(cfg);
388
+ const actualPort = await api.start();
389
+ console.log(`CIPHER API listening on ${cfg.host}:${actualPort}`);
390
+ // Keep process alive — Ctrl+C to stop
391
+ process.on('SIGINT', async () => {
392
+ await api.stop();
393
+ console.log('\nShutdown complete.');
394
+ process.exit(0);
395
+ });
396
+ process.on('SIGTERM', async () => {
397
+ await api.stop();
398
+ process.exit(0);
399
+ });
400
+ // ── MCP server command: JSON-RPC over stdio ──────────────────────────
401
+ } else if (command === 'mcp') {
402
+ const { CipherMCPServer } = await import('../lib/mcp/server.js');
403
+ const mcp = new CipherMCPServer();
404
+ mcp.startStdio();
405
+ // ── Bot command: Signal bot ──────────────────────────────────────────
406
+ } else if (command === 'bot') {
407
+ const { runBot } = await import('../lib/bot/bot.js');
408
+ runBot({
409
+ signalService: commandArgs.find((a, i) => commandArgs[i - 1] === '--service') || '',
410
+ phoneNumber: commandArgs.find((a, i) => commandArgs[i - 1] === '--phone') || '',
411
+ });
412
+ // ── Query command: streaming via Gateway or non-streaming via handler ──
413
+ } else if (command === 'query') {
414
+ const queryText = commandArgs.filter(a => !a.startsWith('-')).join(' ');
415
+
416
+ if (queryNoStream) {
417
+ // --no-stream: use handleQuery for non-streaming response
418
+ const { handleQuery } = await import('../lib/gateway/commands.js');
419
+ const result = await handleQuery({ message: queryText });
420
+ formatBridgeResult(result);
421
+ } else {
422
+ // Streaming path — Gateway.sendStream() → async iterator → stdout
423
+ const { Gateway } = await import('../lib/gateway/gateway.js');
424
+ const gw = new Gateway({
425
+ backendOverride: queryBackendOverride || undefined,
426
+ rag: true,
427
+ });
428
+
429
+ for await (const token of gw.sendStream(queryText)) {
430
+ process.stdout.write(token);
431
+ }
432
+ process.stdout.write('\n');
433
+ }
434
+ } else {
435
+ // ── Non-query native commands ──────────────────────────────────────
436
+ const handlers = await import('../lib/gateway/commands.js');
437
+ const handlerName = toHandlerName(command);
438
+ const handler = handlers[handlerName];
439
+
440
+ if (!handler) {
441
+ console.error(`Unknown native command: ${command}`);
442
+ process.exit(1);
443
+ }
444
+
445
+ const result = await handler(commandArgs);
446
+
447
+ // Native handlers return {error: true, message: "..."} for errors.
448
+ // formatBridgeResult expects {error: true, output: "...", exit_code: N}.
449
+ // Bridge the shape difference here.
450
+ if (result && result.error === true && result.message && !('output' in result)) {
451
+ process.stderr.write(result.message + '\n');
452
+ process.exit(result.exit_code || 1);
453
+ } else {
454
+ formatBridgeResult(result);
455
+ }
456
+ }
457
+ } catch (err) {
458
+ console.error(`Error: ${err.message || err}`);
459
+ process.exit(1);
460
+ }
461
+ } else {
462
+ // Unknown command mode — should not happen
463
+ console.error(`Unknown command: ${command}. Run \`cipher --help\` for usage.`);
464
+ process.exit(1);
465
+ }