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.
- package/LICENSE +1 -1
- package/README.md +419 -236
- package/dist/agent/confirmation.d.ts +5 -1
- package/dist/agent/confirmation.js +29 -9
- package/dist/agent/context.js +2 -3
- package/dist/agent/orchestrator.d.ts +2 -0
- package/dist/agent/orchestrator.js +50 -8
- package/dist/agent/prompts/system.d.ts +1 -1
- package/dist/agent/prompts/system.js +5 -7
- package/dist/agent/skills/health-check.js +22 -2
- package/dist/agent/skills/index.d.ts +1 -0
- package/dist/agent/skills/index.js +3 -1
- package/dist/agent/skills/verify-finding.d.ts +17 -0
- package/dist/agent/skills/verify-finding.js +91 -0
- package/dist/agent/skills/web-scan.js +46 -0
- package/dist/cli.js +156 -149
- package/dist/commands/agent.js +38 -38
- package/dist/commands/ai.d.ts +2 -0
- package/dist/commands/ai.js +112 -0
- package/dist/commands/analyze.js +103 -54
- package/dist/commands/config.js +55 -29
- package/dist/commands/dev.d.ts +2 -0
- package/dist/commands/dev.js +236 -0
- package/dist/commands/doctor.js +20 -15
- package/dist/commands/gate.d.ts +2 -0
- package/dist/commands/gate.js +109 -0
- package/dist/commands/onboard.js +188 -141
- package/dist/commands/report.js +68 -76
- package/dist/commands/scan.js +262 -81
- package/dist/commands/scans.d.ts +2 -0
- package/dist/commands/scans.js +55 -0
- package/dist/core/ai-client.d.ts +6 -1
- package/dist/core/ai-client.js +80 -12
- package/dist/core/ai-payloads.d.ts +17 -0
- package/dist/core/ai-payloads.js +54 -0
- package/dist/core/config-schema.d.ts +197 -0
- package/dist/core/config-schema.js +68 -0
- package/dist/core/config-schema.test.d.ts +1 -0
- package/dist/core/config-schema.test.js +151 -0
- package/dist/core/config.d.ts +8 -31
- package/dist/core/config.js +71 -14
- package/dist/core/diff-engine.d.ts +12 -0
- package/dist/core/diff-engine.js +47 -0
- package/dist/core/errors.d.ts +71 -0
- package/dist/core/errors.js +162 -0
- package/dist/core/scan-index.d.ts +20 -0
- package/dist/core/scan-index.js +52 -0
- package/dist/core/scan-storage.d.ts +11 -0
- package/dist/core/scan-storage.js +69 -0
- package/dist/core/scanner.d.ts +95 -13
- package/dist/core/scanner.js +342 -248
- package/dist/core/server-probe.d.ts +20 -0
- package/dist/core/server-probe.js +109 -0
- package/dist/core/vulnerability-detector.d.ts +9 -0
- package/dist/core/vulnerability-detector.js +46 -15
- package/dist/core/vulnerability-detector.test.d.ts +1 -0
- package/dist/core/vulnerability-detector.test.js +210 -0
- package/dist/index.js +3 -0
- package/dist/plugins/PluginManager.d.ts +27 -0
- package/dist/plugins/PluginManager.js +166 -0
- package/dist/plugins/index.d.ts +12 -0
- package/dist/plugins/index.js +29 -0
- package/dist/plugins/types.d.ts +55 -0
- package/dist/plugins/types.js +25 -0
- package/dist/plugins/vulnerabilities/CORSAnalyzerPlugin.d.ts +10 -0
- package/dist/plugins/vulnerabilities/CORSAnalyzerPlugin.js +67 -0
- package/dist/plugins/vulnerabilities/CSRFPlugin.d.ts +8 -0
- package/dist/plugins/vulnerabilities/CSRFPlugin.js +34 -0
- package/dist/plugins/vulnerabilities/CookieSecurityPlugin.d.ts +10 -0
- package/dist/plugins/vulnerabilities/CookieSecurityPlugin.js +91 -0
- package/dist/plugins/vulnerabilities/DebugEndpointPlugin.d.ts +15 -0
- package/dist/plugins/vulnerabilities/DebugEndpointPlugin.js +222 -0
- package/dist/plugins/vulnerabilities/DirectoryTraversalPlugin.d.ts +13 -0
- package/dist/plugins/vulnerabilities/DirectoryTraversalPlugin.js +110 -0
- package/dist/plugins/vulnerabilities/OpenRedirectPlugin.d.ts +10 -0
- package/dist/plugins/vulnerabilities/OpenRedirectPlugin.js +69 -0
- package/dist/plugins/vulnerabilities/SQLInjectionPlugin.d.ts +11 -0
- package/dist/plugins/vulnerabilities/SQLInjectionPlugin.js +109 -0
- package/dist/plugins/vulnerabilities/SecurityHeadersPlugin.d.ts +11 -0
- package/dist/plugins/vulnerabilities/SecurityHeadersPlugin.js +63 -0
- package/dist/plugins/vulnerabilities/SensitiveDataPlugin.d.ts +9 -0
- package/dist/plugins/vulnerabilities/SensitiveDataPlugin.js +32 -0
- package/dist/plugins/vulnerabilities/XSSPlugin.d.ts +15 -0
- package/dist/plugins/vulnerabilities/XSSPlugin.js +81 -0
- package/dist/reports/PdfGenerator.d.ts +36 -0
- package/dist/reports/PdfGenerator.js +404 -0
- package/dist/utils/logger.d.ts +33 -1
- package/dist/utils/logger.js +127 -8
- package/dist/utils/theme.d.ts +56 -0
- package/dist/utils/theme.js +201 -0
- package/package.json +6 -3
package/dist/commands/report.js
CHANGED
|
@@ -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
|
|
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
|
|
13
|
-
const
|
|
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("
|
|
23
|
-
console.log(chalk_1.default.gray("
|
|
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
|
-
|
|
27
|
-
|
|
28
|
-
if (
|
|
29
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
123
|
-
new docx_1.Paragraph({ text:
|
|
124
|
-
new docx_1.Paragraph({ text:
|
|
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:
|
|
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,
|
|
128
|
+
...scanResult.vulnerabilities.flatMap((vuln, index) => [
|
|
135
129
|
new docx_1.Paragraph({
|
|
136
|
-
text: `${
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
173
|
-
|
|
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
|
-
|
|
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,
|
|
201
|
-
lines.push(`${
|
|
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
|
-
|
|
199
|
+
}
|
|
200
|
+
if (vuln.remediation) {
|
|
208
201
|
lines.push(` Remediation: ${vuln.remediation}`);
|
|
209
|
-
|
|
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 =
|
|
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
|
}
|