kramscan 0.1.1 → 0.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 (91) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +419 -236
  3. package/dist/agent/confirmation.d.ts +5 -1
  4. package/dist/agent/confirmation.js +29 -9
  5. package/dist/agent/context.js +2 -3
  6. package/dist/agent/orchestrator.d.ts +2 -0
  7. package/dist/agent/orchestrator.js +50 -8
  8. package/dist/agent/prompts/system.d.ts +1 -1
  9. package/dist/agent/prompts/system.js +5 -7
  10. package/dist/agent/skills/health-check.js +22 -2
  11. package/dist/agent/skills/index.d.ts +1 -0
  12. package/dist/agent/skills/index.js +3 -1
  13. package/dist/agent/skills/verify-finding.d.ts +17 -0
  14. package/dist/agent/skills/verify-finding.js +91 -0
  15. package/dist/agent/skills/web-scan.js +46 -0
  16. package/dist/cli.js +156 -149
  17. package/dist/commands/agent.js +38 -38
  18. package/dist/commands/ai.d.ts +2 -0
  19. package/dist/commands/ai.js +112 -0
  20. package/dist/commands/analyze.js +103 -54
  21. package/dist/commands/config.js +55 -29
  22. package/dist/commands/dev.d.ts +2 -0
  23. package/dist/commands/dev.js +236 -0
  24. package/dist/commands/doctor.js +20 -15
  25. package/dist/commands/gate.d.ts +2 -0
  26. package/dist/commands/gate.js +109 -0
  27. package/dist/commands/onboard.js +188 -141
  28. package/dist/commands/report.js +68 -76
  29. package/dist/commands/scan.js +262 -81
  30. package/dist/commands/scans.d.ts +2 -0
  31. package/dist/commands/scans.js +55 -0
  32. package/dist/core/ai-client.d.ts +6 -1
  33. package/dist/core/ai-client.js +80 -12
  34. package/dist/core/ai-payloads.d.ts +17 -0
  35. package/dist/core/ai-payloads.js +54 -0
  36. package/dist/core/config-schema.d.ts +197 -0
  37. package/dist/core/config-schema.js +68 -0
  38. package/dist/core/config-schema.test.d.ts +1 -0
  39. package/dist/core/config-schema.test.js +151 -0
  40. package/dist/core/config.d.ts +8 -31
  41. package/dist/core/config.js +71 -14
  42. package/dist/core/diff-engine.d.ts +12 -0
  43. package/dist/core/diff-engine.js +47 -0
  44. package/dist/core/errors.d.ts +71 -0
  45. package/dist/core/errors.js +162 -0
  46. package/dist/core/scan-index.d.ts +20 -0
  47. package/dist/core/scan-index.js +52 -0
  48. package/dist/core/scan-storage.d.ts +11 -0
  49. package/dist/core/scan-storage.js +69 -0
  50. package/dist/core/scanner.d.ts +95 -13
  51. package/dist/core/scanner.js +342 -248
  52. package/dist/core/server-probe.d.ts +20 -0
  53. package/dist/core/server-probe.js +109 -0
  54. package/dist/core/vulnerability-detector.d.ts +9 -0
  55. package/dist/core/vulnerability-detector.js +46 -15
  56. package/dist/core/vulnerability-detector.test.d.ts +1 -0
  57. package/dist/core/vulnerability-detector.test.js +210 -0
  58. package/dist/index.js +3 -0
  59. package/dist/plugins/PluginManager.d.ts +27 -0
  60. package/dist/plugins/PluginManager.js +166 -0
  61. package/dist/plugins/index.d.ts +12 -0
  62. package/dist/plugins/index.js +29 -0
  63. package/dist/plugins/types.d.ts +55 -0
  64. package/dist/plugins/types.js +25 -0
  65. package/dist/plugins/vulnerabilities/CORSAnalyzerPlugin.d.ts +10 -0
  66. package/dist/plugins/vulnerabilities/CORSAnalyzerPlugin.js +67 -0
  67. package/dist/plugins/vulnerabilities/CSRFPlugin.d.ts +8 -0
  68. package/dist/plugins/vulnerabilities/CSRFPlugin.js +34 -0
  69. package/dist/plugins/vulnerabilities/CookieSecurityPlugin.d.ts +10 -0
  70. package/dist/plugins/vulnerabilities/CookieSecurityPlugin.js +91 -0
  71. package/dist/plugins/vulnerabilities/DebugEndpointPlugin.d.ts +15 -0
  72. package/dist/plugins/vulnerabilities/DebugEndpointPlugin.js +222 -0
  73. package/dist/plugins/vulnerabilities/DirectoryTraversalPlugin.d.ts +13 -0
  74. package/dist/plugins/vulnerabilities/DirectoryTraversalPlugin.js +110 -0
  75. package/dist/plugins/vulnerabilities/OpenRedirectPlugin.d.ts +10 -0
  76. package/dist/plugins/vulnerabilities/OpenRedirectPlugin.js +69 -0
  77. package/dist/plugins/vulnerabilities/SQLInjectionPlugin.d.ts +11 -0
  78. package/dist/plugins/vulnerabilities/SQLInjectionPlugin.js +109 -0
  79. package/dist/plugins/vulnerabilities/SecurityHeadersPlugin.d.ts +11 -0
  80. package/dist/plugins/vulnerabilities/SecurityHeadersPlugin.js +63 -0
  81. package/dist/plugins/vulnerabilities/SensitiveDataPlugin.d.ts +9 -0
  82. package/dist/plugins/vulnerabilities/SensitiveDataPlugin.js +32 -0
  83. package/dist/plugins/vulnerabilities/XSSPlugin.d.ts +15 -0
  84. package/dist/plugins/vulnerabilities/XSSPlugin.js +81 -0
  85. package/dist/reports/PdfGenerator.d.ts +36 -0
  86. package/dist/reports/PdfGenerator.js +404 -0
  87. package/dist/utils/logger.d.ts +33 -1
  88. package/dist/utils/logger.js +127 -8
  89. package/dist/utils/theme.d.ts +56 -0
  90. package/dist/utils/theme.js +201 -0
  91. package/package.json +6 -3
@@ -1,124 +1,305 @@
1
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
+ })();
2
35
  var __importDefault = (this && this.__importDefault) || function (mod) {
3
36
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
37
  };
5
38
  Object.defineProperty(exports, "__esModule", { value: true });
6
39
  exports.registerScanCommand = registerScanCommand;
7
- const chalk_1 = __importDefault(require("chalk"));
40
+ const commander_1 = require("commander");
8
41
  const scanner_1 = require("../core/scanner");
42
+ const scan_index_1 = require("../core/scan-index");
43
+ const scan_storage_1 = require("../core/scan-storage");
44
+ const config_1 = require("../core/config");
45
+ const PdfGenerator_1 = require("../reports/PdfGenerator");
46
+ const theme_1 = require("../utils/theme");
9
47
  const logger_1 = require("../utils/logger");
10
48
  const promises_1 = __importDefault(require("fs/promises"));
11
49
  const path_1 = __importDefault(require("path"));
12
- const os_1 = __importDefault(require("os"));
13
50
  function registerScanCommand(program) {
14
51
  program
15
52
  .command("scan <url>")
16
53
  .description("Scan a target URL for vulnerabilities")
17
- .option("-d, --depth <number>", "Crawl depth", "2")
18
- .option("-t, --timeout <ms>", "Request timeout", "30000")
54
+ .option("--profile <name>", "Scan profile: quick|balanced|deep", "balanced")
55
+ .option("-d, --depth <number>", "Crawl depth (overrides profile)")
56
+ .option("-t, --timeout <ms>", "Request timeout (overrides profile)")
57
+ .option("--max-pages <number>", "Maximum pages to crawl (overrides profile)")
58
+ .option("--max-links-per-page <number>", "Maximum links to follow per page (overrides profile)")
59
+ .option("--include <regex...>", "Only include URLs matching these regex patterns")
60
+ .option("--exclude <regex...>", "Exclude URLs matching these regex patterns")
61
+ .option("--no-pdf", "Disable automatic PDF report generation")
62
+ .option("--json", "Output scan results as JSON to stdout (CI/CD mode)")
19
63
  .option("-o, --output <file>", "Save results to file")
20
64
  .option("--headless", "Run in headless mode", true)
65
+ .option("--no-plugins", "Disable plugin-based scanning (use legacy mode)")
21
66
  .action(async (url, options) => {
22
- console.log("");
23
- console.log(chalk_1.default.bold.cyan("🔍 Starting Security Scan"));
24
- console.log(chalk_1.default.gray("".repeat(50)));
25
- console.log("");
67
+ const jsonMode = options.json === true;
68
+ if (!jsonMode) {
69
+ console.log("");
70
+ console.log(theme_1.theme.brand.bold("🔍 Starting Security Scan"));
71
+ console.log(theme_1.theme.gray("─".repeat(50)));
72
+ console.log("");
73
+ }
26
74
  // Validate URL
27
75
  try {
28
76
  new URL(url);
29
77
  }
30
78
  catch (error) {
31
- logger_1.logger.error(`Invalid URL: ${url}`);
79
+ if (jsonMode) {
80
+ console.log(JSON.stringify({ error: `Invalid URL: ${url}` }));
81
+ }
82
+ else {
83
+ logger_1.logger.error(`Invalid URL: ${url}`);
84
+ }
32
85
  process.exit(1);
33
86
  }
34
- const spinner = logger_1.logger.spinner("Initializing scanner...");
87
+ const spinner = jsonMode ? null : logger_1.logger.spinner("Initializing scanner...");
35
88
  try {
36
- const scanner = new scanner_1.Scanner();
37
- spinner.text = `Scanning ${url}...`;
38
- const result = await scanner.scan(url, {
39
- depth: parseInt(options.depth),
40
- timeout: parseInt(options.timeout),
41
- headless: options.headless,
89
+ const profile = String(options.profile || "balanced").toLowerCase();
90
+ const defaults = config_1.scanProfiles[profile] || config_1.scanProfiles.balanced;
91
+ const parsedDepth = Number.parseInt(options.depth ?? String(defaults.depth), 10);
92
+ const parsedTimeout = Number.parseInt(options.timeout ?? String(defaults.timeout), 10);
93
+ const parsedMaxPages = Number.parseInt(options.maxPages ?? String(defaults.maxPages), 10);
94
+ const parsedMaxLinksPerPage = Number.parseInt(options.maxLinksPerPage ?? String(defaults.maxLinksPerPage), 10);
95
+ if (!Number.isFinite(parsedDepth) || parsedDepth < 1 || parsedDepth > 5) {
96
+ throw new Error("Depth must be a number between 1 and 5.");
97
+ }
98
+ if (!Number.isFinite(parsedTimeout) || parsedTimeout < 1000) {
99
+ throw new Error("Timeout must be a positive number (milliseconds).");
100
+ }
101
+ if (!Number.isFinite(parsedMaxPages) || parsedMaxPages < 1) {
102
+ throw new Error("max-pages must be a positive number.");
103
+ }
104
+ if (!Number.isFinite(parsedMaxLinksPerPage) || parsedMaxLinksPerPage < 1) {
105
+ throw new Error("max-links-per-page must be a positive number.");
106
+ }
107
+ // Display scan estimate
108
+ if (!jsonMode) {
109
+ const estimateMap = {
110
+ quick: "~15–30s",
111
+ balanced: "~30–90s",
112
+ deep: "~2–5min",
113
+ };
114
+ const estimate = estimateMap[profile] || estimateMap.balanced;
115
+ console.log(theme_1.theme.gray(` ⏱ Estimated duration: ${estimate} (${profile} profile)`));
116
+ console.log("");
117
+ }
118
+ const scanner = new scanner_1.Scanner(options.plugins !== false);
119
+ // Set up event listeners for progress feedback
120
+ let currentStage = "initializing";
121
+ let vulnerabilitiesFound = 0;
122
+ scanner.on("scan:start", () => {
123
+ if (spinner)
124
+ spinner.text = `Starting scan of ${url}...`;
125
+ currentStage = "scanning";
126
+ });
127
+ scanner.on("crawl:page", (data) => {
128
+ if (spinner)
129
+ spinner.text = `Crawling: ${data.url} (${data.crawledCount}/${data.maxPages})`;
130
+ currentStage = "crawling";
131
+ });
132
+ scanner.on("form:test", (data) => {
133
+ if (spinner)
134
+ spinner.text = `Testing forms on ${data.url} (${data.formCount} forms)...`;
135
+ currentStage = "testing forms";
42
136
  });
43
- spinner.succeed("Scan complete!");
137
+ scanner.on("vuln:found", (data) => {
138
+ vulnerabilitiesFound++;
139
+ if (spinner) {
140
+ spinner.stopAndPersist({
141
+ symbol: theme_1.theme.warning("⚠️"),
142
+ text: `Found ${data.vulnerability.severity} vulnerability: ${data.vulnerability.title}`
143
+ });
144
+ spinner.start(`Continuing scan (${vulnerabilitiesFound} vulns found)...`);
145
+ }
146
+ });
147
+ scanner.on("scan:complete", () => {
148
+ if (spinner)
149
+ spinner.text = "Finalizing scan results...";
150
+ });
151
+ scanner.on("crawl:error", (data) => {
152
+ if (!jsonMode)
153
+ logger_1.logger.warn(`Failed to crawl ${data.url}: ${data.error.message}`);
154
+ });
155
+ const scanOptions = {
156
+ depth: parsedDepth,
157
+ timeout: parsedTimeout,
158
+ headless: options.headless,
159
+ maxPages: parsedMaxPages,
160
+ maxLinksPerPage: parsedMaxLinksPerPage,
161
+ include: options.include,
162
+ exclude: options.exclude,
163
+ profile,
164
+ };
165
+ const result = await scanner.scan(url, scanOptions);
166
+ if (spinner)
167
+ spinner.succeed("Scan complete!");
44
168
  // Save results
45
- const scanDir = path_1.default.join(os_1.default.homedir(), ".kramscan", "scans");
46
- await promises_1.default.mkdir(scanDir, { recursive: true });
169
+ const scanDir = await (0, scan_storage_1.ensureScansDirectory)();
47
170
  const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
48
171
  const filename = options.output || `scan-${timestamp}.json`;
49
172
  const filepath = path_1.default.isAbsolute(filename)
50
173
  ? filename
51
174
  : path_1.default.join(scanDir, filename);
52
- await promises_1.default.writeFile(filepath, JSON.stringify(result, null, 2));
53
- // Display summary
54
- console.log("");
55
- console.log(chalk_1.default.bold("📊 Scan Summary"));
56
- console.log(chalk_1.default.gray("─".repeat(50)));
57
- console.log("");
58
- console.log(chalk_1.default.white("Target:"), chalk_1.default.cyan(result.target));
59
- console.log(chalk_1.default.white("Duration:"), chalk_1.default.cyan(`${(result.duration / 1000).toFixed(2)}s`));
60
- console.log(chalk_1.default.white("URLs Crawled:"), chalk_1.default.cyan(result.metadata.crawledUrls));
61
- console.log(chalk_1.default.white("Forms Tested:"), chalk_1.default.cyan(result.metadata.testedForms));
62
- console.log(chalk_1.default.white("Requests Made:"), chalk_1.default.cyan(result.metadata.requestsMade));
63
- console.log("");
64
- // Vulnerability summary
65
- console.log(chalk_1.default.bold("🛡️ Vulnerabilities Found"));
66
- console.log(chalk_1.default.gray("─".repeat(50)));
67
- console.log("");
68
- const { summary } = result;
69
- if (summary.total === 0) {
70
- console.log(chalk_1.default.green("✓ No vulnerabilities found!"));
175
+ // Include error data in JSON
176
+ const scanErrors = scanner.getScanErrors();
177
+ const pluginErrors = scanner.getPluginErrors();
178
+ const resultWithErrors = {
179
+ ...result,
180
+ errors: {
181
+ scan: scanErrors,
182
+ plugins: Object.fromEntries(pluginErrors),
183
+ },
184
+ };
185
+ await promises_1.default.writeFile(filepath, JSON.stringify(resultWithErrors, null, 2));
186
+ // In JSON mode, output the result to stdout and exit
187
+ if (jsonMode) {
188
+ console.log(JSON.stringify(resultWithErrors, null, 2));
189
+ return;
71
190
  }
72
- else {
73
- if (summary.critical > 0)
74
- console.log(chalk_1.default.red(` ${summary.critical} Critical`), chalk_1.default.gray("- Immediate action required"));
75
- if (summary.high > 0)
76
- console.log(chalk_1.default.red(` ${summary.high} High`), chalk_1.default.gray("- Should be fixed soon"));
77
- if (summary.medium > 0)
78
- console.log(chalk_1.default.yellow(` ${summary.medium} Medium`), chalk_1.default.gray("- Fix when possible"));
79
- if (summary.low > 0)
80
- console.log(chalk_1.default.blue(` ${summary.low} Low`), chalk_1.default.gray("- Minor issues"));
81
- if (summary.info > 0)
82
- console.log(chalk_1.default.gray(` ${summary.info} Info`), chalk_1.default.gray("- Informational"));
191
+ let pdfPath = null;
192
+ if (options.pdf !== false) {
193
+ const pdfSpinner = logger_1.logger.spinner("Generating PDF report...");
194
+ try {
195
+ const pdfData = {
196
+ scanResult: result,
197
+ scanErrors,
198
+ pluginErrors,
199
+ };
200
+ pdfPath = await PdfGenerator_1.pdfGenerator.generate(pdfData);
201
+ pdfSpinner.succeed("PDF report generated!");
202
+ }
203
+ catch (error) {
204
+ pdfSpinner.fail("PDF report generation failed");
205
+ logger_1.logger.warn(`Could not generate PDF automatically: ${error.message}`);
206
+ }
83
207
  }
84
- console.log("");
85
- console.log(chalk_1.default.gray("Results saved to:"), chalk_1.default.white(filepath));
86
- console.log("");
87
- // Show top vulnerabilities
88
- if (result.vulnerabilities.length > 0) {
89
- console.log(chalk_1.default.bold("🔴 Top Findings"));
90
- console.log(chalk_1.default.gray("─".repeat(50)));
208
+ try {
209
+ const target = new URL(result.target);
210
+ await (0, scan_index_1.addScanToIndex)({
211
+ target: result.target,
212
+ hostname: target.hostname || "unknown",
213
+ timestamp: result.timestamp,
214
+ jsonPath: filepath,
215
+ pdfPath: pdfPath || undefined,
216
+ summary: result.summary,
217
+ });
218
+ }
219
+ catch (error) {
220
+ logger_1.logger.debug(`Failed to update scan index: ${error.message}`);
221
+ }
222
+ // Display summary using theme
223
+ (0, theme_1.displayScanSummary)({
224
+ target: result.target,
225
+ duration: result.duration,
226
+ metadata: result.metadata,
227
+ summary: result.summary,
228
+ vulnerabilities: result.vulnerabilities,
229
+ score: result.score,
230
+ filepath,
231
+ pdfPath,
232
+ });
233
+ // Display any scan errors
234
+ const scanErrorsList = scanner.getScanErrors();
235
+ const pluginErrorsMap = scanner.getPluginErrors();
236
+ if (scanErrorsList.length > 0 || pluginErrorsMap.size > 0) {
237
+ console.log(theme_1.theme.warning("⚠️ Some URLs/plugins encountered errors:"));
238
+ if (scanErrorsList.length > 0) {
239
+ console.log(theme_1.theme.yellow(" Crawl Errors:"));
240
+ for (const error of scanErrorsList.slice(0, 5)) {
241
+ console.log(theme_1.theme.gray(` - ${error.url}: ${error.error}`));
242
+ }
243
+ if (scanErrorsList.length > 5) {
244
+ console.log(theme_1.theme.gray(` ... and ${scanErrorsList.length - 5} more`));
245
+ }
246
+ }
247
+ if (pluginErrorsMap.size > 0) {
248
+ console.log(theme_1.theme.yellow(" Plugin Errors:"));
249
+ for (const [pluginName, errors] of pluginErrorsMap) {
250
+ console.log(theme_1.theme.gray(` ${pluginName}:`));
251
+ for (const error of errors.slice(0, 3)) {
252
+ console.log(theme_1.theme.gray(` - ${error.url}: ${error.error}`));
253
+ }
254
+ if (errors.length > 3) {
255
+ console.log(theme_1.theme.gray(` ... and ${errors.length - 3} more`));
256
+ }
257
+ }
258
+ const totalPluginErrors = Array.from(pluginErrorsMap.values()).reduce((sum, errs) => sum + errs.length, 0);
259
+ if (totalPluginErrors > 10) {
260
+ console.log(theme_1.theme.gray(` Total plugin errors: ${totalPluginErrors}`));
261
+ }
262
+ }
91
263
  console.log("");
92
- const topVulns = result.vulnerabilities
93
- .sort((a, b) => {
94
- const severityOrder = { critical: 0, high: 1, medium: 2, low: 3, info: 4 };
95
- return severityOrder[a.severity] - severityOrder[b.severity];
96
- })
97
- .slice(0, 5);
98
- for (const vuln of topVulns) {
99
- const severityColor = vuln.severity === "critical" || vuln.severity === "high"
100
- ? chalk_1.default.red
101
- : vuln.severity === "medium"
102
- ? chalk_1.default.yellow
103
- : chalk_1.default.blue;
104
- console.log(severityColor(`[${vuln.severity.toUpperCase()}]`), chalk_1.default.bold(vuln.title));
105
- console.log(chalk_1.default.gray(` ${vuln.url}`));
106
- console.log(chalk_1.default.white(` ${vuln.description}`));
107
- console.log("");
264
+ }
265
+ // Add "What's Next" interactive prompt
266
+ if (!jsonMode) {
267
+ const inquirer = (await Promise.resolve().then(() => __importStar(require("inquirer")))).default;
268
+ const { nextAction } = await inquirer.prompt([
269
+ {
270
+ type: "list",
271
+ name: "nextAction",
272
+ message: theme_1.theme.cyan("Scan complete! What would you like to do next?"),
273
+ choices: [
274
+ { name: "🧠 Analyze findings with AI", value: "analyze" },
275
+ { name: "📄 Generate a professional report", value: "report" },
276
+ { name: "👋 Exit to main menu", value: "exit" }
277
+ ]
278
+ }
279
+ ]);
280
+ if (nextAction === "analyze") {
281
+ const { registerAnalyzeCommand } = await Promise.resolve().then(() => __importStar(require("./analyze")));
282
+ const analyzeProgram = new commander_1.Command();
283
+ registerAnalyzeCommand(analyzeProgram);
284
+ await analyzeProgram.parseAsync(["node", "kramscan", "analyze", filepath]);
108
285
  }
109
- if (result.vulnerabilities.length > 5) {
110
- console.log(chalk_1.default.gray(` ... and ${result.vulnerabilities.length - 5} more`));
111
- console.log("");
286
+ else if (nextAction === "report") {
287
+ const { registerReportCommand } = await Promise.resolve().then(() => __importStar(require("./report")));
288
+ const reportProgram = new commander_1.Command();
289
+ registerReportCommand(reportProgram);
290
+ await reportProgram.parseAsync(["node", "kramscan", "report", filepath]);
112
291
  }
113
292
  }
114
- console.log(chalk_1.default.cyan("💡 Next steps:"));
115
- console.log(chalk_1.default.white(` 1. Run ${chalk_1.default.cyan(`kramscan analyze ${filepath}`)} for AI-powered insights`));
116
- console.log(chalk_1.default.white(` 2. Run ${chalk_1.default.cyan(`kramscan report ${filepath}`)} to generate a report`));
117
- console.log("");
118
293
  }
119
294
  catch (error) {
120
- spinner.fail("Scan failed");
121
- logger_1.logger.error(error.message);
295
+ if (spinner)
296
+ spinner.fail("Scan failed");
297
+ if (jsonMode) {
298
+ console.log(JSON.stringify({ error: error.message }));
299
+ }
300
+ else {
301
+ logger_1.logger.error(error.message);
302
+ }
122
303
  process.exit(1);
123
304
  }
124
305
  });
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare function registerScansCommand(program: Command): void;
@@ -0,0 +1,55 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.registerScansCommand = registerScansCommand;
7
+ const chalk_1 = __importDefault(require("chalk"));
8
+ const scan_index_1 = require("../core/scan-index");
9
+ const logger_1 = require("../utils/logger");
10
+ function registerScansCommand(program) {
11
+ const scans = program.command("scans").description("Manage saved scans");
12
+ scans
13
+ .command("list")
14
+ .description("List recent scans")
15
+ .option("-n, --limit <number>", "How many scans to show", "20")
16
+ .action(async (options) => {
17
+ const limit = Number.parseInt(options.limit, 10);
18
+ const entries = await (0, scan_index_1.listScans)(Number.isFinite(limit) ? limit : 20);
19
+ if (entries.length === 0) {
20
+ logger_1.logger.warn("No scans found in index yet. Run 'kramscan scan <url>' first.");
21
+ return;
22
+ }
23
+ console.log("");
24
+ console.log(chalk_1.default.bold.cyan("Recent Scans"));
25
+ console.log(chalk_1.default.gray("-".repeat(60)));
26
+ for (const entry of entries) {
27
+ console.log(chalk_1.default.white(entry.timestamp), chalk_1.default.gray("-"), chalk_1.default.cyan(entry.hostname));
28
+ console.log(chalk_1.default.gray(" JSON:"), chalk_1.default.white(entry.jsonPath));
29
+ if (entry.pdfPath) {
30
+ console.log(chalk_1.default.gray(" PDF :"), chalk_1.default.white(entry.pdfPath));
31
+ }
32
+ console.log(chalk_1.default.gray(" Findings:"), chalk_1.default.white(`${entry.summary.total} total (${entry.summary.critical}C ${entry.summary.high}H ${entry.summary.medium}M ${entry.summary.low}L ${entry.summary.info}I)`));
33
+ if (entry.score !== undefined) {
34
+ const scoreColor = entry.score > 80 ? chalk_1.default.green : (entry.score > 50 ? chalk_1.default.yellow : chalk_1.default.red);
35
+ console.log(chalk_1.default.gray(" Score :"), scoreColor(`${entry.score}/100`));
36
+ }
37
+ console.log("");
38
+ }
39
+ });
40
+ scans
41
+ .command("latest")
42
+ .description("Show the latest scan paths")
43
+ .action(async () => {
44
+ const latest = await (0, scan_index_1.getLatestScan)();
45
+ if (!latest) {
46
+ logger_1.logger.warn("No scans found in index yet. Run 'kramscan scan <url>' first.");
47
+ return;
48
+ }
49
+ console.log(chalk_1.default.bold("Latest scan:"));
50
+ console.log(chalk_1.default.gray("Target:"), chalk_1.default.cyan(latest.target));
51
+ console.log(chalk_1.default.gray("Time :"), chalk_1.default.white(latest.timestamp));
52
+ console.log(chalk_1.default.gray("JSON :"), chalk_1.default.white(latest.jsonPath));
53
+ console.log(chalk_1.default.gray("PDF :"), chalk_1.default.white(latest.pdfPath || "N/A"));
54
+ });
55
+ }
@@ -1,3 +1,4 @@
1
+ import { ScanResult } from "./vulnerability-detector";
1
2
  export interface AIResponse {
2
3
  content: string;
3
4
  usage?: {
@@ -7,6 +8,10 @@ export interface AIResponse {
7
8
  };
8
9
  }
9
10
  export interface AIClient {
10
- analyze(prompt: string): Promise<AIResponse>;
11
+ analyze(prompt: string, systemPrompt?: string): Promise<AIResponse>;
11
12
  }
12
13
  export declare function createAIClient(): Promise<AIClient>;
14
+ /**
15
+ * Generates a high-level executive summary of the scan results using AI.
16
+ */
17
+ export declare function generateExecutiveSummary(client: AIClient, result: ScanResult): Promise<string>;
@@ -4,6 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.createAIClient = createAIClient;
7
+ exports.generateExecutiveSummary = generateExecutiveSummary;
7
8
  const openai_1 = __importDefault(require("openai"));
8
9
  const sdk_1 = __importDefault(require("@anthropic-ai/sdk"));
9
10
  const generative_ai_1 = require("@google/generative-ai");
@@ -17,6 +18,7 @@ function getApiKeyFromEnv(provider) {
17
18
  mistral: process.env.MISTRAL_API_KEY || "",
18
19
  openrouter: process.env.OPENROUTER_API_KEY || "",
19
20
  kimi: process.env.KIMI_API_KEY || "",
21
+ groq: process.env.GROQ_API_KEY || "",
20
22
  };
21
23
  return envVars[provider] || "";
22
24
  }
@@ -44,6 +46,8 @@ async function createAIClient() {
44
46
  return new MistralClient(apiKey, model);
45
47
  case "kimi":
46
48
  return new KimiClient(apiKey, model);
49
+ case "groq":
50
+ return new GroqClient(apiKey, model);
47
51
  default:
48
52
  throw new Error(`Unsupported AI provider: ${provider}`);
49
53
  }
@@ -55,13 +59,14 @@ class OpenAIClient {
55
59
  this.client = new openai_1.default({ apiKey });
56
60
  this.model = model;
57
61
  }
58
- async analyze(prompt) {
62
+ async analyze(prompt, systemPrompt) {
59
63
  const response = await this.client.chat.completions.create({
60
64
  model: this.model,
61
65
  messages: [
62
66
  {
63
67
  role: "system",
64
- content: "You are a security expert analyzing web application vulnerabilities. Provide detailed, actionable insights.",
68
+ content: systemPrompt ||
69
+ "You are a security expert analyzing web application vulnerabilities. Provide detailed, actionable insights.",
65
70
  },
66
71
  { role: "user", content: prompt },
67
72
  ],
@@ -85,7 +90,7 @@ class AnthropicClient {
85
90
  this.client = new sdk_1.default({ apiKey });
86
91
  this.model = model;
87
92
  }
88
- async analyze(prompt) {
93
+ async analyze(prompt, systemPrompt) {
89
94
  const response = await this.client.messages.create({
90
95
  model: this.model,
91
96
  max_tokens: 4096,
@@ -95,7 +100,8 @@ class AnthropicClient {
95
100
  content: prompt,
96
101
  },
97
102
  ],
98
- system: "You are a security expert analyzing web application vulnerabilities. Provide detailed, actionable insights.",
103
+ system: systemPrompt ||
104
+ "You are a security expert analyzing web application vulnerabilities. Provide detailed, actionable insights.",
99
105
  });
100
106
  const content = response.content[0]?.type === "text" ? response.content[0].text : "";
101
107
  return {
@@ -115,10 +121,10 @@ class GeminiClient {
115
121
  this.client = new generative_ai_1.GoogleGenerativeAI(apiKey);
116
122
  this.model = model;
117
123
  }
118
- async analyze(prompt) {
124
+ async analyze(prompt, systemPrompt) {
119
125
  const generativeModel = this.client.getGenerativeModel({ model: this.model });
120
126
  const result = await generativeModel.generateContent([
121
- "You are a security expert analyzing web application vulnerabilities. Provide detailed, actionable insights.",
127
+ systemPrompt || "You are a security expert analyzing web application vulnerabilities. Provide detailed, actionable insights.",
122
128
  prompt
123
129
  ]);
124
130
  const response = await result.response;
@@ -143,13 +149,13 @@ class OpenRouterClient {
143
149
  });
144
150
  this.model = model;
145
151
  }
146
- async analyze(prompt) {
152
+ async analyze(prompt, systemPrompt) {
147
153
  const response = await this.client.chat.completions.create({
148
154
  model: this.model,
149
155
  messages: [
150
156
  {
151
157
  role: "system",
152
- content: "You are a security expert analyzing web application vulnerabilities. Provide detailed, actionable insights.",
158
+ content: systemPrompt || "You are a security expert analyzing web application vulnerabilities. Provide detailed, actionable insights.",
153
159
  },
154
160
  { role: "user", content: prompt },
155
161
  ],
@@ -173,13 +179,13 @@ class MistralClient {
173
179
  this.client = new mistralai_1.Mistral({ apiKey });
174
180
  this.model = model;
175
181
  }
176
- async analyze(prompt) {
182
+ async analyze(prompt, systemPrompt) {
177
183
  const response = await this.client.chat.complete({
178
184
  model: this.model,
179
185
  messages: [
180
186
  {
181
187
  role: "system",
182
- content: "You are a security expert analyzing web application vulnerabilities. Provide detailed, actionable insights.",
188
+ content: systemPrompt || "You are a security expert analyzing web application vulnerabilities. Provide detailed, actionable insights.",
183
189
  },
184
190
  { role: "user", content: prompt },
185
191
  ],
@@ -207,13 +213,13 @@ class KimiClient {
207
213
  });
208
214
  this.model = model;
209
215
  }
210
- async analyze(prompt) {
216
+ async analyze(prompt, systemPrompt) {
211
217
  const response = await this.client.chat.completions.create({
212
218
  model: this.model,
213
219
  messages: [
214
220
  {
215
221
  role: "system",
216
- content: "You are a security expert analyzing web application vulnerabilities. Provide detailed, actionable insights.",
222
+ content: systemPrompt || "You are a security expert analyzing web application vulnerabilities. Provide detailed, actionable insights.",
217
223
  },
218
224
  { role: "user", content: prompt },
219
225
  ],
@@ -230,3 +236,65 @@ class KimiClient {
230
236
  };
231
237
  }
232
238
  }
239
+ class GroqClient {
240
+ client;
241
+ model;
242
+ constructor(apiKey, model) {
243
+ this.client = new openai_1.default({
244
+ apiKey,
245
+ baseURL: "https://api.groq.com/openai/v1",
246
+ });
247
+ this.model = model;
248
+ }
249
+ async analyze(prompt, systemPrompt) {
250
+ const response = await this.client.chat.completions.create({
251
+ model: this.model,
252
+ messages: [
253
+ {
254
+ role: "system",
255
+ content: systemPrompt || "You are a security expert analyzing web application vulnerabilities. Provide detailed, actionable insights.",
256
+ },
257
+ { role: "user", content: prompt },
258
+ ],
259
+ temperature: 0.3,
260
+ });
261
+ const content = response.choices[0]?.message?.content || "";
262
+ return {
263
+ content,
264
+ usage: {
265
+ promptTokens: response.usage?.prompt_tokens || 0,
266
+ completionTokens: response.usage?.completion_tokens || 0,
267
+ totalTokens: response.usage?.total_tokens || 0,
268
+ },
269
+ };
270
+ }
271
+ }
272
+ /**
273
+ * Generates a high-level executive summary of the scan results using AI.
274
+ */
275
+ async function generateExecutiveSummary(client, result) {
276
+ const vulnSummary = result.vulnerabilities.map(v => `- [${v.severity.toUpperCase()}] ${v.title} on ${v.url}`).join("\n");
277
+ const prompt = `
278
+ Please provide a professional executive summary for a web security scan.
279
+
280
+ Target: ${result.target}
281
+ Scan Date: ${result.timestamp}
282
+ Total Vulnerabilities: ${result.summary.total}
283
+ Critical: ${result.summary.critical}
284
+ High: ${result.summary.high}
285
+ Medium: ${result.summary.medium}
286
+ Low: ${result.summary.low}
287
+
288
+ Detailed Findings:
289
+ ${vulnSummary}
290
+
291
+ The summary should:
292
+ 1. Briefly state the overall security posture.
293
+ 2. Highlight the most critical risks.
294
+ 3. Provide high-level recommendations for management.
295
+ 4. Be concise (max 3-4 paragraphs).
296
+ `;
297
+ const systemPrompt = "You are a senior cybersecurity consultant writing for a non-technical executive audience. Focus on business risk and impact.";
298
+ const response = await client.analyze(prompt, systemPrompt);
299
+ return response.content;
300
+ }