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.
- package/bin/cipher.js +11 -1
- package/lib/agent-runtime/handlers/architect.js +199 -0
- package/lib/agent-runtime/handlers/base.js +240 -0
- package/lib/agent-runtime/handlers/blue.js +220 -0
- package/lib/agent-runtime/handlers/incident.js +161 -0
- package/lib/agent-runtime/handlers/privacy.js +190 -0
- package/lib/agent-runtime/handlers/purple.js +209 -0
- package/lib/agent-runtime/handlers/recon.js +174 -0
- package/lib/agent-runtime/handlers/red.js +246 -0
- package/lib/agent-runtime/handlers/researcher.js +170 -0
- package/lib/agent-runtime/handlers.js +35 -0
- package/lib/agent-runtime/index.js +196 -0
- package/lib/agent-runtime/parser.js +316 -0
- package/lib/analyze/consistency.js +566 -0
- package/lib/analyze/constitution.js +110 -0
- package/lib/analyze/sharding.js +251 -0
- package/lib/autonomous/agent-tool.js +165 -0
- package/lib/autonomous/feedback-loop.js +13 -6
- package/lib/autonomous/framework.js +17 -0
- package/lib/autonomous/handoff.js +506 -0
- package/lib/autonomous/modes/blue.js +26 -0
- package/lib/autonomous/modes/red.js +585 -0
- package/lib/autonomous/modes/researcher.js +322 -0
- package/lib/autonomous/researcher.js +12 -45
- package/lib/autonomous/runner.js +9 -537
- package/lib/benchmark/agent.js +88 -26
- package/lib/benchmark/baselines.js +3 -0
- package/lib/benchmark/claude-code-solver.js +254 -0
- package/lib/benchmark/cognitive.js +283 -0
- package/lib/benchmark/index.js +12 -2
- package/lib/benchmark/knowledge.js +281 -0
- package/lib/benchmark/llm.js +156 -15
- package/lib/benchmark/models.js +5 -2
- package/lib/benchmark/nyu-ctf.js +192 -0
- package/lib/benchmark/overthewire.js +347 -0
- package/lib/benchmark/picoctf.js +281 -0
- package/lib/benchmark/prompts.js +280 -0
- package/lib/benchmark/registry.js +219 -0
- package/lib/benchmark/remote-solver.js +356 -0
- package/lib/benchmark/remote-target.js +263 -0
- package/lib/benchmark/reporter.js +35 -0
- package/lib/benchmark/runner.js +174 -10
- package/lib/benchmark/sandbox.js +35 -0
- package/lib/benchmark/scorer.js +22 -4
- package/lib/benchmark/solver.js +34 -1
- package/lib/benchmark/tools.js +262 -16
- package/lib/commands.js +9 -0
- package/lib/execution/council.js +434 -0
- package/lib/execution/parallel.js +292 -0
- package/lib/gates/circuit-breaker.js +135 -0
- package/lib/gates/confidence.js +302 -0
- package/lib/gates/corrections.js +219 -0
- package/lib/gates/self-check.js +245 -0
- package/lib/gateway/commands.js +727 -0
- package/lib/guardrails/engine.js +364 -0
- package/lib/mcp/server.js +349 -3
- package/lib/memory/compressor.js +94 -7
- package/lib/pipeline/hooks.js +288 -0
- package/lib/pipeline/index.js +11 -0
- package/lib/review/budget.js +210 -0
- package/lib/review/engine.js +526 -0
- package/lib/review/layers/acceptance-auditor.js +279 -0
- package/lib/review/layers/blind-hunter.js +500 -0
- package/lib/review/layers/defense-in-depth.js +209 -0
- package/lib/review/layers/edge-case-hunter.js +266 -0
- package/lib/review/panel.js +519 -0
- package/lib/review/two-stage.js +244 -0
- package/lib/session/cost-tracker.js +203 -0
- package/lib/session/logger.js +349 -0
- 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
|
+
}
|