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
@@ -0,0 +1,49 @@
1
+ // Copyright (c) 2026 defconxt. All rights reserved.
2
+ // Licensed under AGPL-3.0 — see LICENSE file for details.
3
+
4
+ /**
5
+ * CIPHER API module — barrel export.
6
+ *
7
+ * Re-exports all public symbols from the API submodules for clean
8
+ * consumption by downstream slices (MCP, bot, bridge removal).
9
+ */
10
+
11
+ // Compliance engine
12
+ export {
13
+ ComplianceFramework,
14
+ ControlStatus,
15
+ ControlAssessment,
16
+ ComplianceReport,
17
+ ComplianceEngine,
18
+ } from './compliance.js';
19
+
20
+ // Control data
21
+ export { CONTROL_MAPS, CATEGORY_KEYWORDS, SEVERITY_WEIGHTS } from './controls.js';
22
+
23
+ // REST server
24
+ export {
25
+ createAPIServer,
26
+ APIConfig,
27
+ APIResponse,
28
+ RateLimiter,
29
+ AuthHandler,
30
+ validateScanTarget,
31
+ } from './server.js';
32
+
33
+ // Billing
34
+ export {
35
+ MeteringEngine,
36
+ BillingTier,
37
+ TIER_LIMITS,
38
+ ENDPOINT_CREDITS,
39
+ } from './billing.js';
40
+
41
+ // Marketplace
42
+ export { SkillMarketplace } from './marketplace.js';
43
+
44
+ // OpenAI proxy
45
+ export {
46
+ createOpenAIProxy,
47
+ ProxyConfig,
48
+ findMatchingSkills,
49
+ } from './openai-proxy.js';
@@ -0,0 +1,467 @@
1
+ // Copyright (c) 2026 defconxt. All rights reserved.
2
+ // Licensed under AGPL-3.0 — see LICENSE file for details.
3
+
4
+ /**
5
+ * CIPHER Skill Marketplace — publish, discover, and install CIPHER skills.
6
+ *
7
+ * SQLite-backed CRUD with ratings/reviews, search, and JSON-based
8
+ * export/import (simplified from Python's tar.gz to reduce complexity).
9
+ */
10
+
11
+ import { randomUUID } from 'node:crypto';
12
+ import { createHash } from 'node:crypto';
13
+ import { homedir } from 'node:os';
14
+ import { join, dirname, resolve as resolvePath } from 'node:path';
15
+ import { mkdirSync, existsSync, readFileSync, writeFileSync, readdirSync, statSync, unlinkSync, rmdirSync } from 'node:fs';
16
+ import { createGzip, createGunzip } from 'node:zlib';
17
+
18
+ /** @type {typeof import('better-sqlite3')} */
19
+ let Database;
20
+
21
+ /**
22
+ * Compute SHA-256 checksum over all file contents sorted by name.
23
+ * @param {Record<string, string>} files
24
+ * @returns {string}
25
+ */
26
+ function computeChecksum(files) {
27
+ const h = createHash('sha256');
28
+ for (const name of Object.keys(files).sort()) {
29
+ h.update(name, 'utf8');
30
+ h.update(files[name], 'utf8');
31
+ }
32
+ return h.digest('hex');
33
+ }
34
+
35
+ /**
36
+ * Skill marketplace for publishing, discovering, and installing CIPHER skills.
37
+ */
38
+ export class SkillMarketplace {
39
+ /**
40
+ * @param {object} [opts]
41
+ * @param {string} [opts.dbPath] - Path to SQLite DB. Default: ~/.cipher/marketplace.db
42
+ * @param {string} [opts.skillsDir] - Default install directory for skills. Default: 'skills'
43
+ */
44
+ constructor(opts = {}) {
45
+ if (!Database) {
46
+ Database = require('better-sqlite3');
47
+ }
48
+ const dbPath = opts.dbPath || join(homedir(), '.cipher', 'marketplace.db');
49
+ this.skillsDir = opts.skillsDir || 'skills';
50
+ mkdirSync(dirname(dbPath), { recursive: true });
51
+ this._db = new Database(dbPath);
52
+ this._db.pragma('journal_mode = WAL');
53
+ this._initDb();
54
+ }
55
+
56
+ _initDb() {
57
+ this._db.exec(`
58
+ CREATE TABLE IF NOT EXISTS packages (
59
+ package_id TEXT PRIMARY KEY,
60
+ name TEXT NOT NULL,
61
+ domain TEXT NOT NULL DEFAULT '',
62
+ version TEXT NOT NULL DEFAULT '0.1.0',
63
+ description TEXT NOT NULL DEFAULT '',
64
+ author TEXT NOT NULL DEFAULT '',
65
+ license TEXT NOT NULL DEFAULT 'AGPL-3.0',
66
+ tags TEXT NOT NULL DEFAULT '[]',
67
+ files TEXT NOT NULL DEFAULT '{}',
68
+ checksum TEXT NOT NULL DEFAULT '',
69
+ downloads INTEGER NOT NULL DEFAULT 0,
70
+ rating REAL NOT NULL DEFAULT 0.0,
71
+ ratings_count INTEGER NOT NULL DEFAULT 0,
72
+ created_at TEXT NOT NULL,
73
+ updated_at TEXT NOT NULL
74
+ );
75
+
76
+ CREATE TABLE IF NOT EXISTS reviews (
77
+ review_id TEXT PRIMARY KEY,
78
+ package_id TEXT NOT NULL,
79
+ reviewer TEXT NOT NULL,
80
+ rating REAL NOT NULL,
81
+ comment TEXT NOT NULL DEFAULT '',
82
+ created_at TEXT NOT NULL,
83
+ FOREIGN KEY (package_id) REFERENCES packages(package_id)
84
+ );
85
+
86
+ CREATE TABLE IF NOT EXISTS download_log (
87
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
88
+ package_id TEXT NOT NULL,
89
+ downloaded_at TEXT NOT NULL,
90
+ target_dir TEXT NOT NULL DEFAULT '',
91
+ FOREIGN KEY (package_id) REFERENCES packages(package_id)
92
+ );
93
+
94
+ CREATE INDEX IF NOT EXISTS idx_packages_domain ON packages(domain);
95
+ CREATE INDEX IF NOT EXISTS idx_packages_rating ON packages(rating DESC);
96
+ CREATE INDEX IF NOT EXISTS idx_reviews_package ON reviews(package_id);
97
+ `);
98
+ }
99
+
100
+ // -- Publish / get / list / search ----------------------------------------
101
+
102
+ /**
103
+ * Publish a skill package to the marketplace. Returns package_id.
104
+ * @param {object} pkg
105
+ * @returns {string} package_id
106
+ */
107
+ publish(pkg) {
108
+ const packageId = pkg.packageId || randomUUID();
109
+ const now = new Date().toISOString();
110
+ const files = pkg.files || {};
111
+ const checksum = pkg.checksum || (Object.keys(files).length ? computeChecksum(files) : '');
112
+ const createdAt = pkg.createdAt || now;
113
+
114
+ this._db
115
+ .prepare(
116
+ `INSERT INTO packages (
117
+ package_id, name, domain, version, description, author,
118
+ license, tags, files, checksum, downloads, rating,
119
+ ratings_count, created_at, updated_at
120
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
121
+ ON CONFLICT(package_id) DO UPDATE SET
122
+ name=excluded.name, domain=excluded.domain,
123
+ version=excluded.version, description=excluded.description,
124
+ author=excluded.author, license=excluded.license,
125
+ tags=excluded.tags, files=excluded.files,
126
+ checksum=excluded.checksum, updated_at=excluded.updated_at`,
127
+ )
128
+ .run(
129
+ packageId,
130
+ pkg.name || '',
131
+ pkg.domain || '',
132
+ pkg.version || '0.1.0',
133
+ pkg.description || '',
134
+ pkg.author || '',
135
+ pkg.license || 'AGPL-3.0',
136
+ JSON.stringify(pkg.tags || []),
137
+ JSON.stringify(files),
138
+ checksum,
139
+ pkg.downloads || 0,
140
+ pkg.rating || 0,
141
+ pkg.ratingsCount || 0,
142
+ createdAt,
143
+ now,
144
+ );
145
+ return packageId;
146
+ }
147
+
148
+ /**
149
+ * Retrieve a single package by ID.
150
+ * @param {string} packageId
151
+ * @returns {object|null}
152
+ */
153
+ getPackage(packageId) {
154
+ const row = this._db.prepare('SELECT * FROM packages WHERE package_id = ?').get(packageId);
155
+ return row ? this._rowToPackage(row) : null;
156
+ }
157
+
158
+ /**
159
+ * List packages with optional domain filter and sorting.
160
+ * @param {object} [opts]
161
+ * @param {string} [opts.domain]
162
+ * @param {string} [opts.sort='rating'] - 'rating'|'downloads'|'name'|'updated'|'created'
163
+ * @param {number} [opts.limit=20]
164
+ * @returns {object[]}
165
+ */
166
+ listPackages(opts = {}) {
167
+ const sortMap = {
168
+ rating: 'rating DESC',
169
+ downloads: 'downloads DESC',
170
+ name: 'name ASC',
171
+ updated: 'updated_at DESC',
172
+ created: 'created_at DESC',
173
+ };
174
+ const order = sortMap[opts.sort || 'rating'] || 'rating DESC';
175
+ const limit = opts.limit || 20;
176
+
177
+ let rows;
178
+ if (opts.domain) {
179
+ rows = this._db
180
+ .prepare(`SELECT * FROM packages WHERE domain = ? ORDER BY ${order} LIMIT ?`)
181
+ .all(opts.domain, limit);
182
+ } else {
183
+ rows = this._db
184
+ .prepare(`SELECT * FROM packages ORDER BY ${order} LIMIT ?`)
185
+ .all(limit);
186
+ }
187
+ return rows.map((r) => this._rowToPackage(r));
188
+ }
189
+
190
+ /**
191
+ * Search packages by name, description, and optional filters.
192
+ * @param {string} query
193
+ * @param {object} [opts]
194
+ * @param {string} [opts.domain]
195
+ * @param {string[]} [opts.tags]
196
+ * @returns {object[]}
197
+ */
198
+ search(query, opts = {}) {
199
+ const conditions = ['(name LIKE ? OR description LIKE ? OR tags LIKE ?)'];
200
+ const params = [`%${query}%`, `%${query}%`, `%${query}%`];
201
+
202
+ if (opts.domain) {
203
+ conditions.push('domain = ?');
204
+ params.push(opts.domain);
205
+ }
206
+ if (opts.tags) {
207
+ for (const tag of opts.tags) {
208
+ conditions.push('tags LIKE ?');
209
+ params.push(`%"${tag}"%`);
210
+ }
211
+ }
212
+
213
+ const where = conditions.join(' AND ');
214
+ const rows = this._db
215
+ .prepare(`SELECT * FROM packages WHERE ${where} ORDER BY rating DESC`)
216
+ .all(...params);
217
+ return rows.map((r) => this._rowToPackage(r));
218
+ }
219
+
220
+ // -- Install / uninstall --------------------------------------------------
221
+
222
+ /**
223
+ * Install a skill package to the target directory.
224
+ * @param {string} packageId
225
+ * @param {string} [targetDir]
226
+ * @returns {boolean}
227
+ */
228
+ install(packageId, targetDir) {
229
+ const pkg = this.getPackage(packageId);
230
+ if (!pkg) return false;
231
+
232
+ const dest = resolvePath(targetDir || join(this.skillsDir, pkg.name));
233
+ mkdirSync(dest, { recursive: true });
234
+
235
+ for (const [filename, content] of Object.entries(pkg.files)) {
236
+ const filepath = resolvePath(dest, filename);
237
+ // Prevent path traversal
238
+ if (!filepath.startsWith(dest)) continue;
239
+ mkdirSync(dirname(filepath), { recursive: true });
240
+ writeFileSync(filepath, content, 'utf8');
241
+ }
242
+
243
+ // Record download
244
+ const now = new Date().toISOString();
245
+ this._db.prepare('UPDATE packages SET downloads = downloads + 1 WHERE package_id = ?').run(packageId);
246
+ this._db
247
+ .prepare('INSERT INTO download_log (package_id, downloaded_at, target_dir) VALUES (?, ?, ?)')
248
+ .run(packageId, now, dest);
249
+ return true;
250
+ }
251
+
252
+ /**
253
+ * Remove installed skill files from disk.
254
+ * @param {string} packageId
255
+ * @returns {boolean}
256
+ */
257
+ uninstall(packageId) {
258
+ const pkg = this.getPackage(packageId);
259
+ if (!pkg) return false;
260
+
261
+ const possibleDirs = [resolvePath(this.skillsDir, pkg.name)];
262
+
263
+ // Check download_log
264
+ const row = this._db
265
+ .prepare('SELECT target_dir FROM download_log WHERE package_id = ? ORDER BY downloaded_at DESC LIMIT 1')
266
+ .get(packageId);
267
+ if (row?.target_dir) {
268
+ possibleDirs.unshift(row.target_dir);
269
+ }
270
+
271
+ let removed = false;
272
+ for (const dir of possibleDirs) {
273
+ if (!existsSync(dir)) continue;
274
+ for (const filename of Object.keys(pkg.files)) {
275
+ const fpath = join(dir, filename);
276
+ if (existsSync(fpath)) {
277
+ try { unlinkSync(fpath); } catch { /* skip */ }
278
+ }
279
+ }
280
+ // Try to remove empty directory
281
+ try {
282
+ const remaining = readdirSync(dir);
283
+ if (remaining.length === 0) rmdirSync(dir);
284
+ } catch { /* directory not empty or doesn't exist */ }
285
+ removed = true;
286
+ }
287
+ return removed;
288
+ }
289
+
290
+ // -- Ratings / reviews ----------------------------------------------------
291
+
292
+ /**
293
+ * Add a rating/review for a package.
294
+ * @param {string} packageId
295
+ * @param {string} reviewer
296
+ * @param {number} rating - 1.0 to 5.0
297
+ * @param {string} [comment='']
298
+ * @returns {boolean}
299
+ */
300
+ rate(packageId, reviewer, rating, comment = '') {
301
+ if (!this.getPackage(packageId)) return false;
302
+
303
+ const clampedRating = Math.max(1.0, Math.min(5.0, rating));
304
+ const reviewId = randomUUID();
305
+ const now = new Date().toISOString();
306
+
307
+ this._db
308
+ .prepare(
309
+ 'INSERT INTO reviews (review_id, package_id, reviewer, rating, comment, created_at) VALUES (?, ?, ?, ?, ?, ?)',
310
+ )
311
+ .run(reviewId, packageId, reviewer, clampedRating, comment, now);
312
+
313
+ // Recompute average rating
314
+ const stats = this._db
315
+ .prepare('SELECT AVG(rating) as avg_r, COUNT(*) as cnt FROM reviews WHERE package_id = ?')
316
+ .get(packageId);
317
+ this._db
318
+ .prepare('UPDATE packages SET rating = ?, ratings_count = ? WHERE package_id = ?')
319
+ .run(Math.round(stats.avg_r * 100) / 100, stats.cnt, packageId);
320
+
321
+ return true;
322
+ }
323
+
324
+ /**
325
+ * Get all reviews for a package.
326
+ * @param {string} packageId
327
+ * @returns {object[]}
328
+ */
329
+ getReviews(packageId) {
330
+ return this._db
331
+ .prepare('SELECT * FROM reviews WHERE package_id = ? ORDER BY created_at DESC')
332
+ .all(packageId)
333
+ .map((r) => ({
334
+ reviewId: r.review_id,
335
+ packageId: r.package_id,
336
+ reviewer: r.reviewer,
337
+ rating: r.rating,
338
+ comment: r.comment,
339
+ createdAt: r.created_at,
340
+ }));
341
+ }
342
+
343
+ // -- Marketplace index ----------------------------------------------------
344
+
345
+ /**
346
+ * Build a summary index of the marketplace.
347
+ * @returns {object}
348
+ */
349
+ getIndex() {
350
+ const totalPackages = this._db.prepare('SELECT COUNT(*) as cnt FROM packages').get().cnt;
351
+ const totalDownloads = this._db.prepare('SELECT COALESCE(SUM(downloads), 0) as total FROM packages').get().total;
352
+
353
+ const catRows = this._db.prepare('SELECT domain, COUNT(*) as cnt FROM packages GROUP BY domain').all();
354
+ const categories = {};
355
+ for (const r of catRows) categories[r.domain] = r.cnt;
356
+
357
+ const topRated = this._db
358
+ .prepare('SELECT package_id, name, domain, rating, downloads FROM packages ORDER BY rating DESC LIMIT 10')
359
+ .all();
360
+ const mostDownloaded = this._db
361
+ .prepare('SELECT package_id, name, domain, rating, downloads FROM packages ORDER BY downloads DESC LIMIT 10')
362
+ .all();
363
+ const recentlyUpdated = this._db
364
+ .prepare('SELECT package_id, name, domain, version, updated_at FROM packages ORDER BY updated_at DESC LIMIT 10')
365
+ .all();
366
+
367
+ return {
368
+ totalPackages,
369
+ totalDownloads,
370
+ categories,
371
+ topRated,
372
+ mostDownloaded,
373
+ recentlyUpdated,
374
+ };
375
+ }
376
+
377
+ // -- Export / import as JSON archive ----------------------------------------
378
+
379
+ /**
380
+ * Export a package as a JSON archive string.
381
+ * @param {string} packageId
382
+ * @returns {string} JSON string
383
+ */
384
+ exportPackage(packageId) {
385
+ const pkg = this.getPackage(packageId);
386
+ if (!pkg) throw new Error(`Package not found: ${packageId}`);
387
+ return JSON.stringify(
388
+ {
389
+ manifest: {
390
+ package_id: pkg.packageId,
391
+ name: pkg.name,
392
+ domain: pkg.domain,
393
+ version: pkg.version,
394
+ description: pkg.description,
395
+ author: pkg.author,
396
+ license: pkg.license,
397
+ tags: pkg.tags,
398
+ checksum: pkg.checksum,
399
+ created_at: pkg.createdAt,
400
+ updated_at: pkg.updatedAt,
401
+ },
402
+ files: pkg.files,
403
+ },
404
+ null,
405
+ 2,
406
+ );
407
+ }
408
+
409
+ /**
410
+ * Import a package from a JSON archive string.
411
+ * @param {string} jsonStr
412
+ * @returns {object} The imported package object
413
+ */
414
+ importPackage(jsonStr) {
415
+ const data = JSON.parse(jsonStr);
416
+ const manifest = data.manifest || {};
417
+ const files = data.files || {};
418
+
419
+ // Verify checksum
420
+ const computed = computeChecksum(files);
421
+ if (manifest.checksum && manifest.checksum !== computed) {
422
+ throw new Error(`Checksum mismatch: expected ${manifest.checksum}, got ${computed}`);
423
+ }
424
+
425
+ return {
426
+ packageId: manifest.package_id || randomUUID(),
427
+ name: manifest.name || '',
428
+ domain: manifest.domain || '',
429
+ version: manifest.version || '0.1.0',
430
+ description: manifest.description || '',
431
+ author: manifest.author || '',
432
+ license: manifest.license || 'AGPL-3.0',
433
+ tags: manifest.tags || [],
434
+ files,
435
+ checksum: computed,
436
+ createdAt: manifest.created_at || '',
437
+ updatedAt: manifest.updated_at || '',
438
+ };
439
+ }
440
+
441
+ // -- Helpers ---------------------------------------------------------------
442
+
443
+ _rowToPackage(row) {
444
+ return {
445
+ packageId: row.package_id,
446
+ name: row.name,
447
+ domain: row.domain,
448
+ version: row.version,
449
+ description: row.description,
450
+ author: row.author,
451
+ license: row.license,
452
+ tags: JSON.parse(row.tags),
453
+ files: JSON.parse(row.files),
454
+ checksum: row.checksum,
455
+ downloads: row.downloads,
456
+ rating: row.rating,
457
+ ratingsCount: row.ratings_count,
458
+ createdAt: row.created_at,
459
+ updatedAt: row.updated_at,
460
+ };
461
+ }
462
+
463
+ /** Close the database connection. */
464
+ close() {
465
+ this._db.close();
466
+ }
467
+ }