kramscan 0.1.0 → 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.
- package/README.md +392 -87
- package/dist/agent/confirmation.d.ts +38 -0
- package/dist/agent/confirmation.js +210 -0
- package/dist/agent/context.d.ts +81 -0
- package/dist/agent/context.js +227 -0
- package/dist/agent/index.d.ts +10 -0
- package/dist/agent/index.js +32 -0
- package/dist/agent/orchestrator.d.ts +63 -0
- package/dist/agent/orchestrator.js +370 -0
- package/dist/agent/prompts/system.d.ts +6 -0
- package/dist/agent/prompts/system.js +116 -0
- package/dist/agent/skill-registry.d.ts +78 -0
- package/dist/agent/skill-registry.js +202 -0
- package/dist/agent/skills/analyze-findings.d.ts +22 -0
- package/dist/agent/skills/analyze-findings.js +191 -0
- package/dist/agent/skills/generate-report.d.ts +26 -0
- package/dist/agent/skills/generate-report.js +436 -0
- package/dist/agent/skills/health-check.d.ts +28 -0
- package/dist/agent/skills/health-check.js +344 -0
- package/dist/agent/skills/index.d.ts +9 -0
- package/dist/agent/skills/index.js +17 -0
- 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.d.ts +22 -0
- package/dist/agent/skills/web-scan.js +203 -0
- package/dist/agent/types.d.ts +141 -0
- package/dist/agent/types.js +16 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.js +176 -139
- package/dist/commands/agent.d.ts +6 -0
- package/dist/commands/agent.js +250 -0
- package/dist/commands/ai.d.ts +2 -0
- package/dist/commands/ai.js +112 -0
- package/dist/commands/analyze.js +104 -55
- package/dist/commands/config.js +63 -37
- package/dist/commands/doctor.js +22 -17
- package/dist/commands/onboard.js +190 -125
- package/dist/commands/report.js +69 -77
- package/dist/commands/scan.js +261 -81
- package/dist/commands/scans.d.ts +2 -0
- package/dist/commands/scans.js +51 -0
- package/dist/core/ai-client.d.ts +7 -2
- package/dist/core/ai-client.js +231 -20
- 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 +17 -36
- package/dist/core/config.js +261 -20
- package/dist/core/errors.d.ts +71 -0
- package/dist/core/errors.js +162 -0
- package/dist/core/scan-index.d.ts +19 -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 +101 -4
- package/dist/core/scanner.js +432 -63
- package/dist/core/vulnerability-detector.d.ts +18 -2
- package/dist/core/vulnerability-detector.js +349 -38
- 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 +7 -0
- package/dist/plugins/index.js +19 -0
- package/dist/plugins/types.d.ts +55 -0
- package/dist/plugins/types.js +25 -0
- package/dist/plugins/vulnerabilities/CSRFPlugin.d.ts +8 -0
- package/dist/plugins/vulnerabilities/CSRFPlugin.js +34 -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 +379 -0
- package/dist/utils/logger.d.ts +33 -1
- package/dist/utils/logger.js +127 -8
- package/dist/utils/theme.d.ts +55 -0
- package/dist/utils/theme.js +195 -0
- package/package.json +27 -6
- package/dist/core/executor.d.ts +0 -2
- package/dist/core/executor.js +0 -74
- package/dist/core/logger.d.ts +0 -12
- package/dist/core/logger.js +0 -51
- package/dist/core/registry.d.ts +0 -3
- package/dist/core/registry.js +0 -35
- package/dist/core/storage.d.ts +0 -4
- package/dist/core/storage.js +0 -39
- package/dist/core/types.d.ts +0 -24
- package/dist/core/types.js +0 -2
- package/dist/skills/base.d.ts +0 -8
- package/dist/skills/base.js +0 -6
- package/dist/skills/builtin.d.ts +0 -4
- package/dist/skills/builtin.js +0 -71
- package/dist/skills/loader.d.ts +0 -2
- package/dist/skills/loader.js +0 -27
- package/dist/skills/types.d.ts +0 -46
- package/dist/skills/types.js +0 -2
|
@@ -0,0 +1,195 @@
|
|
|
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.theme = exports.CLI_VERSION = void 0;
|
|
40
|
+
exports.printBanner = printBanner;
|
|
41
|
+
exports.printInfo = printInfo;
|
|
42
|
+
exports.getSeverityColor = getSeverityColor;
|
|
43
|
+
exports.displayScanSummary = displayScanSummary;
|
|
44
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
45
|
+
const path = __importStar(require("path"));
|
|
46
|
+
const fs = __importStar(require("fs"));
|
|
47
|
+
// Read version from package.json (single source of truth)
|
|
48
|
+
function getPackageVersion() {
|
|
49
|
+
try {
|
|
50
|
+
const pkgPath = path.resolve(__dirname, "..", "..", "package.json");
|
|
51
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
52
|
+
return pkg.version || "0.0.0";
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return "0.0.0";
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
exports.CLI_VERSION = getPackageVersion();
|
|
59
|
+
exports.theme = {
|
|
60
|
+
// Colors
|
|
61
|
+
brand: chalk_1.default.hex("#00D1FF"),
|
|
62
|
+
error: chalk_1.default.red.bold,
|
|
63
|
+
success: chalk_1.default.green,
|
|
64
|
+
warning: chalk_1.default.yellow,
|
|
65
|
+
info: chalk_1.default.blue,
|
|
66
|
+
dim: chalk_1.default.gray,
|
|
67
|
+
gray: chalk_1.default.gray,
|
|
68
|
+
white: chalk_1.default.white,
|
|
69
|
+
cyan: chalk_1.default.cyan,
|
|
70
|
+
brightWhite: chalk_1.default.white.bold,
|
|
71
|
+
brightCyan: chalk_1.default.cyan.bold,
|
|
72
|
+
brightMagenta: chalk_1.default.magenta.bold,
|
|
73
|
+
brightBlue: chalk_1.default.blue.bold,
|
|
74
|
+
brightGreen: chalk_1.default.green.bold,
|
|
75
|
+
brightYellow: chalk_1.default.yellow.bold,
|
|
76
|
+
brightRed: chalk_1.default.red.bold,
|
|
77
|
+
// Severity colors
|
|
78
|
+
critical: chalk_1.default.red.bold,
|
|
79
|
+
high: chalk_1.default.red,
|
|
80
|
+
medium: chalk_1.default.yellow,
|
|
81
|
+
low: chalk_1.default.blue,
|
|
82
|
+
infoSeverity: chalk_1.default.gray,
|
|
83
|
+
yellow: chalk_1.default.yellow,
|
|
84
|
+
green: chalk_1.default.green,
|
|
85
|
+
};
|
|
86
|
+
// ─── ASCII Art Banner ──────────────────────────────────────────────
|
|
87
|
+
function printBanner() {
|
|
88
|
+
const lines = [
|
|
89
|
+
`██╗ ██╗██████╗ █████╗ ███╗ ███╗███████╗ ██████╗ █████╗ ███╗ ██╗`,
|
|
90
|
+
`██║ ██╔╝██╔══██╗██╔══██╗████╗ ████║██╔════╝██╔════╝██╔══██╗████╗ ██║`,
|
|
91
|
+
`█████╔╝ ██████╔╝███████║██╔████╔██║███████╗██║ ███████║██╔██╗ ██║`,
|
|
92
|
+
`██╔═██╗ ██╔══██╗██╔══██║██║╚██╔╝██║╚════██║██║ ██╔══██║██║╚██╗██║`,
|
|
93
|
+
`██║ ██╗██║ ██║██║ ██║██║ ╚═╝ ██║███████║╚██████╗██║ ██║██║ ╚████║`,
|
|
94
|
+
`╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝ ╚═════╝╚═╝ ╚═╝╚═╝ ╚═══╝`,
|
|
95
|
+
];
|
|
96
|
+
console.log("");
|
|
97
|
+
lines.forEach((line, i) => {
|
|
98
|
+
const shade = i % 2 === 0 ? exports.theme.brightWhite : exports.theme.dim;
|
|
99
|
+
console.log(` ${shade(line)}`);
|
|
100
|
+
});
|
|
101
|
+
console.log("");
|
|
102
|
+
}
|
|
103
|
+
// ─── Dashboard Info ────────────────────────────────────────────────
|
|
104
|
+
function printInfo() {
|
|
105
|
+
console.log(` ${exports.theme.dim("───────────────────────────────────────────────────────")}`);
|
|
106
|
+
console.log(` ${exports.theme.brightWhite(" KramScan")} ${exports.theme.gray(`v${exports.CLI_VERSION}`)} ${exports.theme.dim("|")} ${exports.theme.cyan("AI-Powered Web Security Scanner")}`);
|
|
107
|
+
console.log(` ${exports.theme.dim("───────────────────────────────────────────────────────")}`);
|
|
108
|
+
console.log("");
|
|
109
|
+
console.log(` ${exports.theme.brightYellow("Tips for getting started:")}`);
|
|
110
|
+
console.log(` ${exports.theme.white("1.")} ${exports.theme.gray("Run")} ${exports.theme.cyan("kramscan onboard")} ${exports.theme.gray("to configure your API keys.")}`);
|
|
111
|
+
console.log(` ${exports.theme.white("2.")} ${exports.theme.gray("Run")} ${exports.theme.cyan("kramscan scan <url>")} ${exports.theme.gray("to scan a target.")}`);
|
|
112
|
+
console.log(` ${exports.theme.white("3.")} ${exports.theme.gray("Run")} ${exports.theme.cyan("kramscan --help")} ${exports.theme.gray("for all commands.")}`);
|
|
113
|
+
console.log("");
|
|
114
|
+
}
|
|
115
|
+
// ─── Severity Color Helper ─────────────────────────────────────────
|
|
116
|
+
function getSeverityColor(severity) {
|
|
117
|
+
const s = severity.toLowerCase();
|
|
118
|
+
if (s === "critical")
|
|
119
|
+
return exports.theme.critical;
|
|
120
|
+
if (s === "high")
|
|
121
|
+
return exports.theme.high;
|
|
122
|
+
if (s === "medium")
|
|
123
|
+
return exports.theme.medium;
|
|
124
|
+
if (s === "low")
|
|
125
|
+
return exports.theme.low;
|
|
126
|
+
return exports.theme.infoSeverity;
|
|
127
|
+
}
|
|
128
|
+
// ─── Scan Summary Display ──────────────────────────────────────────
|
|
129
|
+
function displayScanSummary(result) {
|
|
130
|
+
const { target, duration, metadata, summary, vulnerabilities, filepath, pdfPath } = result;
|
|
131
|
+
// Scan Summary
|
|
132
|
+
console.log("");
|
|
133
|
+
console.log(exports.theme.brightWhite.bold("📊 Scan Summary"));
|
|
134
|
+
console.log(exports.theme.gray("─".repeat(50)));
|
|
135
|
+
console.log("");
|
|
136
|
+
console.log(exports.theme.white("Target:"), exports.theme.cyan(target));
|
|
137
|
+
console.log(exports.theme.white("Duration:"), exports.theme.cyan(`${(duration / 1000).toFixed(2)}s`));
|
|
138
|
+
console.log(exports.theme.white("URLs Crawled:"), exports.theme.cyan(metadata.crawledUrls));
|
|
139
|
+
console.log(exports.theme.white("Forms Tested:"), exports.theme.cyan(metadata.testedForms));
|
|
140
|
+
console.log(exports.theme.white("Requests Made:"), exports.theme.cyan(metadata.requestsMade));
|
|
141
|
+
console.log("");
|
|
142
|
+
// Vulnerability summary
|
|
143
|
+
console.log(exports.theme.brightWhite.bold("🛡️ Vulnerabilities Found"));
|
|
144
|
+
console.log(exports.theme.gray("─".repeat(50)));
|
|
145
|
+
console.log("");
|
|
146
|
+
if (summary.total === 0) {
|
|
147
|
+
console.log(exports.theme.success("✓ No vulnerabilities found!"));
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
if (summary.critical > 0)
|
|
151
|
+
console.log(exports.theme.critical(` ${summary.critical} Critical`), exports.theme.gray("- Immediate action required"));
|
|
152
|
+
if (summary.high > 0)
|
|
153
|
+
console.log(exports.theme.high(` ${summary.high} High`), exports.theme.gray("- Should be fixed soon"));
|
|
154
|
+
if (summary.medium > 0)
|
|
155
|
+
console.log(exports.theme.medium(` ${summary.medium} Medium`), exports.theme.gray("- Fix when possible"));
|
|
156
|
+
if (summary.low > 0)
|
|
157
|
+
console.log(exports.theme.low(` ${summary.low} Low`), exports.theme.gray("- Minor issues"));
|
|
158
|
+
if (summary.info > 0)
|
|
159
|
+
console.log(exports.theme.infoSeverity(` ${summary.info} Info`), exports.theme.gray("- Informational"));
|
|
160
|
+
}
|
|
161
|
+
console.log("");
|
|
162
|
+
console.log(exports.theme.gray("Results saved to:"), exports.theme.white(filepath));
|
|
163
|
+
if (pdfPath) {
|
|
164
|
+
console.log(exports.theme.gray("PDF report saved to:"), exports.theme.white(pdfPath));
|
|
165
|
+
}
|
|
166
|
+
console.log("");
|
|
167
|
+
// Show top vulnerabilities
|
|
168
|
+
if (vulnerabilities.length > 0) {
|
|
169
|
+
console.log(exports.theme.brightWhite.bold("🔴 Top Findings"));
|
|
170
|
+
console.log(exports.theme.gray("─".repeat(50)));
|
|
171
|
+
console.log("");
|
|
172
|
+
const topVulns = vulnerabilities
|
|
173
|
+
.sort((a, b) => {
|
|
174
|
+
const severityOrder = { critical: 0, high: 1, medium: 2, low: 3, info: 4 };
|
|
175
|
+
return severityOrder[a.severity] - severityOrder[b.severity];
|
|
176
|
+
})
|
|
177
|
+
.slice(0, 5);
|
|
178
|
+
for (const vuln of topVulns) {
|
|
179
|
+
const severityColor = getSeverityColor(vuln.severity);
|
|
180
|
+
console.log(severityColor(`[${vuln.severity.toUpperCase()}]`), exports.theme.brightWhite.bold(vuln.title));
|
|
181
|
+
console.log(exports.theme.gray(` ${vuln.url}`));
|
|
182
|
+
console.log(exports.theme.white(` ${vuln.description}`));
|
|
183
|
+
console.log("");
|
|
184
|
+
}
|
|
185
|
+
if (vulnerabilities.length > 5) {
|
|
186
|
+
console.log(exports.theme.gray(` ... and ${vulnerabilities.length - 5} more`));
|
|
187
|
+
console.log("");
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
console.log(exports.theme.cyan("💡 Next steps:"));
|
|
191
|
+
console.log(exports.theme.white(` 1. Run ${exports.theme.cyan(`kramscan analyze ${filepath}`)} for AI-powered insights`));
|
|
192
|
+
console.log(exports.theme.white(" 2. PDF report is generated automatically after the scan"));
|
|
193
|
+
console.log("");
|
|
194
|
+
}
|
|
195
|
+
exports.default = exports.theme;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kramscan",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "KramScan CLI — AI-powered web app security testing",
|
|
5
5
|
"author": "Akram Shaikh <akramshaikh.me>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -15,12 +15,12 @@
|
|
|
15
15
|
],
|
|
16
16
|
"repository": {
|
|
17
17
|
"type": "git",
|
|
18
|
-
"url": "https://github.com/
|
|
18
|
+
"url": "https://github.com/shaikhakramshakil/kramscan.git"
|
|
19
19
|
},
|
|
20
20
|
"bugs": {
|
|
21
|
-
"url": "https://github.com/
|
|
21
|
+
"url": "https://github.com/shaikhakramshakil/kramscan/issues"
|
|
22
22
|
},
|
|
23
|
-
"homepage": "https://github.com/
|
|
23
|
+
"homepage": "https://github.com/shaikhakramshakil/kramscan#readme",
|
|
24
24
|
"files": [
|
|
25
25
|
"dist",
|
|
26
26
|
"bin",
|
|
@@ -38,10 +38,19 @@
|
|
|
38
38
|
"scripts": {
|
|
39
39
|
"build": "tsc -p tsconfig.json",
|
|
40
40
|
"dev": "node dist/index.js",
|
|
41
|
-
"start": "node dist/index.js"
|
|
41
|
+
"start": "node dist/index.js",
|
|
42
|
+
"clean": "rimraf dist",
|
|
43
|
+
"test": "jest",
|
|
44
|
+
"test:watch": "jest --watch",
|
|
45
|
+
"lint": "eslint src --ext .ts",
|
|
46
|
+
"lint:fix": "eslint src --ext .ts --fix",
|
|
47
|
+
"format": "prettier --write \"src/**/*.ts\"",
|
|
48
|
+
"prepublishOnly": "npm run clean && npm run build"
|
|
42
49
|
},
|
|
43
50
|
"dependencies": {
|
|
44
51
|
"@anthropic-ai/sdk": "^0.31.0",
|
|
52
|
+
"@google/generative-ai": "^0.24.1",
|
|
53
|
+
"@mistralai/mistralai": "^1.14.0",
|
|
45
54
|
"axios": "^1.6.8",
|
|
46
55
|
"chalk": "^5.6.2",
|
|
47
56
|
"commander": "^12.1.0",
|
|
@@ -49,14 +58,26 @@
|
|
|
49
58
|
"docx": "^9.5.1",
|
|
50
59
|
"inquirer": "^9.2.12",
|
|
51
60
|
"js-yaml": "^4.1.0",
|
|
61
|
+
"keytar": "^7.9.0",
|
|
52
62
|
"openai": "^4.104.0",
|
|
53
63
|
"ora": "^8.2.0",
|
|
54
|
-
"puppeteer": "^22.15.0"
|
|
64
|
+
"puppeteer": "^22.15.0",
|
|
65
|
+
"uuid": "^9.0.1"
|
|
55
66
|
},
|
|
56
67
|
"devDependencies": {
|
|
57
68
|
"@types/inquirer": "^9.0.9",
|
|
69
|
+
"@types/jest": "^29.5.12",
|
|
58
70
|
"@types/js-yaml": "^4.0.9",
|
|
71
|
+
"@types/keytar": "^4.4.2",
|
|
59
72
|
"@types/node": "^20.12.8",
|
|
73
|
+
"@types/uuid": "^9.0.8",
|
|
74
|
+
"@typescript-eslint/eslint-plugin": "^7.0.0",
|
|
75
|
+
"@typescript-eslint/parser": "^7.0.0",
|
|
76
|
+
"eslint": "^8.57.0",
|
|
77
|
+
"jest": "^29.7.0",
|
|
78
|
+
"prettier": "^3.2.0",
|
|
79
|
+
"rimraf": "^5.0.0",
|
|
80
|
+
"ts-jest": "^29.1.2",
|
|
60
81
|
"typescript": "^5.4.5"
|
|
61
82
|
}
|
|
62
83
|
}
|
package/dist/core/executor.d.ts
DELETED
package/dist/core/executor.js
DELETED
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.executeScan = executeScan;
|
|
4
|
-
const scanner_1 = require("./scanner");
|
|
5
|
-
const builtin_1 = require("../skills/builtin");
|
|
6
|
-
const registry_1 = require("./registry");
|
|
7
|
-
const logger_1 = require("./logger");
|
|
8
|
-
async function executeScan(targetUrl, options) {
|
|
9
|
-
const logger = (0, logger_1.createLogger)();
|
|
10
|
-
const startedAt = new Date().toISOString();
|
|
11
|
-
const skills = (0, registry_1.listSkills)();
|
|
12
|
-
const selectedSkills = skills
|
|
13
|
-
.map((skill) => skill.id)
|
|
14
|
-
.filter((skillId) => {
|
|
15
|
-
if (options.skills && options.skills.length > 0) {
|
|
16
|
-
return options.skills.includes(skillId);
|
|
17
|
-
}
|
|
18
|
-
return true;
|
|
19
|
-
})
|
|
20
|
-
.filter((skillId) => {
|
|
21
|
-
if (options.excludeSkills && options.excludeSkills.length > 0) {
|
|
22
|
-
return !options.excludeSkills.includes(skillId);
|
|
23
|
-
}
|
|
24
|
-
return true;
|
|
25
|
-
});
|
|
26
|
-
const scanner = await (0, scanner_1.createScannerClient)({
|
|
27
|
-
userAgent: "OpenScan/0.1.0",
|
|
28
|
-
timeoutSeconds: options.timeoutSeconds,
|
|
29
|
-
proxy: options.proxy,
|
|
30
|
-
verifySSL: true,
|
|
31
|
-
followRedirects: true,
|
|
32
|
-
enableBrowser: options.deep
|
|
33
|
-
});
|
|
34
|
-
const findings = [];
|
|
35
|
-
for (const skillId of selectedSkills) {
|
|
36
|
-
const runner = builtin_1.builtinSkillRunners[skillId];
|
|
37
|
-
if (!runner) {
|
|
38
|
-
logger.warn(`No runner available for skill ${skillId}.`);
|
|
39
|
-
continue;
|
|
40
|
-
}
|
|
41
|
-
try {
|
|
42
|
-
const result = await runner({
|
|
43
|
-
targetUrl,
|
|
44
|
-
timeoutSeconds: options.timeoutSeconds,
|
|
45
|
-
logger,
|
|
46
|
-
http: {
|
|
47
|
-
get: async (url) => {
|
|
48
|
-
const response = await scanner.http.get(url);
|
|
49
|
-
return {
|
|
50
|
-
data: response.data,
|
|
51
|
-
headers: response.headers
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
});
|
|
56
|
-
findings.push(...result.findings);
|
|
57
|
-
}
|
|
58
|
-
catch (error) {
|
|
59
|
-
logger.error(`Skill ${skillId} failed: ${error.message}`);
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
await scanner.close();
|
|
63
|
-
const finishedAt = new Date().toISOString();
|
|
64
|
-
const result = {
|
|
65
|
-
id: `scan-${Date.now()}`,
|
|
66
|
-
startedAt,
|
|
67
|
-
finishedAt,
|
|
68
|
-
target: { url: targetUrl },
|
|
69
|
-
options,
|
|
70
|
-
skillsRun: selectedSkills,
|
|
71
|
-
findings
|
|
72
|
-
};
|
|
73
|
-
return result;
|
|
74
|
-
}
|
package/dist/core/logger.d.ts
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
export interface Logger {
|
|
2
|
-
info(message: string): void;
|
|
3
|
-
warn(message: string): void;
|
|
4
|
-
error(message: string): void;
|
|
5
|
-
success(message: string): void;
|
|
6
|
-
spinner(message: string): {
|
|
7
|
-
stop: () => void;
|
|
8
|
-
succeed: (msg?: string) => void;
|
|
9
|
-
fail: (msg?: string) => void;
|
|
10
|
-
};
|
|
11
|
-
}
|
|
12
|
-
export declare function createLogger(): Logger;
|
package/dist/core/logger.js
DELETED
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.createLogger = createLogger;
|
|
4
|
-
// ANSI color helpers (replaces ESM-only chalk)
|
|
5
|
-
const c = {
|
|
6
|
-
reset: "\x1b[0m",
|
|
7
|
-
cyan: "\x1b[36m",
|
|
8
|
-
yellow: "\x1b[33m",
|
|
9
|
-
red: "\x1b[31m",
|
|
10
|
-
green: "\x1b[32m",
|
|
11
|
-
gray: "\x1b[90m",
|
|
12
|
-
bold: "\x1b[1m",
|
|
13
|
-
};
|
|
14
|
-
function createLogger() {
|
|
15
|
-
return {
|
|
16
|
-
info(message) {
|
|
17
|
-
console.log(`${c.cyan}ℹ ${message}${c.reset}`);
|
|
18
|
-
},
|
|
19
|
-
warn(message) {
|
|
20
|
-
console.log(`${c.yellow}⚠ ${message}${c.reset}`);
|
|
21
|
-
},
|
|
22
|
-
error(message) {
|
|
23
|
-
console.error(`${c.red}✖ ${message}${c.reset}`);
|
|
24
|
-
},
|
|
25
|
-
success(message) {
|
|
26
|
-
console.log(`${c.green}✔ ${message}${c.reset}`);
|
|
27
|
-
},
|
|
28
|
-
spinner(message) {
|
|
29
|
-
const frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
30
|
-
let i = 0;
|
|
31
|
-
const id = setInterval(() => {
|
|
32
|
-
process.stdout.write(`\r${c.cyan}${frames[i % frames.length]}${c.reset} ${message}`);
|
|
33
|
-
i++;
|
|
34
|
-
}, 80);
|
|
35
|
-
return {
|
|
36
|
-
stop() {
|
|
37
|
-
clearInterval(id);
|
|
38
|
-
process.stdout.write("\r\x1b[K"); // Clear line
|
|
39
|
-
},
|
|
40
|
-
succeed(msg) {
|
|
41
|
-
clearInterval(id);
|
|
42
|
-
console.log(`\r${c.green}✔${c.reset} ${msg || message}`);
|
|
43
|
-
},
|
|
44
|
-
fail(msg) {
|
|
45
|
-
clearInterval(id);
|
|
46
|
-
console.log(`\r${c.red}✖${c.reset} ${msg || message}`);
|
|
47
|
-
},
|
|
48
|
-
};
|
|
49
|
-
},
|
|
50
|
-
};
|
|
51
|
-
}
|
package/dist/core/registry.d.ts
DELETED
package/dist/core/registry.js
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
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.listSkills = listSkills;
|
|
7
|
-
exports.getSkillMetadata = getSkillMetadata;
|
|
8
|
-
const fs_1 = __importDefault(require("fs"));
|
|
9
|
-
const path_1 = __importDefault(require("path"));
|
|
10
|
-
const loader_1 = require("../skills/loader");
|
|
11
|
-
function getSkillsRoot() {
|
|
12
|
-
return path_1.default.resolve(process.cwd(), "skills");
|
|
13
|
-
}
|
|
14
|
-
function listSkills() {
|
|
15
|
-
const skillsRoot = getSkillsRoot();
|
|
16
|
-
if (!fs_1.default.existsSync(skillsRoot)) {
|
|
17
|
-
return [];
|
|
18
|
-
}
|
|
19
|
-
const entries = fs_1.default.readdirSync(skillsRoot, { withFileTypes: true });
|
|
20
|
-
const skills = [];
|
|
21
|
-
for (const entry of entries) {
|
|
22
|
-
if (!entry.isDirectory()) {
|
|
23
|
-
continue;
|
|
24
|
-
}
|
|
25
|
-
const skillDir = path_1.default.join(skillsRoot, entry.name);
|
|
26
|
-
const meta = (0, loader_1.loadSkillMetadata)(skillDir);
|
|
27
|
-
if (meta) {
|
|
28
|
-
skills.push(meta);
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
return skills;
|
|
32
|
-
}
|
|
33
|
-
function getSkillMetadata(skillId) {
|
|
34
|
-
return listSkills().find((skill) => skill.id === skillId);
|
|
35
|
-
}
|
package/dist/core/storage.d.ts
DELETED
package/dist/core/storage.js
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
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.saveScanResult = saveScanResult;
|
|
7
|
-
exports.loadLastScanResult = loadLastScanResult;
|
|
8
|
-
exports.loadScanResultFromFile = loadScanResultFromFile;
|
|
9
|
-
const fs_1 = __importDefault(require("fs"));
|
|
10
|
-
const path_1 = __importDefault(require("path"));
|
|
11
|
-
const os_1 = __importDefault(require("os"));
|
|
12
|
-
const ROOT_DIR = path_1.default.join(os_1.default.homedir(), ".openscan");
|
|
13
|
-
const SCANS_DIR = path_1.default.join(ROOT_DIR, "scans");
|
|
14
|
-
const LAST_SCAN_PATH = path_1.default.join(SCANS_DIR, "last.json");
|
|
15
|
-
function ensureDir(dirPath) {
|
|
16
|
-
if (!fs_1.default.existsSync(dirPath)) {
|
|
17
|
-
fs_1.default.mkdirSync(dirPath, { recursive: true });
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
function saveScanResult(result) {
|
|
21
|
-
ensureDir(SCANS_DIR);
|
|
22
|
-
const filename = `${result.id}.json`;
|
|
23
|
-
const filePath = path_1.default.join(SCANS_DIR, filename);
|
|
24
|
-
const payload = JSON.stringify(result, null, 2);
|
|
25
|
-
fs_1.default.writeFileSync(filePath, payload, "utf-8");
|
|
26
|
-
fs_1.default.writeFileSync(LAST_SCAN_PATH, payload, "utf-8");
|
|
27
|
-
return filePath;
|
|
28
|
-
}
|
|
29
|
-
function loadLastScanResult() {
|
|
30
|
-
if (!fs_1.default.existsSync(LAST_SCAN_PATH)) {
|
|
31
|
-
return null;
|
|
32
|
-
}
|
|
33
|
-
const raw = fs_1.default.readFileSync(LAST_SCAN_PATH, "utf-8");
|
|
34
|
-
return JSON.parse(raw);
|
|
35
|
-
}
|
|
36
|
-
function loadScanResultFromFile(filePath) {
|
|
37
|
-
const raw = fs_1.default.readFileSync(filePath, "utf-8");
|
|
38
|
-
return JSON.parse(raw);
|
|
39
|
-
}
|
package/dist/core/types.d.ts
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { Finding } from "../skills/types";
|
|
2
|
-
export interface ScanTarget {
|
|
3
|
-
url: string;
|
|
4
|
-
}
|
|
5
|
-
export interface ScanOptions {
|
|
6
|
-
deep: boolean;
|
|
7
|
-
timeoutSeconds: number;
|
|
8
|
-
threads: number;
|
|
9
|
-
skills?: string[];
|
|
10
|
-
excludeSkills?: string[];
|
|
11
|
-
proxy?: string;
|
|
12
|
-
aiEnabled?: boolean;
|
|
13
|
-
aiModel?: string;
|
|
14
|
-
}
|
|
15
|
-
export interface ScanResult {
|
|
16
|
-
id: string;
|
|
17
|
-
startedAt: string;
|
|
18
|
-
finishedAt: string;
|
|
19
|
-
target: ScanTarget;
|
|
20
|
-
options: ScanOptions;
|
|
21
|
-
skillsRun: string[];
|
|
22
|
-
findings: Finding[];
|
|
23
|
-
aiSummary?: string;
|
|
24
|
-
}
|
package/dist/core/types.js
DELETED
package/dist/skills/base.d.ts
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import { Skill, SkillContext, SkillResult } from "./types";
|
|
2
|
-
export declare abstract class BaseSkill implements Skill {
|
|
3
|
-
abstract id: string;
|
|
4
|
-
abstract name: string;
|
|
5
|
-
abstract description: string;
|
|
6
|
-
abstract tags: string[];
|
|
7
|
-
abstract run(context: SkillContext): Promise<SkillResult>;
|
|
8
|
-
}
|
package/dist/skills/base.js
DELETED
package/dist/skills/builtin.d.ts
DELETED
package/dist/skills/builtin.js
DELETED
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.builtinSkillRunners = void 0;
|
|
4
|
-
async function runHeadersSkill(context) {
|
|
5
|
-
const response = await context.http.get(context.targetUrl);
|
|
6
|
-
const headers = {};
|
|
7
|
-
for (const [key, value] of Object.entries(response.headers)) {
|
|
8
|
-
headers[key.toLowerCase()] = Array.isArray(value) ? value.join(", ") : String(value);
|
|
9
|
-
}
|
|
10
|
-
const checks = [
|
|
11
|
-
{
|
|
12
|
-
key: "strict-transport-security",
|
|
13
|
-
title: "Missing HSTS header",
|
|
14
|
-
recommendation: "Enable Strict-Transport-Security with a long max-age and includeSubDomains."
|
|
15
|
-
},
|
|
16
|
-
{
|
|
17
|
-
key: "content-security-policy",
|
|
18
|
-
title: "Missing Content-Security-Policy header",
|
|
19
|
-
recommendation: "Define a CSP to restrict scripts, styles, and frames."
|
|
20
|
-
},
|
|
21
|
-
{
|
|
22
|
-
key: "x-frame-options",
|
|
23
|
-
title: "Missing X-Frame-Options header",
|
|
24
|
-
recommendation: "Set X-Frame-Options to DENY or SAMEORIGIN."
|
|
25
|
-
},
|
|
26
|
-
{
|
|
27
|
-
key: "x-content-type-options",
|
|
28
|
-
title: "Missing X-Content-Type-Options header",
|
|
29
|
-
recommendation: "Set X-Content-Type-Options to nosniff."
|
|
30
|
-
},
|
|
31
|
-
{
|
|
32
|
-
key: "referrer-policy",
|
|
33
|
-
title: "Missing Referrer-Policy header",
|
|
34
|
-
recommendation: "Set Referrer-Policy to strict-origin-when-cross-origin or similar."
|
|
35
|
-
},
|
|
36
|
-
{
|
|
37
|
-
key: "permissions-policy",
|
|
38
|
-
title: "Missing Permissions-Policy header",
|
|
39
|
-
recommendation: "Set Permissions-Policy to restrict sensitive browser APIs."
|
|
40
|
-
}
|
|
41
|
-
];
|
|
42
|
-
const findings = checks
|
|
43
|
-
.filter((check) => !headers[check.key])
|
|
44
|
-
.map((check) => ({
|
|
45
|
-
id: `headers-${check.key}`,
|
|
46
|
-
skillId: "headers",
|
|
47
|
-
title: check.title,
|
|
48
|
-
severity: "low",
|
|
49
|
-
description: `The response did not include the ${check.key} header.`,
|
|
50
|
-
recommendation: check.recommendation
|
|
51
|
-
}));
|
|
52
|
-
return {
|
|
53
|
-
skillId: "headers",
|
|
54
|
-
findings
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
|
-
async function runPlaceholderSkill(skillId, context) {
|
|
58
|
-
context.logger.warn(`${skillId} is not implemented yet. Returning no findings.`);
|
|
59
|
-
return {
|
|
60
|
-
skillId,
|
|
61
|
-
findings: []
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
|
-
exports.builtinSkillRunners = {
|
|
65
|
-
headers: runHeadersSkill,
|
|
66
|
-
sqli: (context) => runPlaceholderSkill("sqli", context),
|
|
67
|
-
xss: (context) => runPlaceholderSkill("xss", context),
|
|
68
|
-
csrf: (context) => runPlaceholderSkill("csrf", context),
|
|
69
|
-
idor: (context) => runPlaceholderSkill("idor", context),
|
|
70
|
-
jwt: (context) => runPlaceholderSkill("jwt", context)
|
|
71
|
-
};
|
package/dist/skills/loader.d.ts
DELETED
package/dist/skills/loader.js
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
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.loadSkillMetadata = loadSkillMetadata;
|
|
7
|
-
const fs_1 = __importDefault(require("fs"));
|
|
8
|
-
const path_1 = __importDefault(require("path"));
|
|
9
|
-
const js_yaml_1 = __importDefault(require("js-yaml"));
|
|
10
|
-
function loadSkillMetadata(skillDir) {
|
|
11
|
-
const skillPath = path_1.default.join(skillDir, "skill.yaml");
|
|
12
|
-
if (!fs_1.default.existsSync(skillPath)) {
|
|
13
|
-
return null;
|
|
14
|
-
}
|
|
15
|
-
const raw = fs_1.default.readFileSync(skillPath, "utf-8");
|
|
16
|
-
const doc = js_yaml_1.default.load(raw);
|
|
17
|
-
if (!doc || !doc.id || !doc.name) {
|
|
18
|
-
return null;
|
|
19
|
-
}
|
|
20
|
-
return {
|
|
21
|
-
id: doc.id,
|
|
22
|
-
name: doc.name,
|
|
23
|
-
description: doc.description ?? "",
|
|
24
|
-
tags: doc.tags ?? [],
|
|
25
|
-
category: doc.category
|
|
26
|
-
};
|
|
27
|
-
}
|