kramscan 0.1.1 → 0.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 (72) hide show
  1. package/README.md +392 -236
  2. package/dist/agent/confirmation.d.ts +5 -1
  3. package/dist/agent/confirmation.js +29 -9
  4. package/dist/agent/context.js +2 -3
  5. package/dist/agent/orchestrator.d.ts +2 -0
  6. package/dist/agent/orchestrator.js +50 -8
  7. package/dist/agent/prompts/system.d.ts +1 -1
  8. package/dist/agent/prompts/system.js +5 -7
  9. package/dist/agent/skills/health-check.js +22 -2
  10. package/dist/agent/skills/index.d.ts +1 -0
  11. package/dist/agent/skills/index.js +3 -1
  12. package/dist/agent/skills/verify-finding.d.ts +17 -0
  13. package/dist/agent/skills/verify-finding.js +91 -0
  14. package/dist/agent/skills/web-scan.js +46 -0
  15. package/dist/cli.js +150 -149
  16. package/dist/commands/agent.js +38 -38
  17. package/dist/commands/ai.d.ts +2 -0
  18. package/dist/commands/ai.js +112 -0
  19. package/dist/commands/analyze.js +103 -54
  20. package/dist/commands/config.js +55 -29
  21. package/dist/commands/doctor.js +20 -15
  22. package/dist/commands/onboard.js +188 -141
  23. package/dist/commands/report.js +68 -76
  24. package/dist/commands/scan.js +261 -81
  25. package/dist/commands/scans.d.ts +2 -0
  26. package/dist/commands/scans.js +51 -0
  27. package/dist/core/ai-client.d.ts +6 -1
  28. package/dist/core/ai-client.js +80 -12
  29. package/dist/core/ai-payloads.d.ts +17 -0
  30. package/dist/core/ai-payloads.js +54 -0
  31. package/dist/core/config-schema.d.ts +197 -0
  32. package/dist/core/config-schema.js +68 -0
  33. package/dist/core/config-schema.test.d.ts +1 -0
  34. package/dist/core/config-schema.test.js +151 -0
  35. package/dist/core/config.d.ts +8 -31
  36. package/dist/core/config.js +68 -11
  37. package/dist/core/errors.d.ts +71 -0
  38. package/dist/core/errors.js +162 -0
  39. package/dist/core/scan-index.d.ts +19 -0
  40. package/dist/core/scan-index.js +52 -0
  41. package/dist/core/scan-storage.d.ts +11 -0
  42. package/dist/core/scan-storage.js +69 -0
  43. package/dist/core/scanner.d.ts +95 -13
  44. package/dist/core/scanner.js +336 -248
  45. package/dist/core/vulnerability-detector.d.ts +3 -0
  46. package/dist/core/vulnerability-detector.js +25 -15
  47. package/dist/core/vulnerability-detector.test.d.ts +1 -0
  48. package/dist/core/vulnerability-detector.test.js +210 -0
  49. package/dist/index.js +3 -0
  50. package/dist/plugins/PluginManager.d.ts +27 -0
  51. package/dist/plugins/PluginManager.js +166 -0
  52. package/dist/plugins/index.d.ts +7 -0
  53. package/dist/plugins/index.js +19 -0
  54. package/dist/plugins/types.d.ts +55 -0
  55. package/dist/plugins/types.js +25 -0
  56. package/dist/plugins/vulnerabilities/CSRFPlugin.d.ts +8 -0
  57. package/dist/plugins/vulnerabilities/CSRFPlugin.js +34 -0
  58. package/dist/plugins/vulnerabilities/SQLInjectionPlugin.d.ts +11 -0
  59. package/dist/plugins/vulnerabilities/SQLInjectionPlugin.js +109 -0
  60. package/dist/plugins/vulnerabilities/SecurityHeadersPlugin.d.ts +11 -0
  61. package/dist/plugins/vulnerabilities/SecurityHeadersPlugin.js +63 -0
  62. package/dist/plugins/vulnerabilities/SensitiveDataPlugin.d.ts +9 -0
  63. package/dist/plugins/vulnerabilities/SensitiveDataPlugin.js +32 -0
  64. package/dist/plugins/vulnerabilities/XSSPlugin.d.ts +15 -0
  65. package/dist/plugins/vulnerabilities/XSSPlugin.js +81 -0
  66. package/dist/reports/PdfGenerator.d.ts +36 -0
  67. package/dist/reports/PdfGenerator.js +379 -0
  68. package/dist/utils/logger.d.ts +33 -1
  69. package/dist/utils/logger.js +127 -8
  70. package/dist/utils/theme.d.ts +55 -0
  71. package/dist/utils/theme.js +195 -0
  72. package/package.json +1 -1
@@ -0,0 +1,379 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.pdfGenerator = exports.PdfGenerator = void 0;
40
+ const puppeteer_1 = __importDefault(require("puppeteer"));
41
+ const scan_storage_1 = require("../core/scan-storage");
42
+ const path_1 = __importDefault(require("path"));
43
+ function escapeHtml(text) { return text; }
44
+ function sanitizeFilenamePart(value) {
45
+ return value
46
+ .replace(/[<>:"\/\\|?*\x00-\x1F]/g, "_")
47
+ .replace(/\s+/g, "_")
48
+ .replace(/_+/g, "_")
49
+ .replace(/^_+|_+$/g, "");
50
+ }
51
+ function severityBadge(severity) {
52
+ const s = severity.toLowerCase();
53
+ if (s === "critical")
54
+ return "badge badge-critical";
55
+ if (s === "high")
56
+ return "badge badge-high";
57
+ if (s === "medium")
58
+ return "badge badge-medium";
59
+ if (s === "low")
60
+ return "badge badge-low";
61
+ return "badge badge-info";
62
+ }
63
+ function buildPdfHtml(data) {
64
+ const { scanResult, scanErrors = [], pluginErrors = new Map() } = data;
65
+ const rows = scanResult.vulnerabilities
66
+ .map((v, i) => {
67
+ const sev = escapeHtml(v.severity.toUpperCase());
68
+ const title = escapeHtml(v.title);
69
+ const url = escapeHtml(v.url);
70
+ const type = escapeHtml(v.type);
71
+ const desc = escapeHtml(v.description);
72
+ const evidence = v.evidence ? escapeHtml(v.evidence) : "";
73
+ const remediation = v.remediation ? escapeHtml(v.remediation) : "";
74
+ const cwe = v.cwe ? escapeHtml(v.cwe) : "";
75
+ return `
76
+ <div class="card">
77
+ <div class="card-h">
78
+ <div class="idx">${i + 1}.</div>
79
+ <div class="title">${title}</div>
80
+ <div class="${severityBadge(v.severity)}">${sev}</div>
81
+ </div>
82
+ <div class="meta">
83
+ <div><span class="k">URL:</span> <span class="v mono">${url}</span></div>
84
+ <div><span class="k">Type:</span> <span class="v mono">${type}</span></div>
85
+ ${cwe ? `<div><span class="k">CWE:</span> <span class="v mono">${cwe}</span></div>` : ""}
86
+ </div>
87
+ <div class="section">
88
+ <div class="k">Description</div>
89
+ <div class="v">${desc}</div>
90
+ </div>
91
+ ${evidence ? `<div class="section"><div class="k">Evidence</div><div class="v mono pre">${evidence}</div></div>` : ""}
92
+ ${remediation ? `<div class="section"><div class="k">Remediation</div><div class="v">${remediation}</div></div>` : ""}
93
+ </div>`;
94
+ })
95
+ .join("\n");
96
+ const summary = scanResult.summary;
97
+ // Build errors section
98
+ let errorsHtml = "";
99
+ if (scanErrors.length > 0 || pluginErrors.size > 0) {
100
+ const errorRows = scanErrors.map((e) => `
101
+ <tr>
102
+ <td class="mono">${escapeHtml(e.url)}</td>
103
+ <td>Crawl Error</td>
104
+ <td>${escapeHtml(e.error)}</td>
105
+ </tr>
106
+ `).join("");
107
+ const pluginErrorRows = [];
108
+ pluginErrors.forEach((errors, pluginName) => {
109
+ errors.forEach((e) => {
110
+ pluginErrorRows.push(`
111
+ <tr>
112
+ <td class="mono">${escapeHtml(e.url)}</td>
113
+ <td>${escapeHtml(pluginName)}</td>
114
+ <td>${escapeHtml(e.error)}</td>
115
+ </tr>
116
+ `);
117
+ });
118
+ });
119
+ const totalErrors = scanErrors.length + pluginErrorRows.length;
120
+ errorsHtml = `
121
+ <div class="section-header">
122
+ <h2>⚠️ Scan Errors & Skipped Items</h2>
123
+ <span class="badge badge-warning">${totalErrors} items</span>
124
+ </div>
125
+ <div class="error-table-container">
126
+ <table class="error-table">
127
+ <thead>
128
+ <tr>
129
+ <th>URL</th>
130
+ <th>Source</th>
131
+ <th>Error</th>
132
+ </tr>
133
+ </thead>
134
+ <tbody>
135
+ ${errorRows}
136
+ ${pluginErrorRows.join("")}
137
+ </tbody>
138
+ </table>
139
+ </div>`;
140
+ }
141
+ return `<!doctype html>
142
+ <html>
143
+ <head>
144
+ <meta charset="utf-8" />
145
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
146
+ <title>KramScan Security Report</title>
147
+ <style>
148
+ :root {
149
+ --bg: #0b1020;
150
+ --panel: #111a33;
151
+ --panel2: #0e1630;
152
+ --text: #e9eefc;
153
+ --muted: #a9b6e5;
154
+ --line: rgba(233,238,252,0.14);
155
+ --critical: #ff4d4f;
156
+ --high: #ff7a45;
157
+ --medium: #fadb14;
158
+ --low: #40a9ff;
159
+ --info: #8c8c8c;
160
+ --warning: #faad14;
161
+ }
162
+ * { box-sizing: border-box; }
163
+ body {
164
+ margin: 0;
165
+ padding: 28px;
166
+ font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Arial, "Noto Sans", "Helvetica Neue", sans-serif;
167
+ color: var(--text);
168
+ background: radial-gradient(1000px 600px at 20% 10%, rgba(64,169,255,0.20), transparent 60%),
169
+ radial-gradient(900px 500px at 70% 0%, rgba(255,77,79,0.18), transparent 55%),
170
+ linear-gradient(180deg, var(--bg), #070a14 70%);
171
+ }
172
+ .top {
173
+ display: flex;
174
+ justify-content: space-between;
175
+ align-items: flex-end;
176
+ gap: 16px;
177
+ padding-bottom: 14px;
178
+ border-bottom: 1px solid var(--line);
179
+ }
180
+ .brand {
181
+ display: flex;
182
+ flex-direction: column;
183
+ gap: 6px;
184
+ }
185
+ .h1 { font-size: 22px; font-weight: 800; letter-spacing: 0.3px; }
186
+ .sub { color: var(--muted); font-size: 12px; }
187
+ .mono { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; }
188
+ .pill {
189
+ padding: 6px 10px;
190
+ border: 1px solid var(--line);
191
+ border-radius: 999px;
192
+ background: rgba(17,26,51,0.55);
193
+ color: var(--muted);
194
+ font-size: 12px;
195
+ white-space: nowrap;
196
+ }
197
+ .grid {
198
+ display: grid;
199
+ grid-template-columns: 1fr 1fr 1fr 1fr 1fr;
200
+ gap: 10px;
201
+ margin: 16px 0 10px;
202
+ }
203
+ .stat {
204
+ border: 1px solid var(--line);
205
+ background: rgba(17,26,51,0.55);
206
+ border-radius: 12px;
207
+ padding: 10px 12px;
208
+ }
209
+ .stat .k { color: var(--muted); font-size: 11px; }
210
+ .stat .v { font-size: 18px; font-weight: 800; margin-top: 6px; }
211
+ .cards { margin-top: 14px; display: flex; flex-direction: column; gap: 12px; }
212
+ .card {
213
+ border: 1px solid var(--line);
214
+ background: linear-gradient(180deg, rgba(17,26,51,0.70), rgba(14,22,48,0.70));
215
+ border-radius: 14px;
216
+ padding: 12px 12px 10px;
217
+ page-break-inside: avoid;
218
+ }
219
+ .card-h {
220
+ display: grid;
221
+ grid-template-columns: auto 1fr auto;
222
+ align-items: center;
223
+ gap: 10px;
224
+ }
225
+ .idx { color: var(--muted); font-weight: 700; }
226
+ .title { font-weight: 800; }
227
+ .badge {
228
+ padding: 4px 8px;
229
+ border-radius: 999px;
230
+ font-size: 11px;
231
+ font-weight: 800;
232
+ border: 1px solid var(--line);
233
+ }
234
+ .badge-critical { background: rgba(255,77,79,0.18); color: #ffd1d1; border-color: rgba(255,77,79,0.45); }
235
+ .badge-high { background: rgba(255,122,69,0.18); color: #ffe1d2; border-color: rgba(255,122,69,0.45); }
236
+ .badge-medium { background: rgba(250,219,20,0.16); color: #fff3bf; border-color: rgba(250,219,20,0.40); }
237
+ .badge-low { background: rgba(64,169,255,0.16); color: #d6ecff; border-color: rgba(64,169,255,0.40); }
238
+ .badge-info { background: rgba(140,140,140,0.16); color: #eee; border-color: rgba(140,140,140,0.35); }
239
+ .badge-warning { background: rgba(250,173,20,0.16); color: #fffbe6; border-color: rgba(250,173,20,0.40); }
240
+ .meta { margin-top: 10px; display: grid; gap: 6px; }
241
+ .section { margin-top: 10px; border-top: 1px dashed rgba(233,238,252,0.20); padding-top: 10px; }
242
+ .section-header {
243
+ display: flex;
244
+ justify-content: space-between;
245
+ align-items: center;
246
+ margin: 24px 0 12px;
247
+ padding-bottom: 8px;
248
+ border-bottom: 1px solid var(--line);
249
+ }
250
+ .section-header h2 {
251
+ margin: 0;
252
+ font-size: 16px;
253
+ color: var(--warning);
254
+ }
255
+ .error-table-container {
256
+ border: 1px solid var(--line);
257
+ border-radius: 12px;
258
+ overflow: hidden;
259
+ background: rgba(17,26,51,0.55);
260
+ }
261
+ .error-table {
262
+ width: 100%;
263
+ border-collapse: collapse;
264
+ font-size: 12px;
265
+ }
266
+ .error-table th {
267
+ background: rgba(17,26,51,0.80);
268
+ padding: 10px 12px;
269
+ text-align: left;
270
+ color: var(--muted);
271
+ font-weight: 600;
272
+ border-bottom: 1px solid var(--line);
273
+ }
274
+ .error-table td {
275
+ padding: 8px 12px;
276
+ border-bottom: 1px solid rgba(233,238,252,0.10);
277
+ color: var(--text);
278
+ }
279
+ .error-table tr:last-child td {
280
+ border-bottom: none;
281
+ }
282
+ .error-table tr:hover td {
283
+ background: rgba(255,255,255,0.03);
284
+ }
285
+ .k { color: var(--muted); font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.6px; }
286
+ .v { margin-top: 6px; font-size: 12px; line-height: 1.45; }
287
+ .pre { white-space: pre-wrap; }
288
+ .footer { margin-top: 18px; color: var(--muted); font-size: 11px; border-top: 1px solid var(--line); padding-top: 12px; }
289
+ </style>
290
+ </head>
291
+ <body>
292
+ <div class="top">
293
+ <div class="brand">
294
+ <div class="h1">KramScan Security Report</div>
295
+ <div class="sub">Target: <span class="mono">${escapeHtml(scanResult.target)}</span></div>
296
+ <div class="sub">Timestamp: <span class="mono">${escapeHtml(scanResult.timestamp)}</span></div>
297
+ </div>
298
+ <div class="pill">Automated PDF generated after scan</div>
299
+ </div>
300
+
301
+ <div class="grid">
302
+ <div class="stat"><div class="k">Total</div><div class="v">${summary.total}</div></div>
303
+ <div class="stat"><div class="k">Critical</div><div class="v">${summary.critical}</div></div>
304
+ <div class="stat"><div class="k">High</div><div class="v">${summary.high}</div></div>
305
+ <div class="stat"><div class="k">Medium</div><div class="v">${summary.medium}</div></div>
306
+ <div class="stat"><div class="k">Low</div><div class="v">${summary.low}</div></div>
307
+ </div>
308
+
309
+ <div class="sub">Crawled URLs: <span class="mono">${scanResult.metadata.crawledUrls}</span> | Forms tested: <span class="mono">${scanResult.metadata.testedForms}</span> | Requests: <span class="mono">${scanResult.metadata.requestsMade}</span> | Duration: <span class="mono">${(scanResult.duration / 1000).toFixed(2)}s</span></div>
310
+
311
+ <div class="cards">
312
+ ${rows || `<div class="card"><div class="title">No vulnerabilities found</div><div class="v">The scanner did not detect issues in the tested scope.</div></div>`}
313
+ </div>
314
+
315
+ ${errorsHtml}
316
+
317
+ <div class="footer">Generated by KramScan</div>
318
+ </body>
319
+ </html>`;
320
+ }
321
+ class PdfGenerator {
322
+ async generate(data, options = {}) {
323
+ const reportsDir = await (0, scan_storage_1.ensureReportsDirectory)();
324
+ const targetUrl = new URL(data.scanResult.target);
325
+ const host = sanitizeFilenamePart(targetUrl.hostname || "unknown");
326
+ const timestamp = new Date(data.scanResult.timestamp || new Date().toISOString())
327
+ .toISOString()
328
+ .replace(/[:.]/g, "-");
329
+ const pdfFilename = options.filename || `scanreport_${host}_${timestamp}.pdf`;
330
+ const pdfPath = path_1.default.join(reportsDir, pdfFilename);
331
+ const browser = await puppeteer_1.default.launch({
332
+ headless: true,
333
+ args: ["--no-sandbox", "--disable-setuid-sandbox"],
334
+ });
335
+ try {
336
+ const page = await browser.newPage();
337
+ const html = buildPdfHtml(data);
338
+ await page.setContent(html, { waitUntil: "networkidle0" });
339
+ await page.pdf({
340
+ path: pdfPath,
341
+ format: options.format || "A4",
342
+ printBackground: true,
343
+ margin: options.margin || {
344
+ top: "12mm",
345
+ bottom: "12mm",
346
+ left: "10mm",
347
+ right: "10mm",
348
+ },
349
+ });
350
+ }
351
+ finally {
352
+ await browser.close();
353
+ }
354
+ return pdfPath;
355
+ }
356
+ /**
357
+ * Generate a standalone HTML report
358
+ * @param data - The scan result data
359
+ * @param options - Options for HTML generation
360
+ * @returns Path to the generated HTML file
361
+ */
362
+ async generateHtml(data, options = {}) {
363
+ const reportsDir = await (0, scan_storage_1.ensureReportsDirectory)();
364
+ const targetUrl = new URL(data.scanResult.target);
365
+ const host = sanitizeFilenamePart(targetUrl.hostname || "unknown");
366
+ const timestamp = new Date(data.scanResult.timestamp || new Date().toISOString())
367
+ .toISOString()
368
+ .replace(/[:.]/g, "-");
369
+ const htmlFilename = options.filename || `scanreport_${host}_${timestamp}.html`;
370
+ const htmlPath = path_1.default.join(reportsDir, htmlFilename);
371
+ const html = buildPdfHtml(data);
372
+ // Write the HTML file
373
+ const fs = await Promise.resolve().then(() => __importStar(require("fs/promises")));
374
+ await fs.writeFile(htmlPath, html, "utf-8");
375
+ return htmlPath;
376
+ }
377
+ }
378
+ exports.PdfGenerator = PdfGenerator;
379
+ exports.pdfGenerator = new PdfGenerator();
@@ -1,9 +1,41 @@
1
1
  import { Ora } from "ora";
2
+ export declare enum LogLevel {
3
+ DEBUG = 0,
4
+ INFO = 1,
5
+ SUCCESS = 2,
6
+ WARN = 3,
7
+ ERROR = 4
8
+ }
9
+ export interface LogEntry {
10
+ timestamp: string;
11
+ level: string;
12
+ message: string;
13
+ context?: Record<string, unknown>;
14
+ }
15
+ export interface LoggerOptions {
16
+ level?: LogLevel;
17
+ jsonOutput?: boolean;
18
+ includeTimestamp?: boolean;
19
+ includeContext?: boolean;
20
+ }
2
21
  export declare const logger: {
3
22
  info: (message: string) => void;
4
23
  success: (message: string) => void;
5
24
  warn: (message: string) => void;
6
25
  error: (message: string) => void;
7
- debug: (message: string) => void;
26
+ debug: (message: string, context?: Record<string, unknown>) => void;
8
27
  spinner: (text: string) => Ora;
9
28
  };
29
+ export declare const structuredLogger: {
30
+ debug: (message: string, context?: Record<string, unknown>) => void;
31
+ info: (message: string, context?: Record<string, unknown>) => void;
32
+ warn: (message: string, context?: Record<string, unknown>) => void;
33
+ error: (message: string, context?: Record<string, unknown>) => void;
34
+ log: (level: LogLevel, message: string, context?: Record<string, unknown>) => void;
35
+ };
36
+ export declare function createChildLogger(defaultContext: Record<string, unknown>): {
37
+ debug: (message: string, context?: Record<string, unknown>) => void;
38
+ info: (message: string, context?: Record<string, unknown>) => void;
39
+ warn: (message: string, context?: Record<string, unknown>) => void;
40
+ error: (message: string, context?: Record<string, unknown>) => void;
41
+ };
@@ -3,25 +3,97 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.logger = void 0;
6
+ exports.structuredLogger = exports.logger = exports.LogLevel = void 0;
7
+ exports.createChildLogger = createChildLogger;
7
8
  const chalk_1 = __importDefault(require("chalk"));
8
9
  const ora_1 = __importDefault(require("ora"));
10
+ var LogLevel;
11
+ (function (LogLevel) {
12
+ LogLevel[LogLevel["DEBUG"] = 0] = "DEBUG";
13
+ LogLevel[LogLevel["INFO"] = 1] = "INFO";
14
+ LogLevel[LogLevel["SUCCESS"] = 2] = "SUCCESS";
15
+ LogLevel[LogLevel["WARN"] = 3] = "WARN";
16
+ LogLevel[LogLevel["ERROR"] = 4] = "ERROR";
17
+ })(LogLevel || (exports.LogLevel = LogLevel = {}));
18
+ function getLogLevel() {
19
+ const envLevel = process.env.LOG_LEVEL?.toUpperCase();
20
+ switch (envLevel) {
21
+ case "DEBUG":
22
+ return LogLevel.DEBUG;
23
+ case "INFO":
24
+ return LogLevel.INFO;
25
+ case "WARN":
26
+ case "WARNING":
27
+ return LogLevel.WARN;
28
+ case "ERROR":
29
+ return LogLevel.ERROR;
30
+ default:
31
+ return process.env.KRAMSCAN_DEBUG === "1" || process.env.KRAMSCAN_DEBUG === "true"
32
+ ? LogLevel.DEBUG
33
+ : LogLevel.INFO;
34
+ }
35
+ }
36
+ function debugEnabled() {
37
+ return getLogLevel() <= LogLevel.DEBUG;
38
+ }
39
+ function isJsonOutput() {
40
+ return process.env.LOG_JSON === "1" || process.env.LOG_JSON === "true";
41
+ }
42
+ function shouldLog(level) {
43
+ return level >= getLogLevel();
44
+ }
45
+ function formatLogEntry(level, message, context) {
46
+ return {
47
+ timestamp: new Date().toISOString(),
48
+ level,
49
+ message,
50
+ context,
51
+ };
52
+ }
53
+ function outputLog(entry) {
54
+ if (isJsonOutput()) {
55
+ console.log(JSON.stringify(entry));
56
+ }
57
+ else {
58
+ const { timestamp, level, message, ...rest } = entry;
59
+ let output = message;
60
+ if (process.env.LOG_INCLUDE_CONTEXT === "true" && Object.keys(rest).length > 0) {
61
+ output += ` ${JSON.stringify(rest)}`;
62
+ }
63
+ console.log(output);
64
+ }
65
+ }
66
+ // Simple human-readable logger (original behavior)
9
67
  exports.logger = {
10
68
  info: (message) => {
11
- console.log(chalk_1.default.blue("ℹ"), message);
69
+ if (shouldLog(LogLevel.INFO)) {
70
+ console.log(chalk_1.default.blue("i"), message);
71
+ }
12
72
  },
13
73
  success: (message) => {
14
- console.log(chalk_1.default.green("✓"), message);
74
+ if (shouldLog(LogLevel.SUCCESS)) {
75
+ console.log(chalk_1.default.green("✓"), message);
76
+ }
15
77
  },
16
78
  warn: (message) => {
17
- console.log(chalk_1.default.yellow("⚠"), message);
79
+ if (shouldLog(LogLevel.WARN)) {
80
+ console.log(chalk_1.default.yellow("⚠"), message);
81
+ }
18
82
  },
19
83
  error: (message) => {
20
- console.log(chalk_1.default.red("✗"), message);
84
+ if (shouldLog(LogLevel.ERROR)) {
85
+ console.log(chalk_1.default.red("✗"), message);
86
+ }
21
87
  },
22
- debug: (message) => {
23
- if (process.env.DEBUG) {
24
- console.log(chalk_1.default.gray("→"), message);
88
+ debug: (message, context) => {
89
+ if (debugEnabled()) {
90
+ if (isJsonOutput()) {
91
+ const entry = formatLogEntry("DEBUG", message, context);
92
+ console.log(JSON.stringify(entry));
93
+ }
94
+ else {
95
+ console.log(chalk_1.default.gray("→"), message, context ? JSON.stringify(context) : "");
96
+ }
25
97
  }
26
98
  },
27
99
  spinner: (text) => {
@@ -32,3 +104,50 @@ exports.logger = {
32
104
  }).start();
33
105
  },
34
106
  };
107
+ // Structured JSON logger for log aggregation systems
108
+ exports.structuredLogger = {
109
+ debug: (message, context) => {
110
+ if (shouldLog(LogLevel.DEBUG)) {
111
+ outputLog(formatLogEntry("DEBUG", message, context));
112
+ }
113
+ },
114
+ info: (message, context) => {
115
+ if (shouldLog(LogLevel.INFO)) {
116
+ outputLog(formatLogEntry("INFO", message, context));
117
+ }
118
+ },
119
+ warn: (message, context) => {
120
+ if (shouldLog(LogLevel.WARN)) {
121
+ outputLog(formatLogEntry("WARN", message, context));
122
+ }
123
+ },
124
+ error: (message, context) => {
125
+ if (shouldLog(LogLevel.ERROR)) {
126
+ outputLog(formatLogEntry("ERROR", message, context));
127
+ }
128
+ },
129
+ // Log with custom level
130
+ log: (level, message, context) => {
131
+ if (shouldLog(level)) {
132
+ const levelName = LogLevel[level];
133
+ outputLog(formatLogEntry(levelName, message, context));
134
+ }
135
+ },
136
+ };
137
+ // Convenience function to create a child logger with additional context
138
+ function createChildLogger(defaultContext) {
139
+ return {
140
+ debug: (message, context) => {
141
+ exports.structuredLogger.debug(message, { ...defaultContext, ...context });
142
+ },
143
+ info: (message, context) => {
144
+ exports.structuredLogger.info(message, { ...defaultContext, ...context });
145
+ },
146
+ warn: (message, context) => {
147
+ exports.structuredLogger.warn(message, { ...defaultContext, ...context });
148
+ },
149
+ error: (message, context) => {
150
+ exports.structuredLogger.error(message, { ...defaultContext, ...context });
151
+ },
152
+ };
153
+ }
@@ -0,0 +1,55 @@
1
+ export declare const CLI_VERSION: string;
2
+ export declare const theme: {
3
+ brand: import("chalk").ChalkInstance;
4
+ error: import("chalk").ChalkInstance;
5
+ success: import("chalk").ChalkInstance;
6
+ warning: import("chalk").ChalkInstance;
7
+ info: import("chalk").ChalkInstance;
8
+ dim: import("chalk").ChalkInstance;
9
+ gray: import("chalk").ChalkInstance;
10
+ white: import("chalk").ChalkInstance;
11
+ cyan: import("chalk").ChalkInstance;
12
+ brightWhite: import("chalk").ChalkInstance;
13
+ brightCyan: import("chalk").ChalkInstance;
14
+ brightMagenta: import("chalk").ChalkInstance;
15
+ brightBlue: import("chalk").ChalkInstance;
16
+ brightGreen: import("chalk").ChalkInstance;
17
+ brightYellow: import("chalk").ChalkInstance;
18
+ brightRed: import("chalk").ChalkInstance;
19
+ critical: import("chalk").ChalkInstance;
20
+ high: import("chalk").ChalkInstance;
21
+ medium: import("chalk").ChalkInstance;
22
+ low: import("chalk").ChalkInstance;
23
+ infoSeverity: import("chalk").ChalkInstance;
24
+ yellow: import("chalk").ChalkInstance;
25
+ green: import("chalk").ChalkInstance;
26
+ };
27
+ export declare function printBanner(): void;
28
+ export declare function printInfo(): void;
29
+ export declare function getSeverityColor(severity: string): (text: string) => string;
30
+ export declare function displayScanSummary(result: {
31
+ target: string;
32
+ duration: number;
33
+ metadata: {
34
+ crawledUrls: number;
35
+ testedForms: number;
36
+ requestsMade: number;
37
+ };
38
+ summary: {
39
+ total: number;
40
+ critical: number;
41
+ high: number;
42
+ medium: number;
43
+ low: number;
44
+ info: number;
45
+ };
46
+ vulnerabilities: Array<{
47
+ severity: string;
48
+ title: string;
49
+ url: string;
50
+ description: string;
51
+ }>;
52
+ filepath: string;
53
+ pdfPath?: string | null;
54
+ }): void;
55
+ export default theme;