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,349 @@
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
+ * Session Logger — JSONL persistence for autonomous engagements.
7
+ *
8
+ * Every autonomous interaction appends a JSON line to a session file.
9
+ * Sessions survive crashes (append-only, no buffering). Each session
10
+ * gets a unique ID and lives in ~/.cipher/sessions/.
11
+ *
12
+ * @module session/logger
13
+ */
14
+
15
+ import { randomBytes } from 'node:crypto';
16
+ import { existsSync, mkdirSync, appendFileSync, readFileSync, readdirSync, statSync } from 'node:fs';
17
+ import { join } from 'node:path';
18
+ import { homedir } from 'node:os';
19
+
20
+ // ---------------------------------------------------------------------------
21
+ // Constants
22
+ // ---------------------------------------------------------------------------
23
+
24
+ /** Default session directory. */
25
+ export function getSessionDir() {
26
+ return join(homedir(), '.cipher', 'sessions');
27
+ }
28
+
29
+ // ---------------------------------------------------------------------------
30
+ // SessionEntry
31
+ // ---------------------------------------------------------------------------
32
+
33
+ /**
34
+ * A single interaction entry in a session log.
35
+ */
36
+ export class SessionEntry {
37
+ /**
38
+ * @param {object} opts
39
+ * @param {string} opts.type - Entry type: 'start' | 'interaction' | 'handoff' | 'cost' | 'end' | 'error'
40
+ * @param {string} [opts.sessionId]
41
+ * @param {number} [opts.timestamp] - Unix epoch seconds
42
+ * @param {string} [opts.mode] - Agent mode
43
+ * @param {string} [opts.task] - Task summary
44
+ * @param {number} [opts.turn] - Turn number in the agent loop
45
+ * @param {number} [opts.toolCalls] - Number of tool calls this interaction
46
+ * @param {number} [opts.tokensIn] - Input tokens
47
+ * @param {number} [opts.tokensOut] - Output tokens
48
+ * @param {number} [opts.costUSD] - Estimated cost in USD
49
+ * @param {number} [opts.cumulativeCostUSD] - Running total cost
50
+ * @param {string} [opts.status] - 'running' | 'completed' | 'failed' | 'budget_exceeded'
51
+ * @param {string} [opts.outputSummary] - Brief output summary
52
+ * @param {object} [opts.metadata] - Additional data
53
+ */
54
+ constructor(opts = {}) {
55
+ this.type = opts.type ?? 'interaction';
56
+ this.sessionId = opts.sessionId ?? '';
57
+ this.timestamp = opts.timestamp ?? Date.now() / 1000;
58
+ this.mode = opts.mode ?? '';
59
+ this.task = opts.task ?? '';
60
+ this.turn = opts.turn ?? 0;
61
+ this.toolCalls = opts.toolCalls ?? 0;
62
+ this.tokensIn = opts.tokensIn ?? 0;
63
+ this.tokensOut = opts.tokensOut ?? 0;
64
+ this.costUSD = opts.costUSD ?? 0;
65
+ this.cumulativeCostUSD = opts.cumulativeCostUSD ?? 0;
66
+ this.status = opts.status ?? 'running';
67
+ this.outputSummary = opts.outputSummary ?? '';
68
+ this.metadata = opts.metadata ?? {};
69
+ }
70
+
71
+ /** Serialize to JSON string (one line). */
72
+ toJSON() {
73
+ return JSON.stringify({
74
+ type: this.type,
75
+ sessionId: this.sessionId,
76
+ timestamp: this.timestamp,
77
+ mode: this.mode,
78
+ task: this.task,
79
+ turn: this.turn,
80
+ toolCalls: this.toolCalls,
81
+ tokensIn: this.tokensIn,
82
+ tokensOut: this.tokensOut,
83
+ costUSD: this.costUSD,
84
+ cumulativeCostUSD: this.cumulativeCostUSD,
85
+ status: this.status,
86
+ outputSummary: this.outputSummary,
87
+ metadata: this.metadata,
88
+ });
89
+ }
90
+
91
+ /** Deserialize from a parsed JSON object. */
92
+ static fromObject(obj) {
93
+ return new SessionEntry(obj);
94
+ }
95
+ }
96
+
97
+ // ---------------------------------------------------------------------------
98
+ // createSessionId
99
+ // ---------------------------------------------------------------------------
100
+
101
+ /**
102
+ * Generate a unique session ID: timestamp prefix + random suffix.
103
+ * Format: YYYYMMDD-HHMMSS-xxxxxxxx
104
+ *
105
+ * @returns {string}
106
+ */
107
+ export function createSessionId() {
108
+ const now = new Date();
109
+ const ts = now.toISOString().replace(/[-:T]/g, '').slice(0, 14);
110
+ const rand = randomBytes(4).toString('hex');
111
+ return `${ts}-${rand}`;
112
+ }
113
+
114
+ // ---------------------------------------------------------------------------
115
+ // SessionLogger
116
+ // ---------------------------------------------------------------------------
117
+
118
+ /**
119
+ * Append-only JSONL session logger.
120
+ */
121
+ export class SessionLogger {
122
+ /**
123
+ * @param {object} [opts]
124
+ * @param {string} [opts.sessionId] - Existing session ID to continue
125
+ * @param {string} [opts.sessionDir] - Override session directory
126
+ * @param {string} [opts.mode] - Initial mode
127
+ * @param {string} [opts.task] - Initial task description
128
+ */
129
+ constructor(opts = {}) {
130
+ this._sessionId = opts.sessionId || createSessionId();
131
+ this._sessionDir = opts.sessionDir || getSessionDir();
132
+ this._mode = opts.mode || '';
133
+ this._task = opts.task || '';
134
+ this._cumulativeCost = 0;
135
+ this._entryCount = 0;
136
+ this._started = false;
137
+
138
+ // Ensure directory exists
139
+ mkdirSync(this._sessionDir, { recursive: true });
140
+ }
141
+
142
+ /** Current session ID. */
143
+ get sessionId() { return this._sessionId; }
144
+
145
+ /** Path to the session JSONL file. */
146
+ get filePath() { return join(this._sessionDir, `${this._sessionId}.jsonl`); }
147
+
148
+ /** Cumulative cost so far. */
149
+ get cumulativeCost() { return this._cumulativeCost; }
150
+
151
+ /** Number of entries written. */
152
+ get entryCount() { return this._entryCount; }
153
+
154
+ /**
155
+ * Write the session start entry.
156
+ * @param {object} [metadata]
157
+ */
158
+ start(metadata = {}) {
159
+ if (this._started) return;
160
+ this._started = true;
161
+
162
+ this._append(new SessionEntry({
163
+ type: 'start',
164
+ sessionId: this._sessionId,
165
+ mode: this._mode,
166
+ task: this._task,
167
+ status: 'running',
168
+ metadata: { ...metadata, startedAt: new Date().toISOString() },
169
+ }));
170
+ }
171
+
172
+ /**
173
+ * Log an interaction (one agent turn).
174
+ *
175
+ * @param {object} opts
176
+ * @param {string} opts.mode
177
+ * @param {number} opts.turn
178
+ * @param {number} opts.toolCalls
179
+ * @param {number} opts.tokensIn
180
+ * @param {number} opts.tokensOut
181
+ * @param {number} opts.costUSD
182
+ * @param {string} [opts.outputSummary]
183
+ * @param {object} [opts.metadata]
184
+ */
185
+ logInteraction(opts) {
186
+ this._cumulativeCost += opts.costUSD || 0;
187
+
188
+ this._append(new SessionEntry({
189
+ type: 'interaction',
190
+ sessionId: this._sessionId,
191
+ mode: opts.mode,
192
+ task: this._task,
193
+ turn: opts.turn,
194
+ toolCalls: opts.toolCalls,
195
+ tokensIn: opts.tokensIn,
196
+ tokensOut: opts.tokensOut,
197
+ costUSD: opts.costUSD || 0,
198
+ cumulativeCostUSD: this._cumulativeCost,
199
+ status: 'running',
200
+ outputSummary: opts.outputSummary || '',
201
+ metadata: opts.metadata || {},
202
+ }));
203
+ }
204
+
205
+ /**
206
+ * Log a handoff event.
207
+ *
208
+ * @param {string} sourceMode
209
+ * @param {string} targetMode
210
+ * @param {object} [metadata]
211
+ */
212
+ logHandoff(sourceMode, targetMode, metadata = {}) {
213
+ this._append(new SessionEntry({
214
+ type: 'handoff',
215
+ sessionId: this._sessionId,
216
+ mode: targetMode,
217
+ status: 'running',
218
+ metadata: { sourceMode, targetMode, ...metadata },
219
+ }));
220
+ }
221
+
222
+ /**
223
+ * Log a cost entry (budget check).
224
+ *
225
+ * @param {number} costUSD
226
+ * @param {number} budgetUSD
227
+ * @param {string} status - 'ok' | 'warning' | 'exceeded'
228
+ */
229
+ logCost(costUSD, budgetUSD, status) {
230
+ this._append(new SessionEntry({
231
+ type: 'cost',
232
+ sessionId: this._sessionId,
233
+ costUSD,
234
+ cumulativeCostUSD: this._cumulativeCost,
235
+ status,
236
+ metadata: { budgetUSD, remaining: budgetUSD - this._cumulativeCost },
237
+ }));
238
+ }
239
+
240
+ /**
241
+ * Write the session end entry.
242
+ *
243
+ * @param {string} status - 'completed' | 'failed' | 'budget_exceeded' | 'interrupted'
244
+ * @param {object} [metadata]
245
+ */
246
+ end(status = 'completed', metadata = {}) {
247
+ this._append(new SessionEntry({
248
+ type: 'end',
249
+ sessionId: this._sessionId,
250
+ status,
251
+ cumulativeCostUSD: this._cumulativeCost,
252
+ metadata: { ...metadata, endedAt: new Date().toISOString() },
253
+ }));
254
+ }
255
+
256
+ // -- internal -----------------------------------------------------------
257
+
258
+ /**
259
+ * Append a SessionEntry to the JSONL file.
260
+ * @param {SessionEntry} entry
261
+ */
262
+ _append(entry) {
263
+ appendFileSync(this.filePath, entry.toJSON() + '\n', 'utf-8');
264
+ this._entryCount += 1;
265
+ }
266
+ }
267
+
268
+ // ---------------------------------------------------------------------------
269
+ // Session loading and listing
270
+ // ---------------------------------------------------------------------------
271
+
272
+ /**
273
+ * Load a session from its JSONL file.
274
+ *
275
+ * @param {string} sessionId
276
+ * @param {string} [sessionDir]
277
+ * @returns {{ entries: SessionEntry[], metadata: object } | null}
278
+ */
279
+ export function loadSession(sessionId, sessionDir) {
280
+ const dir = sessionDir || getSessionDir();
281
+ const filePath = join(dir, `${sessionId}.jsonl`);
282
+
283
+ if (!existsSync(filePath)) return null;
284
+
285
+ const lines = readFileSync(filePath, 'utf-8').trim().split('\n').filter(Boolean);
286
+ const entries = lines.map(line => {
287
+ try {
288
+ return SessionEntry.fromObject(JSON.parse(line));
289
+ } catch {
290
+ return null;
291
+ }
292
+ }).filter(Boolean);
293
+
294
+ if (entries.length === 0) return null;
295
+
296
+ // Extract metadata from start entry
297
+ const startEntry = entries.find(e => e.type === 'start');
298
+ const endEntry = [...entries].reverse().find(e => e.type === 'end');
299
+ const lastEntry = entries[entries.length - 1];
300
+
301
+ return {
302
+ entries,
303
+ metadata: {
304
+ sessionId,
305
+ mode: startEntry?.mode || '',
306
+ task: startEntry?.task || '',
307
+ startedAt: startEntry?.metadata?.startedAt || '',
308
+ endedAt: endEntry?.metadata?.endedAt || '',
309
+ status: endEntry?.status || lastEntry?.status || 'interrupted',
310
+ totalTokensIn: entries.reduce((s, e) => s + (e.tokensIn || 0), 0),
311
+ totalTokensOut: entries.reduce((s, e) => s + (e.tokensOut || 0), 0),
312
+ totalCostUSD: lastEntry?.cumulativeCostUSD || 0,
313
+ interactionCount: entries.filter(e => e.type === 'interaction').length,
314
+ entryCount: entries.length,
315
+ },
316
+ };
317
+ }
318
+
319
+ /**
320
+ * List recent sessions from the session directory.
321
+ *
322
+ * @param {object} [opts]
323
+ * @param {number} [opts.limit=20] - Max sessions to return
324
+ * @param {string} [opts.sessionDir] - Override session directory
325
+ * @returns {Array<object>} Session metadata sorted by most recent first
326
+ */
327
+ export function listSessions(opts = {}) {
328
+ const dir = opts.sessionDir || getSessionDir();
329
+ const limit = opts.limit ?? 20;
330
+
331
+ if (!existsSync(dir)) return [];
332
+
333
+ const files = readdirSync(dir)
334
+ .filter(f => f.endsWith('.jsonl'))
335
+ .map(f => {
336
+ const filePath = join(dir, f);
337
+ const stat = statSync(filePath);
338
+ return { name: f, mtime: stat.mtimeMs };
339
+ })
340
+ .sort((a, b) => b.mtime - a.mtime)
341
+ .slice(0, limit);
342
+
343
+ return files.map(f => {
344
+ const sessionId = f.name.replace('.jsonl', '');
345
+ const session = loadSession(sessionId, dir);
346
+ if (!session) return null;
347
+ return session.metadata;
348
+ }).filter(Boolean);
349
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cipher-security",
3
- "version": "2.1.0",
3
+ "version": "2.2.0",
4
4
  "description": "CIPHER — AI Security Engineering Platform CLI",
5
5
  "type": "module",
6
6
  "engines": {