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
@@ -5,60 +5,59 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.registerReportCommand = registerReportCommand;
7
7
  const chalk_1 = __importDefault(require("chalk"));
8
- const logger_1 = require("../utils/logger");
9
- const config_1 = require("../core/config");
8
+ const docx_1 = require("docx");
10
9
  const promises_1 = __importDefault(require("fs/promises"));
11
10
  const path_1 = __importDefault(require("path"));
12
- const os_1 = __importDefault(require("os"));
13
- const docx_1 = require("docx");
11
+ const config_1 = require("../core/config");
12
+ const scan_storage_1 = require("../core/scan-storage");
13
+ const ai_client_1 = require("../core/ai-client");
14
+ const logger_1 = require("../utils/logger");
14
15
  function registerReportCommand(program) {
15
16
  program
16
17
  .command("report [scan-file]")
17
18
  .description("Generate a professional security report")
18
19
  .option("-f, --format <type>", "Report format: word|json|txt")
19
20
  .option("-o, --output <file>", "Output filename")
21
+ .option("--ai-summary", "Generate an AI-powered executive summary")
20
22
  .action(async (scanFile, options) => {
21
23
  console.log("");
22
- console.log(chalk_1.default.bold.cyan("📄 Generating Security Report"));
23
- console.log(chalk_1.default.gray("".repeat(50)));
24
+ console.log(chalk_1.default.bold.cyan("Generating Security Report"));
25
+ console.log(chalk_1.default.gray("-".repeat(50)));
24
26
  console.log("");
27
+ let spinner = null;
25
28
  try {
26
- // Load scan results
27
- let filepath;
28
- if (scanFile) {
29
- filepath = path_1.default.isAbsolute(scanFile)
30
- ? scanFile
31
- : path_1.default.join(process.cwd(), scanFile);
32
- }
33
- else {
34
- // Find latest scan
35
- const scanDir = path_1.default.join(os_1.default.homedir(), ".kramscan", "scans");
36
- const files = await promises_1.default.readdir(scanDir);
37
- const scanFiles = files.filter((f) => f.endsWith(".json"));
38
- if (scanFiles.length === 0) {
39
- logger_1.logger.error("No scan results found. Run 'kramscan scan <url>' first.");
40
- process.exit(1);
41
- }
42
- scanFiles.sort().reverse();
43
- filepath = path_1.default.join(scanDir, scanFiles[0]);
44
- logger_1.logger.info(`Using latest scan: ${scanFiles[0]}`);
29
+ const resolved = await (0, scan_storage_1.resolveScanFile)(scanFile);
30
+ const filepath = resolved.filepath;
31
+ if (resolved.isLatest) {
32
+ logger_1.logger.info(`Using latest scan: ${resolved.filename}`);
45
33
  }
46
34
  const content = await promises_1.default.readFile(filepath, "utf-8");
47
35
  const scanResult = JSON.parse(content);
48
- // Determine format
49
36
  const config = await (0, config_1.getConfig)();
50
- const format = options.format || config.report.defaultFormat;
51
- const spinner = logger_1.logger.spinner(`Generating ${format.toUpperCase()} report...`);
37
+ const format = (options.format || config.report.defaultFormat);
38
+ let aiSummary;
39
+ if (options.aiSummary) {
40
+ spinner = logger_1.logger.spinner("Generating AI executive summary...");
41
+ try {
42
+ const aiClient = await (0, ai_client_1.createAIClient)();
43
+ aiSummary = await (0, ai_client_1.generateExecutiveSummary)(aiClient, scanResult);
44
+ spinner.succeed("AI summary generated!");
45
+ }
46
+ catch (err) {
47
+ spinner.warn(`AI summary failed: ${err.message}`);
48
+ }
49
+ }
50
+ spinner = logger_1.logger.spinner(`Generating ${format.toUpperCase()} report...`);
52
51
  let outputPath;
53
52
  switch (format) {
54
53
  case "word":
55
- outputPath = await generateWordReport(scanResult, options.output);
54
+ outputPath = await generateWordReport(scanResult, options.output, aiSummary);
56
55
  break;
57
56
  case "json":
58
- outputPath = await generateJsonReport(scanResult, options.output);
57
+ outputPath = await generateJsonReport(scanResult, options.output, aiSummary);
59
58
  break;
60
59
  case "txt":
61
- outputPath = await generateTxtReport(scanResult, options.output);
60
+ outputPath = await generateTxtReport(scanResult, options.output, aiSummary);
62
61
  break;
63
62
  default:
64
63
  throw new Error(`Unsupported format: ${format}`);
@@ -69,18 +68,20 @@ function registerReportCommand(program) {
69
68
  console.log("");
70
69
  }
71
70
  catch (error) {
71
+ if (spinner) {
72
+ spinner.fail("Report generation failed");
73
+ }
72
74
  logger_1.logger.error(error.message);
73
75
  process.exit(1);
74
76
  }
75
77
  });
76
78
  }
77
- async function generateWordReport(scanResult, outputFile) {
79
+ async function generateWordReport(scanResult, outputFile, aiSummary) {
78
80
  const doc = new docx_1.Document({
79
81
  sections: [
80
82
  {
81
83
  properties: {},
82
84
  children: [
83
- // Title
84
85
  new docx_1.Paragraph({
85
86
  text: "Security Assessment Report",
86
87
  heading: docx_1.HeadingLevel.HEADING_1,
@@ -95,7 +96,6 @@ async function generateWordReport(scanResult, outputFile) {
95
96
  alignment: docx_1.AlignmentType.CENTER,
96
97
  }),
97
98
  new docx_1.Paragraph({ text: "" }),
98
- // Executive Summary
99
99
  new docx_1.Paragraph({
100
100
  text: "Executive Summary",
101
101
  heading: docx_1.HeadingLevel.HEADING_2,
@@ -103,45 +103,37 @@ async function generateWordReport(scanResult, outputFile) {
103
103
  new docx_1.Paragraph({
104
104
  children: [
105
105
  new docx_1.TextRun({
106
- text: `This report contains the results of an automated security assessment performed on ${scanResult.target}. `,
107
- }),
108
- new docx_1.TextRun({
109
- text: `A total of ${scanResult.summary.total} vulnerabilities were identified, `,
110
- }),
111
- new docx_1.TextRun({
112
- text: `including ${scanResult.summary.critical} critical and ${scanResult.summary.high} high severity issues.`,
106
+ text: aiSummary || `This report contains the results of an automated security assessment performed on ${scanResult.target}. ` +
107
+ `A total of ${scanResult.summary.total} vulnerabilities were identified, ` +
108
+ `including ${scanResult.summary.critical} critical and ${scanResult.summary.high} high severity issues.`,
113
109
  }),
114
110
  ],
115
111
  }),
116
112
  new docx_1.Paragraph({ text: "" }),
117
- // Scan Statistics
118
113
  new docx_1.Paragraph({
119
114
  text: "Scan Statistics",
120
115
  heading: docx_1.HeadingLevel.HEADING_2,
121
116
  }),
122
- new docx_1.Paragraph({ text: `• URLs Crawled: ${scanResult.metadata.crawledUrls}` }),
123
- new docx_1.Paragraph({ text: `• Forms Tested: ${scanResult.metadata.testedForms}` }),
124
- new docx_1.Paragraph({ text: `• Requests Made: ${scanResult.metadata.requestsMade}` }),
117
+ new docx_1.Paragraph({ text: `- URLs Crawled: ${scanResult.metadata.crawledUrls}` }),
118
+ new docx_1.Paragraph({ text: `- Forms Tested: ${scanResult.metadata.testedForms}` }),
119
+ new docx_1.Paragraph({ text: `- Requests Made: ${scanResult.metadata.requestsMade}` }),
125
120
  new docx_1.Paragraph({
126
- text: `• Duration: ${(scanResult.duration / 1000).toFixed(2)} seconds`,
121
+ text: `- Duration: ${(scanResult.duration / 1000).toFixed(2)} seconds`,
127
122
  }),
128
123
  new docx_1.Paragraph({ text: "" }),
129
- // Findings
130
124
  new docx_1.Paragraph({
131
125
  text: "Detailed Findings",
132
126
  heading: docx_1.HeadingLevel.HEADING_2,
133
127
  }),
134
- ...scanResult.vulnerabilities.flatMap((vuln, i) => [
128
+ ...scanResult.vulnerabilities.flatMap((vuln, index) => [
135
129
  new docx_1.Paragraph({
136
- text: `${i + 1}. ${vuln.title} [${vuln.severity.toUpperCase()}]`,
130
+ text: `${index + 1}. ${vuln.title} [${vuln.severity.toUpperCase()}]`,
137
131
  heading: docx_1.HeadingLevel.HEADING_3,
138
132
  }),
139
133
  new docx_1.Paragraph({ text: `URL: ${vuln.url}` }),
140
134
  new docx_1.Paragraph({ text: `Type: ${vuln.type}` }),
141
135
  new docx_1.Paragraph({ text: `Description: ${vuln.description}` }),
142
- ...(vuln.evidence
143
- ? [new docx_1.Paragraph({ text: `Evidence: ${vuln.evidence}` })]
144
- : []),
136
+ ...(vuln.evidence ? [new docx_1.Paragraph({ text: `Evidence: ${vuln.evidence}` })] : []),
145
137
  ...(vuln.remediation
146
138
  ? [new docx_1.Paragraph({ text: `Remediation: ${vuln.remediation}` })]
147
139
  : []),
@@ -153,28 +145,23 @@ async function generateWordReport(scanResult, outputFile) {
153
145
  ],
154
146
  });
155
147
  const buffer = await docx_1.Packer.toBuffer(doc);
156
- const reportsDir = path_1.default.join(os_1.default.homedir(), ".kramscan", "reports");
157
- await promises_1.default.mkdir(reportsDir, { recursive: true });
148
+ const reportsDir = await (0, scan_storage_1.ensureReportsDirectory)();
158
149
  const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
159
150
  const filename = outputFile || `report-${timestamp}.docx`;
160
- const filepath = path_1.default.isAbsolute(filename)
161
- ? filename
162
- : path_1.default.join(reportsDir, filename);
151
+ const filepath = path_1.default.isAbsolute(filename) ? filename : path_1.default.join(reportsDir, filename);
163
152
  await promises_1.default.writeFile(filepath, buffer);
164
153
  return filepath;
165
154
  }
166
- async function generateJsonReport(scanResult, outputFile) {
167
- const reportsDir = path_1.default.join(os_1.default.homedir(), ".kramscan", "reports");
168
- await promises_1.default.mkdir(reportsDir, { recursive: true });
155
+ async function generateJsonReport(scanResult, outputFile, aiSummary) {
156
+ const reportsDir = await (0, scan_storage_1.ensureReportsDirectory)();
169
157
  const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
170
158
  const filename = outputFile || `report-${timestamp}.json`;
171
- const filepath = path_1.default.isAbsolute(filename)
172
- ? filename
173
- : path_1.default.join(reportsDir, filename);
174
- await promises_1.default.writeFile(filepath, JSON.stringify(scanResult, null, 2));
159
+ const filepath = path_1.default.isAbsolute(filename) ? filename : path_1.default.join(reportsDir, filename);
160
+ const finalResult = aiSummary ? { ...scanResult, aiSummary } : scanResult;
161
+ await promises_1.default.writeFile(filepath, JSON.stringify(finalResult, null, 2));
175
162
  return filepath;
176
163
  }
177
- async function generateTxtReport(scanResult, outputFile) {
164
+ async function generateTxtReport(scanResult, outputFile, aiSummary) {
178
165
  const lines = [];
179
166
  lines.push("=".repeat(60));
180
167
  lines.push("SECURITY ASSESSMENT REPORT");
@@ -186,7 +173,12 @@ async function generateTxtReport(scanResult, outputFile) {
186
173
  lines.push("");
187
174
  lines.push("EXECUTIVE SUMMARY");
188
175
  lines.push("-".repeat(60));
189
- lines.push(`Total Vulnerabilities: ${scanResult.summary.total} (${scanResult.summary.critical} Critical, ${scanResult.summary.high} High, ${scanResult.summary.medium} Medium, ${scanResult.summary.low} Low, ${scanResult.summary.info} Info)`);
176
+ if (aiSummary) {
177
+ lines.push(aiSummary);
178
+ }
179
+ else {
180
+ lines.push(`Total Vulnerabilities: ${scanResult.summary.total} (${scanResult.summary.critical} Critical, ${scanResult.summary.high} High, ${scanResult.summary.medium} Medium, ${scanResult.summary.low} Low, ${scanResult.summary.info} Info)`);
181
+ }
190
182
  lines.push("");
191
183
  lines.push("SCAN STATISTICS");
192
184
  lines.push("-".repeat(60));
@@ -197,29 +189,29 @@ async function generateTxtReport(scanResult, outputFile) {
197
189
  lines.push("DETAILED FINDINGS");
198
190
  lines.push("-".repeat(60));
199
191
  lines.push("");
200
- scanResult.vulnerabilities.forEach((vuln, i) => {
201
- lines.push(`${i + 1}. ${vuln.title} [${vuln.severity.toUpperCase()}]`);
192
+ scanResult.vulnerabilities.forEach((vuln, index) => {
193
+ lines.push(`${index + 1}. ${vuln.title} [${vuln.severity.toUpperCase()}]`);
202
194
  lines.push(` URL: ${vuln.url}`);
203
195
  lines.push(` Type: ${vuln.type}`);
204
196
  lines.push(` Description: ${vuln.description}`);
205
- if (vuln.evidence)
197
+ if (vuln.evidence) {
206
198
  lines.push(` Evidence: ${vuln.evidence}`);
207
- if (vuln.remediation)
199
+ }
200
+ if (vuln.remediation) {
208
201
  lines.push(` Remediation: ${vuln.remediation}`);
209
- if (vuln.cwe)
202
+ }
203
+ if (vuln.cwe) {
210
204
  lines.push(` CWE: ${vuln.cwe}`);
205
+ }
211
206
  lines.push("");
212
207
  });
213
208
  lines.push("=".repeat(60));
214
209
  lines.push("End of Report");
215
210
  lines.push("=".repeat(60));
216
- const reportsDir = path_1.default.join(os_1.default.homedir(), ".kramscan", "reports");
217
- await promises_1.default.mkdir(reportsDir, { recursive: true });
211
+ const reportsDir = await (0, scan_storage_1.ensureReportsDirectory)();
218
212
  const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
219
213
  const filename = outputFile || `report-${timestamp}.txt`;
220
- const filepath = path_1.default.isAbsolute(filename)
221
- ? filename
222
- : path_1.default.join(reportsDir, filename);
214
+ const filepath = path_1.default.isAbsolute(filename) ? filename : path_1.default.join(reportsDir, filename);
223
215
  await promises_1.default.writeFile(filepath, lines.join("\n"));
224
216
  return filepath;
225
217
  }