@asifkibria/claude-code-toolkit 1.0.7 → 1.3.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 (79) hide show
  1. package/README.md +172 -475
  2. package/dist/CLAUDE.md +7 -0
  3. package/dist/__tests__/dashboard.test.d.ts +2 -0
  4. package/dist/__tests__/dashboard.test.d.ts.map +1 -0
  5. package/dist/__tests__/dashboard.test.js +606 -0
  6. package/dist/__tests__/dashboard.test.js.map +1 -0
  7. package/dist/__tests__/mcp-validator.test.d.ts +2 -0
  8. package/dist/__tests__/mcp-validator.test.d.ts.map +1 -0
  9. package/dist/__tests__/mcp-validator.test.js +217 -0
  10. package/dist/__tests__/mcp-validator.test.js.map +1 -0
  11. package/dist/__tests__/security.test.d.ts +2 -0
  12. package/dist/__tests__/security.test.d.ts.map +1 -0
  13. package/dist/__tests__/security.test.js +375 -0
  14. package/dist/__tests__/security.test.js.map +1 -0
  15. package/dist/__tests__/session-recovery.test.d.ts +2 -0
  16. package/dist/__tests__/session-recovery.test.d.ts.map +1 -0
  17. package/dist/__tests__/session-recovery.test.js +230 -0
  18. package/dist/__tests__/session-recovery.test.js.map +1 -0
  19. package/dist/__tests__/storage.test.d.ts +2 -0
  20. package/dist/__tests__/storage.test.d.ts.map +1 -0
  21. package/dist/__tests__/storage.test.js +241 -0
  22. package/dist/__tests__/storage.test.js.map +1 -0
  23. package/dist/__tests__/trace.test.d.ts +2 -0
  24. package/dist/__tests__/trace.test.d.ts.map +1 -0
  25. package/dist/__tests__/trace.test.js +376 -0
  26. package/dist/__tests__/trace.test.js.map +1 -0
  27. package/dist/cli.js +680 -39
  28. package/dist/cli.js.map +1 -1
  29. package/dist/index.js +674 -2
  30. package/dist/index.js.map +1 -1
  31. package/dist/lib/alerts.d.ts +38 -0
  32. package/dist/lib/alerts.d.ts.map +1 -0
  33. package/dist/lib/alerts.js +296 -0
  34. package/dist/lib/alerts.js.map +1 -0
  35. package/dist/lib/dashboard-ui.d.ts +2 -0
  36. package/dist/lib/dashboard-ui.d.ts.map +1 -0
  37. package/dist/lib/dashboard-ui.js +2267 -0
  38. package/dist/lib/dashboard-ui.js.map +1 -0
  39. package/dist/lib/dashboard.d.ts +16 -0
  40. package/dist/lib/dashboard.d.ts.map +1 -0
  41. package/dist/lib/dashboard.js +1571 -0
  42. package/dist/lib/dashboard.js.map +1 -0
  43. package/dist/lib/git.d.ts +29 -0
  44. package/dist/lib/git.d.ts.map +1 -0
  45. package/dist/lib/git.js +124 -0
  46. package/dist/lib/git.js.map +1 -0
  47. package/dist/lib/logs.d.ts +42 -0
  48. package/dist/lib/logs.d.ts.map +1 -0
  49. package/dist/lib/logs.js +166 -0
  50. package/dist/lib/logs.js.map +1 -0
  51. package/dist/lib/mcp-validator.d.ts +109 -0
  52. package/dist/lib/mcp-validator.d.ts.map +1 -0
  53. package/dist/lib/mcp-validator.js +601 -0
  54. package/dist/lib/mcp-validator.js.map +1 -0
  55. package/dist/lib/scanner.d.ts +6 -2
  56. package/dist/lib/scanner.d.ts.map +1 -1
  57. package/dist/lib/scanner.js +120 -19
  58. package/dist/lib/scanner.js.map +1 -1
  59. package/dist/lib/search.d.ts +56 -0
  60. package/dist/lib/search.d.ts.map +1 -0
  61. package/dist/lib/search.js +284 -0
  62. package/dist/lib/search.js.map +1 -0
  63. package/dist/lib/security.d.ts +96 -0
  64. package/dist/lib/security.d.ts.map +1 -0
  65. package/dist/lib/security.js +795 -0
  66. package/dist/lib/security.js.map +1 -0
  67. package/dist/lib/session-recovery.d.ts +60 -0
  68. package/dist/lib/session-recovery.d.ts.map +1 -0
  69. package/dist/lib/session-recovery.js +433 -0
  70. package/dist/lib/session-recovery.js.map +1 -0
  71. package/dist/lib/storage.d.ts +68 -0
  72. package/dist/lib/storage.d.ts.map +1 -0
  73. package/dist/lib/storage.js +503 -0
  74. package/dist/lib/storage.js.map +1 -0
  75. package/dist/lib/trace.d.ts +119 -0
  76. package/dist/lib/trace.d.ts.map +1 -0
  77. package/dist/lib/trace.js +649 -0
  78. package/dist/lib/trace.js.map +1 -0
  79. package/package.json +11 -3
@@ -0,0 +1,795 @@
1
+ import * as fs from "fs";
2
+ import * as path from "path";
3
+ import * as os from "os";
4
+ const CLAUDE_DIR = path.join(os.homedir(), ".claude");
5
+ const PROJECTS_DIR = path.join(CLAUDE_DIR, "projects");
6
+ const PII_PATTERNS = [
7
+ {
8
+ name: "Email Address",
9
+ type: "email",
10
+ regex: /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g,
11
+ category: "contact",
12
+ sensitivity: "medium",
13
+ },
14
+ {
15
+ name: "US Phone Number",
16
+ type: "phone_us",
17
+ regex: /(?:\+1[-.\s]?)?(?:\(?\d{3}\)?[-.\s]?)?\d{3}[-.\s]?\d{4}/g,
18
+ category: "contact",
19
+ sensitivity: "medium",
20
+ },
21
+ {
22
+ name: "International Phone",
23
+ type: "phone_intl",
24
+ regex: /\+(?:[0-9][-.\s]?){6,14}[0-9]/g,
25
+ category: "contact",
26
+ sensitivity: "medium",
27
+ },
28
+ {
29
+ name: "Social Security Number",
30
+ type: "ssn",
31
+ regex: /\b\d{3}[-.\s]?\d{2}[-.\s]?\d{4}\b/g,
32
+ category: "identifier",
33
+ sensitivity: "high",
34
+ },
35
+ {
36
+ name: "Credit Card Number",
37
+ type: "credit_card",
38
+ regex: /\b(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|3[47][0-9]{13}|6(?:011|5[0-9]{2})[0-9]{12})\b/g,
39
+ category: "financial",
40
+ sensitivity: "high",
41
+ },
42
+ {
43
+ name: "IP Address",
44
+ type: "ip_address",
45
+ regex: /\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b/g,
46
+ category: "identifier",
47
+ sensitivity: "low",
48
+ },
49
+ {
50
+ name: "Date of Birth",
51
+ type: "dob",
52
+ regex: /\b(?:born|dob|birth(?:day|date)?)[:\s]+(?:\d{1,2}[-/]\d{1,2}[-/]\d{2,4}|\d{4}[-/]\d{1,2}[-/]\d{1,2})/gi,
53
+ category: "identifier",
54
+ sensitivity: "medium",
55
+ },
56
+ {
57
+ name: "Street Address",
58
+ type: "address",
59
+ regex: /\b\d+\s+[A-Za-z]+(?:\s+[A-Za-z]+)*\s+(?:Street|St|Avenue|Ave|Road|Rd|Boulevard|Blvd|Drive|Dr|Lane|Ln|Court|Ct|Way|Place|Pl)\.?\b/gi,
60
+ category: "location",
61
+ sensitivity: "medium",
62
+ },
63
+ {
64
+ name: "Passport Number",
65
+ type: "passport",
66
+ regex: /\b[A-Z]{1,2}[0-9]{6,9}\b/g,
67
+ category: "identifier",
68
+ sensitivity: "high",
69
+ },
70
+ {
71
+ name: "Driver License",
72
+ type: "drivers_license",
73
+ regex: /\b(?:DL|driver'?s?\s+license)[:\s#]*[A-Z0-9]{5,15}\b/gi,
74
+ category: "identifier",
75
+ sensitivity: "high",
76
+ },
77
+ {
78
+ name: "Bank Account",
79
+ type: "bank_account",
80
+ regex: /\b(?:account|acct)[:\s#]*[0-9]{8,17}\b/gi,
81
+ category: "financial",
82
+ sensitivity: "high",
83
+ },
84
+ {
85
+ name: "IBAN",
86
+ type: "iban",
87
+ regex: /\b[A-Z]{2}[0-9]{2}[A-Z0-9]{4}[0-9]{7}(?:[A-Z0-9]?){0,16}\b/g,
88
+ category: "financial",
89
+ sensitivity: "high",
90
+ },
91
+ ];
92
+ const SECRET_PATTERNS = [
93
+ {
94
+ name: "AWS Access Key ID",
95
+ type: "aws_key",
96
+ regex: /AKIA[0-9A-Z]{16}/g,
97
+ severity: "critical",
98
+ },
99
+ {
100
+ name: "AWS Secret Access Key",
101
+ type: "aws_secret",
102
+ regex: /(?:aws_secret_access_key|AWS_SECRET_ACCESS_KEY)['"=:\s]+([A-Za-z0-9/+=]{40})/gi,
103
+ severity: "critical",
104
+ },
105
+ {
106
+ name: "API Token (ghp_, xoxb-, sk-)",
107
+ type: "api_token",
108
+ regex: /(?:ghp_[A-Za-z0-9]{36,}|xoxb-[A-Za-z0-9-]+|xoxp-[A-Za-z0-9-]+)/g,
109
+ severity: "high",
110
+ },
111
+ {
112
+ name: "sk- API Key",
113
+ type: "api_key",
114
+ regex: /sk-[A-Za-z0-9]{20,}/g,
115
+ severity: "high",
116
+ },
117
+ {
118
+ name: "Private Key",
119
+ type: "private_key",
120
+ regex: /-----BEGIN\s+(?:RSA\s+)?PRIVATE\s+KEY-----/g,
121
+ severity: "critical",
122
+ },
123
+ {
124
+ name: "Connection String",
125
+ type: "connection_string",
126
+ regex: /(?:mongodb|postgres|mysql|redis|amqp):\/\/[^\s'"]+:[^\s'"]+@[^\s'"]+/g,
127
+ severity: "high",
128
+ },
129
+ {
130
+ name: "JWT Token",
131
+ type: "jwt",
132
+ regex: /eyJ[A-Za-z0-9_-]{10,}\.eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}/g,
133
+ severity: "medium",
134
+ },
135
+ {
136
+ name: "Password in Config",
137
+ type: "password",
138
+ regex: /(?:password|passwd|pwd)\s*[=:]\s*["'][^"']{4,}["']/gi,
139
+ severity: "high",
140
+ },
141
+ {
142
+ name: "Slack Token",
143
+ type: "slack_token",
144
+ regex: /xox[bpras]-[A-Za-z0-9-]+/g,
145
+ severity: "high",
146
+ },
147
+ {
148
+ name: "Generic Secret Assignment",
149
+ type: "generic_secret",
150
+ regex: /(?:secret|api_key|apikey|access_token)\s*[=:]\s*["'][A-Za-z0-9+/=]{16,}["']/gi,
151
+ severity: "medium",
152
+ },
153
+ ];
154
+ function maskSecret(value) {
155
+ if (value.length <= 8)
156
+ return "****";
157
+ return value.slice(0, 4) + "****" + value.slice(-4);
158
+ }
159
+ function findJsonlFiles(dir) {
160
+ const files = [];
161
+ function walk(d) {
162
+ try {
163
+ const entries = fs.readdirSync(d, { withFileTypes: true });
164
+ for (const entry of entries) {
165
+ const full = path.join(d, entry.name);
166
+ if (entry.isDirectory()) {
167
+ walk(full);
168
+ }
169
+ else if (entry.isFile() && entry.name.endsWith(".jsonl") && !entry.name.includes(".backup.")) {
170
+ files.push(full);
171
+ }
172
+ }
173
+ }
174
+ catch { /* skip */ }
175
+ }
176
+ walk(dir);
177
+ return files;
178
+ }
179
+ const TEXT_MEDIA_HINTS = ["text/", "application/json", "application/xml", "application/x-yaml", "application/yaml"];
180
+ function decodeDocumentBlock(block) {
181
+ const source = block.source;
182
+ if (!source || source.type !== "base64")
183
+ return null;
184
+ const mediaType = source.media_type || "";
185
+ if (!TEXT_MEDIA_HINTS.some((hint) => mediaType.includes(hint)))
186
+ return null;
187
+ const data = source.data;
188
+ if (!data)
189
+ return null;
190
+ try {
191
+ const buffer = Buffer.from(data, "base64");
192
+ if (!buffer.length)
193
+ return null;
194
+ return buffer.toString("utf-8");
195
+ }
196
+ catch {
197
+ return null;
198
+ }
199
+ }
200
+ function collectTextFromContent(content) {
201
+ const texts = [];
202
+ if (!content)
203
+ return texts;
204
+ if (typeof content === "string") {
205
+ texts.push(content);
206
+ return texts;
207
+ }
208
+ const blocks = Array.isArray(content) ? content : [content];
209
+ for (const rawBlock of blocks) {
210
+ if (!rawBlock || typeof rawBlock !== "object")
211
+ continue;
212
+ const block = rawBlock;
213
+ const type = block.type;
214
+ if (type === "text" && typeof block.text === "string") {
215
+ texts.push(block.text);
216
+ continue;
217
+ }
218
+ if (type === "tool_use") {
219
+ const input = block.input;
220
+ if (input) {
221
+ for (const value of Object.values(input)) {
222
+ if (typeof value === "string") {
223
+ texts.push(value);
224
+ }
225
+ }
226
+ }
227
+ continue;
228
+ }
229
+ if (type === "document") {
230
+ const decoded = decodeDocumentBlock(block);
231
+ if (decoded)
232
+ texts.push(decoded);
233
+ continue;
234
+ }
235
+ if (type === "tool_result") {
236
+ const inner = block.content;
237
+ if (inner) {
238
+ texts.push(...collectTextFromContent(inner));
239
+ }
240
+ continue;
241
+ }
242
+ }
243
+ return texts;
244
+ }
245
+ function extractTextFromMessage(parsed) {
246
+ const message = parsed.message;
247
+ if (!message || !message.content)
248
+ return [];
249
+ return collectTextFromContent(message.content);
250
+ }
251
+ export function scanForSecrets(projectsDir = PROJECTS_DIR, options) {
252
+ const result = {
253
+ filesScanned: 0,
254
+ totalFindings: 0,
255
+ findings: [],
256
+ summary: {},
257
+ scannedAt: new Date(),
258
+ };
259
+ let files;
260
+ if (options?.file) {
261
+ files = [options.file];
262
+ }
263
+ else {
264
+ files = findJsonlFiles(projectsDir);
265
+ }
266
+ for (const file of files) {
267
+ result.filesScanned++;
268
+ let content;
269
+ try {
270
+ content = fs.readFileSync(file, "utf-8");
271
+ }
272
+ catch {
273
+ continue;
274
+ }
275
+ const lines = content.split("\n");
276
+ for (let i = 0; i < lines.length; i++) {
277
+ const line = lines[i].trim();
278
+ if (!line)
279
+ continue;
280
+ let parsed;
281
+ try {
282
+ parsed = JSON.parse(line);
283
+ }
284
+ catch {
285
+ continue;
286
+ }
287
+ const texts = extractTextFromMessage(parsed);
288
+ const toolResult = parsed.toolUseResult;
289
+ if (toolResult) {
290
+ if (toolResult.content) {
291
+ texts.push(...collectTextFromContent(toolResult.content));
292
+ }
293
+ if (typeof toolResult.text === "string") {
294
+ texts.push(toolResult.text);
295
+ }
296
+ }
297
+ for (const text of texts) {
298
+ for (const pattern of SECRET_PATTERNS) {
299
+ pattern.regex.lastIndex = 0;
300
+ const matches = text.match(pattern.regex);
301
+ if (matches) {
302
+ for (const match of matches) {
303
+ result.findings.push({
304
+ file,
305
+ line: i + 1,
306
+ type: pattern.type,
307
+ pattern: pattern.name,
308
+ maskedPreview: maskSecret(match),
309
+ severity: pattern.severity,
310
+ });
311
+ result.summary[pattern.type] = (result.summary[pattern.type] || 0) + 1;
312
+ result.totalFindings++;
313
+ }
314
+ }
315
+ }
316
+ }
317
+ }
318
+ }
319
+ return result;
320
+ }
321
+ export function scanForPII(projectsDir = PROJECTS_DIR, options) {
322
+ const result = {
323
+ filesScanned: 0,
324
+ totalFindings: 0,
325
+ findings: [],
326
+ byCategory: {},
327
+ bySensitivity: { high: 0, medium: 0, low: 0 },
328
+ scannedAt: new Date(),
329
+ };
330
+ let files;
331
+ if (options?.file) {
332
+ files = [options.file];
333
+ }
334
+ else {
335
+ files = findJsonlFiles(projectsDir);
336
+ }
337
+ for (const file of files) {
338
+ result.filesScanned++;
339
+ let content;
340
+ try {
341
+ content = fs.readFileSync(file, "utf-8");
342
+ }
343
+ catch {
344
+ continue;
345
+ }
346
+ const lines = content.split("\n");
347
+ for (let i = 0; i < lines.length; i++) {
348
+ const line = lines[i].trim();
349
+ if (!line)
350
+ continue;
351
+ let parsed;
352
+ try {
353
+ parsed = JSON.parse(line);
354
+ }
355
+ catch {
356
+ continue;
357
+ }
358
+ const texts = extractTextFromMessage(parsed);
359
+ for (const text of texts) {
360
+ for (const pattern of PII_PATTERNS) {
361
+ pattern.regex.lastIndex = 0;
362
+ const matches = text.match(pattern.regex);
363
+ if (matches) {
364
+ for (const match of matches) {
365
+ if (shouldFilterPII(match, pattern.type))
366
+ continue;
367
+ result.findings.push({
368
+ file,
369
+ line: i + 1,
370
+ type: pattern.type,
371
+ pattern: pattern.name,
372
+ maskedValue: maskPII(match, pattern.type),
373
+ fullValue: options?.includeFullValues ? match : undefined,
374
+ category: pattern.category,
375
+ sensitivity: pattern.sensitivity,
376
+ });
377
+ result.byCategory[pattern.category] = (result.byCategory[pattern.category] || 0) + 1;
378
+ result.bySensitivity[pattern.sensitivity]++;
379
+ result.totalFindings++;
380
+ }
381
+ }
382
+ }
383
+ }
384
+ }
385
+ }
386
+ return result;
387
+ }
388
+ function shouldFilterPII(value, type) {
389
+ if (type === "email") {
390
+ if (value.includes("@example.") || value.includes("@test.") || value.endsWith("@localhost")) {
391
+ return true;
392
+ }
393
+ if (value.includes("noreply@") || value.includes("no-reply@")) {
394
+ return true;
395
+ }
396
+ }
397
+ if (type === "ip_address") {
398
+ if (value.startsWith("127.") || value.startsWith("192.168.") || value.startsWith("10.") || value === "0.0.0.0") {
399
+ return true;
400
+ }
401
+ }
402
+ if (type === "phone_us") {
403
+ if (value.includes("555-") || value.startsWith("1234") || value === "000-000-0000") {
404
+ return true;
405
+ }
406
+ }
407
+ return false;
408
+ }
409
+ function maskPII(value, type) {
410
+ if (type === "email") {
411
+ const [local, domain] = value.split("@");
412
+ return local.slice(0, 2) + "***@" + domain;
413
+ }
414
+ if (type === "ssn" || type === "credit_card" || type === "bank_account" || type === "iban") {
415
+ return value.slice(0, 2) + "*".repeat(value.length - 6) + value.slice(-4);
416
+ }
417
+ if (type === "phone_us" || type === "phone_intl") {
418
+ return value.slice(0, 3) + "***" + value.slice(-4);
419
+ }
420
+ if (value.length <= 6)
421
+ return "***";
422
+ return value.slice(0, 3) + "***" + value.slice(-3);
423
+ }
424
+ export function formatPIIScanReport(result, showDetails = false) {
425
+ let output = "";
426
+ output += "╔══════════════════════════════════════════════╗\n";
427
+ output += "║ PII SCAN REPORT ║\n";
428
+ output += "╚══════════════════════════════════════════════╝\n\n";
429
+ output += `Files scanned: ${result.filesScanned}\n`;
430
+ output += `Total findings: ${result.totalFindings}\n\n`;
431
+ if (result.totalFindings === 0) {
432
+ output += "No PII found. ✓\n";
433
+ return output;
434
+ }
435
+ output += "By Sensitivity:\n";
436
+ output += ` \x1b[31mHigh: ${result.bySensitivity.high}\x1b[0m\n`;
437
+ output += ` \x1b[33mMedium: ${result.bySensitivity.medium}\x1b[0m\n`;
438
+ output += ` \x1b[32mLow: ${result.bySensitivity.low}\x1b[0m\n\n`;
439
+ output += "By Category:\n";
440
+ for (const [category, count] of Object.entries(result.byCategory)) {
441
+ output += ` ${category}: ${count}\n`;
442
+ }
443
+ output += "\n";
444
+ const limit = showDetails ? 50 : 25;
445
+ output += `Details (first ${limit}):\n`;
446
+ for (const finding of result.findings.slice(0, limit)) {
447
+ const icon = finding.sensitivity === "high" ? "🔴" : finding.sensitivity === "medium" ? "🟡" : "🟢";
448
+ output += ` ${icon} ${finding.pattern}\n`;
449
+ output += ` File: ${path.basename(finding.file)} Line: ${finding.line}\n`;
450
+ if (showDetails && finding.fullValue) {
451
+ output += ` Value: \x1b[31m${finding.fullValue}\x1b[0m (unmasked)\n`;
452
+ }
453
+ else {
454
+ output += ` Value: ${finding.maskedValue}\n`;
455
+ }
456
+ }
457
+ if (result.findings.length > limit) {
458
+ output += `\n ... and ${result.findings.length - limit} more findings\n`;
459
+ }
460
+ return output;
461
+ }
462
+ export function redactPII(file, lineNum, piiType) {
463
+ if (!fs.existsSync(file))
464
+ return { success: false, error: "File not found" };
465
+ const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
466
+ const backupPath = file.replace(".jsonl", `.backup.${timestamp}.jsonl`);
467
+ try {
468
+ fs.copyFileSync(file, backupPath);
469
+ }
470
+ catch {
471
+ return { success: false, error: "Failed to create backup" };
472
+ }
473
+ const content = fs.readFileSync(file, "utf-8");
474
+ const lines = content.split("\n");
475
+ if (lineNum < 1 || lineNum > lines.length)
476
+ return { success: false, error: "Line out of range" };
477
+ const line = lines[lineNum - 1];
478
+ if (!line.trim())
479
+ return { success: false, error: "Empty line" };
480
+ let redacted = line;
481
+ let count = 0;
482
+ const patterns = piiType
483
+ ? PII_PATTERNS.filter(p => p.type === piiType || p.name === piiType)
484
+ : PII_PATTERNS;
485
+ for (const pat of patterns) {
486
+ pat.regex.lastIndex = 0;
487
+ const before = redacted;
488
+ redacted = redacted.replace(pat.regex, "[PII_REDACTED]");
489
+ if (redacted !== before)
490
+ count++;
491
+ }
492
+ if (count === 0)
493
+ return { success: false, error: "No matching PII found on this line" };
494
+ lines[lineNum - 1] = redacted;
495
+ fs.writeFileSync(file, lines.join("\n"), "utf-8");
496
+ return { success: true, redactedCount: count, backupPath };
497
+ }
498
+ export function redactAllPII() {
499
+ const scan = scanForPII(PROJECTS_DIR, { includeFullValues: false });
500
+ if (scan.totalFindings === 0)
501
+ return { success: true, filesModified: 0, piiRedacted: 0 };
502
+ const fileGroups = new Map();
503
+ for (const f of scan.findings) {
504
+ const existing = fileGroups.get(f.file) || [];
505
+ existing.push({ line: f.line, type: f.type });
506
+ fileGroups.set(f.file, existing);
507
+ }
508
+ let filesModified = 0;
509
+ let piiRedacted = 0;
510
+ const errors = [];
511
+ for (const [file, findings] of fileGroups) {
512
+ try {
513
+ const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
514
+ const backupPath = file.replace(".jsonl", `.backup.${timestamp}.jsonl`);
515
+ fs.copyFileSync(file, backupPath);
516
+ const content = fs.readFileSync(file, "utf-8");
517
+ const lines = content.split("\n");
518
+ let modified = false;
519
+ const processedLines = new Set();
520
+ for (const f of findings) {
521
+ if (processedLines.has(f.line))
522
+ continue;
523
+ processedLines.add(f.line);
524
+ if (f.line < 1 || f.line > lines.length)
525
+ continue;
526
+ let line = lines[f.line - 1];
527
+ for (const pat of PII_PATTERNS) {
528
+ pat.regex.lastIndex = 0;
529
+ const before = line;
530
+ line = line.replace(pat.regex, "[PII_REDACTED]");
531
+ if (line !== before) {
532
+ piiRedacted++;
533
+ modified = true;
534
+ }
535
+ }
536
+ lines[f.line - 1] = line;
537
+ }
538
+ if (modified) {
539
+ fs.writeFileSync(file, lines.join("\n"), "utf-8");
540
+ filesModified++;
541
+ }
542
+ }
543
+ catch (err) {
544
+ errors.push(`${file}: ${err instanceof Error ? err.message : String(err)}`);
545
+ }
546
+ }
547
+ return { success: true, filesModified, piiRedacted, errors, items: Array.from(fileGroups.keys()).slice(0, 50) };
548
+ }
549
+ export function auditSession(sessionPath) {
550
+ const audit = {
551
+ sessionPath,
552
+ actions: [],
553
+ filesRead: [],
554
+ filesWritten: [],
555
+ commandsRun: [],
556
+ mcpToolsUsed: [],
557
+ urlsFetched: [],
558
+ };
559
+ let content;
560
+ try {
561
+ content = fs.readFileSync(sessionPath, "utf-8");
562
+ }
563
+ catch {
564
+ return audit;
565
+ }
566
+ const lines = content.split("\n");
567
+ let firstTimestamp = null;
568
+ let lastTimestamp = null;
569
+ for (const line of lines) {
570
+ const trimmed = line.trim();
571
+ if (!trimmed)
572
+ continue;
573
+ let parsed;
574
+ try {
575
+ parsed = JSON.parse(trimmed);
576
+ }
577
+ catch {
578
+ continue;
579
+ }
580
+ if (parsed.timestamp) {
581
+ const ts = new Date(parsed.timestamp);
582
+ if (!isNaN(ts.getTime())) {
583
+ if (!firstTimestamp)
584
+ firstTimestamp = ts;
585
+ lastTimestamp = ts;
586
+ }
587
+ }
588
+ const message = parsed.message;
589
+ if (!message || !message.content)
590
+ continue;
591
+ const contentArr = Array.isArray(message.content) ? message.content : [message.content];
592
+ for (const block of contentArr) {
593
+ if (block.type !== "tool_use")
594
+ continue;
595
+ const name = block.name;
596
+ const input = block.input;
597
+ if (!input)
598
+ continue;
599
+ const timestamp = parsed.timestamp;
600
+ if (name === "Read") {
601
+ const filePath = input.file_path;
602
+ if (filePath && !audit.filesRead.includes(filePath)) {
603
+ audit.filesRead.push(filePath);
604
+ }
605
+ audit.actions.push({ type: "file_read", detail: filePath || "", timestamp });
606
+ }
607
+ if (name === "Write" || name === "Edit") {
608
+ const filePath = (input.file_path || input.path);
609
+ if (filePath && !audit.filesWritten.includes(filePath)) {
610
+ audit.filesWritten.push(filePath);
611
+ }
612
+ audit.actions.push({ type: "file_write", detail: filePath || "", timestamp });
613
+ }
614
+ if (name === "Bash") {
615
+ const command = input.command;
616
+ if (command && !audit.commandsRun.includes(command)) {
617
+ audit.commandsRun.push(command);
618
+ }
619
+ audit.actions.push({ type: "command", detail: command || "", timestamp });
620
+ }
621
+ if (name === "WebFetch") {
622
+ const url = input.url;
623
+ if (url && !audit.urlsFetched.includes(url)) {
624
+ audit.urlsFetched.push(url);
625
+ }
626
+ audit.actions.push({ type: "web_fetch", detail: url || "", timestamp });
627
+ }
628
+ if (name.startsWith("mcp__")) {
629
+ if (!audit.mcpToolsUsed.includes(name)) {
630
+ audit.mcpToolsUsed.push(name);
631
+ }
632
+ audit.actions.push({ type: "mcp_tool", detail: name, timestamp });
633
+ }
634
+ }
635
+ }
636
+ if (firstTimestamp && lastTimestamp) {
637
+ audit.duration = Math.round((lastTimestamp.getTime() - firstTimestamp.getTime()) / 60000);
638
+ }
639
+ return audit;
640
+ }
641
+ export function enforceRetention(projectsDir = PROJECTS_DIR, options) {
642
+ const days = options?.days ?? 30;
643
+ const dryRun = options?.dryRun ?? true;
644
+ const threshold = Date.now() - days * 24 * 60 * 60 * 1000;
645
+ const result = {
646
+ sessionsDeleted: 0,
647
+ sessionsExported: 0,
648
+ spaceFreed: 0,
649
+ errors: [],
650
+ dryRun,
651
+ };
652
+ const files = findJsonlFiles(projectsDir);
653
+ for (const file of files) {
654
+ try {
655
+ const stat = fs.statSync(file);
656
+ if (stat.mtime.getTime() < threshold) {
657
+ result.sessionsDeleted++;
658
+ result.spaceFreed += stat.size;
659
+ if (!dryRun) {
660
+ fs.unlinkSync(file);
661
+ }
662
+ }
663
+ }
664
+ catch (e) {
665
+ result.errors.push(`${file}: ${e}`);
666
+ }
667
+ }
668
+ return result;
669
+ }
670
+ export function generateComplianceReport(projectsDir = PROJECTS_DIR) {
671
+ const secretsScan = scanForSecrets(projectsDir);
672
+ const files = findJsonlFiles(projectsDir);
673
+ let oldestSession;
674
+ for (const file of files) {
675
+ try {
676
+ const stat = fs.statSync(file);
677
+ if (!oldestSession || stat.mtime < oldestSession) {
678
+ oldestSession = stat.mtime;
679
+ }
680
+ }
681
+ catch { /* skip */ }
682
+ }
683
+ return {
684
+ secretsScan,
685
+ sessionCount: files.length,
686
+ oldestSession,
687
+ retentionStatus: oldestSession
688
+ ? `Oldest session: ${Math.round((Date.now() - oldestSession.getTime()) / (24 * 60 * 60 * 1000))} days old`
689
+ : "No sessions found",
690
+ generatedAt: new Date(),
691
+ };
692
+ }
693
+ function formatBytes(bytes) {
694
+ if (bytes < 1024)
695
+ return `${bytes} B`;
696
+ if (bytes < 1024 * 1024)
697
+ return `${(bytes / 1024).toFixed(1)} KB`;
698
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
699
+ }
700
+ export function formatSecretsScanReport(result) {
701
+ let output = "";
702
+ output += "╔══════════════════════════════════════════════╗\n";
703
+ output += "║ SECRETS SCAN REPORT ║\n";
704
+ output += "╚══════════════════════════════════════════════╝\n\n";
705
+ output += `Files scanned: ${result.filesScanned}\n`;
706
+ output += `Total findings: ${result.totalFindings}\n\n`;
707
+ if (result.totalFindings === 0) {
708
+ output += "No secrets found. ✓\n";
709
+ return output;
710
+ }
711
+ output += "Findings by type:\n";
712
+ for (const [type, count] of Object.entries(result.summary)) {
713
+ output += ` ${type}: ${count}\n`;
714
+ }
715
+ output += "\nDetails:\n";
716
+ for (const finding of result.findings.slice(0, 25)) {
717
+ const icon = finding.severity === "critical" ? "✗" : finding.severity === "high" ? "⚠" : "ℹ";
718
+ output += ` ${icon} [${finding.severity}] ${finding.pattern}\n`;
719
+ output += ` File: ${path.basename(finding.file)} Line: ${finding.line}\n`;
720
+ output += ` Preview: ${finding.maskedPreview}\n`;
721
+ }
722
+ if (result.findings.length > 25) {
723
+ output += `\n ... and ${result.findings.length - 25} more findings\n`;
724
+ }
725
+ return output;
726
+ }
727
+ export function formatAuditReport(audit) {
728
+ let output = "";
729
+ output += "╔══════════════════════════════════════════════╗\n";
730
+ output += "║ SESSION AUDIT REPORT ║\n";
731
+ output += "╚══════════════════════════════════════════════╝\n\n";
732
+ output += `Session: ${path.basename(audit.sessionPath)}\n`;
733
+ output += `Total actions: ${audit.actions.length}\n`;
734
+ if (audit.duration)
735
+ output += `Duration: ${audit.duration} minutes\n`;
736
+ output += "\n";
737
+ if (audit.filesRead.length > 0) {
738
+ output += `Files Read (${audit.filesRead.length}):\n`;
739
+ for (const f of audit.filesRead) {
740
+ output += ` ${f}\n`;
741
+ }
742
+ output += "\n";
743
+ }
744
+ if (audit.filesWritten.length > 0) {
745
+ output += `Files Written (${audit.filesWritten.length}):\n`;
746
+ for (const f of audit.filesWritten) {
747
+ output += ` ${f}\n`;
748
+ }
749
+ output += "\n";
750
+ }
751
+ if (audit.commandsRun.length > 0) {
752
+ output += `Commands Run (${audit.commandsRun.length}):\n`;
753
+ for (const cmd of audit.commandsRun) {
754
+ output += ` ${cmd}\n`;
755
+ }
756
+ output += "\n";
757
+ }
758
+ if (audit.mcpToolsUsed.length > 0) {
759
+ output += `MCP Tools Used (${audit.mcpToolsUsed.length}):\n`;
760
+ for (const tool of audit.mcpToolsUsed) {
761
+ output += ` ${tool}\n`;
762
+ }
763
+ output += "\n";
764
+ }
765
+ if (audit.urlsFetched.length > 0) {
766
+ output += `URLs Fetched (${audit.urlsFetched.length}):\n`;
767
+ for (const url of audit.urlsFetched) {
768
+ output += ` ${url}\n`;
769
+ }
770
+ output += "\n";
771
+ }
772
+ return output;
773
+ }
774
+ export function formatRetentionReport(result) {
775
+ let output = "";
776
+ if (result.dryRun) {
777
+ output += "[DRY RUN] Retention policy preview:\n\n";
778
+ }
779
+ else {
780
+ output += "Retention Policy Enforced:\n\n";
781
+ }
782
+ output += `Sessions to delete: ${result.sessionsDeleted}\n`;
783
+ output += `Space to free: ${formatBytes(result.spaceFreed)}\n`;
784
+ if (result.errors.length > 0) {
785
+ output += `\nErrors:\n`;
786
+ for (const err of result.errors) {
787
+ output += ` ✗ ${err}\n`;
788
+ }
789
+ }
790
+ if (result.dryRun) {
791
+ output += "\nRun without --dry-run to enforce retention.\n";
792
+ }
793
+ return output;
794
+ }
795
+ //# sourceMappingURL=security.js.map