kramscan 0.1.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 +21 -0
- package/README.md +87 -0
- package/bin/kramscan.js +4 -0
- package/bin/openscan.js +4 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +225 -0
- package/dist/commands/analyze.d.ts +2 -0
- package/dist/commands/analyze.js +115 -0
- package/dist/commands/config.d.ts +2 -0
- package/dist/commands/config.js +139 -0
- package/dist/commands/doctor.d.ts +2 -0
- package/dist/commands/doctor.js +234 -0
- package/dist/commands/onboard.d.ts +2 -0
- package/dist/commands/onboard.js +146 -0
- package/dist/commands/report.d.ts +2 -0
- package/dist/commands/report.js +225 -0
- package/dist/commands/scan.d.ts +2 -0
- package/dist/commands/scan.js +125 -0
- package/dist/core/ai-client.d.ts +12 -0
- package/dist/core/ai-client.js +89 -0
- package/dist/core/config.d.ts +45 -0
- package/dist/core/config.js +146 -0
- package/dist/core/executor.d.ts +2 -0
- package/dist/core/executor.js +74 -0
- package/dist/core/logger.d.ts +12 -0
- package/dist/core/logger.js +51 -0
- package/dist/core/registry.d.ts +3 -0
- package/dist/core/registry.js +35 -0
- package/dist/core/scanner.d.ts +24 -0
- package/dist/core/scanner.js +197 -0
- package/dist/core/storage.d.ts +4 -0
- package/dist/core/storage.js +39 -0
- package/dist/core/types.d.ts +24 -0
- package/dist/core/types.js +2 -0
- package/dist/core/vulnerability-detector.d.ts +47 -0
- package/dist/core/vulnerability-detector.js +150 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +7 -0
- package/dist/skills/base.d.ts +8 -0
- package/dist/skills/base.js +6 -0
- package/dist/skills/builtin.d.ts +4 -0
- package/dist/skills/builtin.js +71 -0
- package/dist/skills/loader.d.ts +2 -0
- package/dist/skills/loader.js +27 -0
- package/dist/skills/types.d.ts +46 -0
- package/dist/skills/types.js +2 -0
- package/dist/utils/logger.d.ts +9 -0
- package/dist/utils/logger.js +34 -0
- package/package.json +62 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Akram Shaikh
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# KramScan 🛡️
|
|
2
|
+
|
|
3
|
+
KramScan is a personal, AI-powered command-line interface (CLI) for web application security testing. It combines automated browser interactions (via Puppeteer) with AI analysis to identify vulnerabilities in modern web apps.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Quick Start
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
# Install dependencies
|
|
11
|
+
npm install
|
|
12
|
+
|
|
13
|
+
# Build the project
|
|
14
|
+
npm run build
|
|
15
|
+
|
|
16
|
+
# Link globally so you can use "kramscan" from anywhere
|
|
17
|
+
npm link
|
|
18
|
+
|
|
19
|
+
# Launch the interactive dashboard
|
|
20
|
+
kramscan
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Commands
|
|
26
|
+
|
|
27
|
+
| Command | Description | Status |
|
|
28
|
+
|:-------------------|:-------------------------------------|:-------------|
|
|
29
|
+
| `kramscan` | Launch interactive dashboard | ✅ Active |
|
|
30
|
+
| `kramscan onboard` | First-time setup wizard | ✅ Active |
|
|
31
|
+
| `kramscan scan` | Scan a target URL | 🔜 Coming |
|
|
32
|
+
| `kramscan analyze` | AI-powered analysis of scan results | 🔜 Coming |
|
|
33
|
+
| `kramscan report` | Generate a professional report | 🔜 Coming |
|
|
34
|
+
| `kramscan doctor` | Check environment health | 🔜 Coming |
|
|
35
|
+
| `kramscan --help` | Show all available commands | ✅ Active |
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## Setup Wizard
|
|
40
|
+
|
|
41
|
+
Run `kramscan onboard` to configure:
|
|
42
|
+
|
|
43
|
+
1. **AI Provider** — OpenAI or Anthropic
|
|
44
|
+
2. **API Key** — Your provider API key
|
|
45
|
+
3. **Default Model** — e.g. `gpt-4`
|
|
46
|
+
4. **Report Format** — Word, TXT, or JSON
|
|
47
|
+
5. **Scope Enforcement** — Strict mode on/off
|
|
48
|
+
6. **Rate Limiting** — Requests per second
|
|
49
|
+
|
|
50
|
+
Configuration is saved to `~/.kramscan/config.json`.
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## Development
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
# Run without building (using tsx)
|
|
58
|
+
npx tsx src/index.ts
|
|
59
|
+
|
|
60
|
+
# Run a specific command
|
|
61
|
+
npx tsx src/index.ts onboard
|
|
62
|
+
|
|
63
|
+
# Build
|
|
64
|
+
npm run build
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## Tech Stack
|
|
70
|
+
|
|
71
|
+
- **TypeScript** + **Node.js**
|
|
72
|
+
- **Commander.js** — CLI framework
|
|
73
|
+
- **Inquirer.js** — Interactive prompts
|
|
74
|
+
- **Puppeteer** — Browser automation
|
|
75
|
+
- **ConfigStore** — Persistent configuration
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## Author
|
|
80
|
+
|
|
81
|
+
**Akram** — *KramScan*
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## License
|
|
86
|
+
|
|
87
|
+
ISC
|
package/bin/kramscan.js
ADDED
package/bin/openscan.js
ADDED
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function run(): Promise<void>;
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,225 @@
|
|
|
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
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.run = run;
|
|
37
|
+
const commander_1 = require("commander");
|
|
38
|
+
const onboard_1 = require("./commands/onboard");
|
|
39
|
+
const scan_1 = require("./commands/scan");
|
|
40
|
+
const analyze_1 = require("./commands/analyze");
|
|
41
|
+
const report_1 = require("./commands/report");
|
|
42
|
+
const config_1 = require("./commands/config");
|
|
43
|
+
const doctor_1 = require("./commands/doctor");
|
|
44
|
+
const CLI_VERSION = "0.1.0";
|
|
45
|
+
// ─── ANSI Color Helpers ────────────────────────────────────────────
|
|
46
|
+
const c = {
|
|
47
|
+
reset: "\x1b[0m",
|
|
48
|
+
bold: "\x1b[1m",
|
|
49
|
+
dim: "\x1b[2m",
|
|
50
|
+
red: "\x1b[31m",
|
|
51
|
+
green: "\x1b[32m",
|
|
52
|
+
yellow: "\x1b[33m",
|
|
53
|
+
blue: "\x1b[34m",
|
|
54
|
+
magenta: "\x1b[35m",
|
|
55
|
+
cyan: "\x1b[36m",
|
|
56
|
+
white: "\x1b[37m",
|
|
57
|
+
gray: "\x1b[90m",
|
|
58
|
+
bgBlue: "\x1b[44m",
|
|
59
|
+
bgMagenta: "\x1b[45m",
|
|
60
|
+
brightCyan: "\x1b[96m",
|
|
61
|
+
brightMagenta: "\x1b[95m",
|
|
62
|
+
brightBlue: "\x1b[94m",
|
|
63
|
+
brightGreen: "\x1b[92m",
|
|
64
|
+
brightYellow: "\x1b[93m",
|
|
65
|
+
brightWhite: "\x1b[97m",
|
|
66
|
+
};
|
|
67
|
+
// ─── ASCII Art Banner ──────────────────────────────────────────────
|
|
68
|
+
function printBanner() {
|
|
69
|
+
// Sleek ANSI Shadow style — KRAMSCAN
|
|
70
|
+
const lines = [
|
|
71
|
+
`██╗ ██╗██████╗ █████╗ ███╗ ███╗███████╗ ██████╗ █████╗ ███╗ ██╗`,
|
|
72
|
+
`██║ ██╔╝██╔══██╗██╔══██╗████╗ ████║██╔════╝██╔════╝██╔══██╗████╗ ██║`,
|
|
73
|
+
`█████╔╝ ██████╔╝███████║██╔████╔██║███████╗██║ ███████║██╔██╗ ██║`,
|
|
74
|
+
`██╔═██╗ ██╔══██╗██╔══██║██║╚██╔╝██║╚════██║██║ ██╔══██║██║╚██╗██║`,
|
|
75
|
+
`██║ ██╗██║ ██║██║ ██║██║ ╚═╝ ██║███████║╚██████╗██║ ██║██║ ╚████║`,
|
|
76
|
+
`╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝ ╚═════╝╚═╝ ╚═╝╚═╝ ╚═══╝`,
|
|
77
|
+
];
|
|
78
|
+
console.log("");
|
|
79
|
+
lines.forEach((line, i) => {
|
|
80
|
+
const shade = i % 2 === 0 ? c.brightWhite : c.gray;
|
|
81
|
+
console.log(` ${shade}${line}${c.reset}`);
|
|
82
|
+
});
|
|
83
|
+
console.log("");
|
|
84
|
+
}
|
|
85
|
+
// ─── Dashboard Info ────────────────────────────────────────────────
|
|
86
|
+
function printInfo() {
|
|
87
|
+
console.log(` ${c.gray}${c.dim}───────────────────────────────────────────────────────${c.reset}`);
|
|
88
|
+
console.log(` ${c.brightWhite}${c.bold} KramScan${c.reset} ${c.gray}v${CLI_VERSION}${c.reset} ${c.dim}${c.gray}|${c.reset} ${c.cyan}AI-Powered Web Security Scanner${c.reset}`);
|
|
89
|
+
console.log(` ${c.gray}${c.dim}───────────────────────────────────────────────────────${c.reset}`);
|
|
90
|
+
console.log("");
|
|
91
|
+
console.log(` ${c.brightYellow}${c.bold}Tips for getting started:${c.reset}`);
|
|
92
|
+
console.log(` ${c.white}1.${c.reset} ${c.gray}Run${c.reset} ${c.cyan}kramscan onboard${c.reset} ${c.gray}to configure your API keys.${c.reset}`);
|
|
93
|
+
console.log(` ${c.white}2.${c.reset} ${c.gray}Run${c.reset} ${c.cyan}kramscan scan <url>${c.reset} ${c.gray}to scan a target.${c.reset}`);
|
|
94
|
+
console.log(` ${c.white}3.${c.reset} ${c.gray}Run${c.reset} ${c.cyan}kramscan --help${c.reset} ${c.gray}for all commands.${c.reset}`);
|
|
95
|
+
console.log("");
|
|
96
|
+
}
|
|
97
|
+
const menuChoices = [
|
|
98
|
+
{ label: "Onboard", value: "onboard", description: "First-time setup wizard", icon: "⚡", status: "active" },
|
|
99
|
+
{ label: "Scan", value: "scan", description: "Scan a target URL for vulnerabilities", icon: "🔍", status: "active" },
|
|
100
|
+
{ label: "Analyze", value: "analyze", description: "Deep AI analysis of scan results", icon: "🧠", status: "active" },
|
|
101
|
+
{ label: "Report", value: "report", description: "Generate a professional report", icon: "📄", status: "active" },
|
|
102
|
+
{ label: "Config", value: "config", description: "View or edit your configuration", icon: "⚙️", status: "active" },
|
|
103
|
+
{ label: "Doctor", value: "doctor", description: "Check environment health", icon: "🩺", status: "active" },
|
|
104
|
+
{ label: "Exit", value: "exit", description: "Quit KramScan", icon: "👋", status: "active" },
|
|
105
|
+
];
|
|
106
|
+
async function showInteractiveMenu() {
|
|
107
|
+
printBanner();
|
|
108
|
+
printInfo();
|
|
109
|
+
// Use readline for a simple, CommonJS-compatible interactive menu
|
|
110
|
+
const readline = await Promise.resolve().then(() => __importStar(require("readline")));
|
|
111
|
+
const rl = readline.createInterface({
|
|
112
|
+
input: process.stdin,
|
|
113
|
+
output: process.stdout,
|
|
114
|
+
});
|
|
115
|
+
function renderMenu(selectedIndex) {
|
|
116
|
+
// Move cursor up to redraw the menu (clear previous render)
|
|
117
|
+
if (selectedIndex >= 0) {
|
|
118
|
+
process.stdout.write(`\x1b[${menuChoices.length + 2}A`);
|
|
119
|
+
}
|
|
120
|
+
console.log(` ${c.brightWhite}${c.bold}What would you like to do?${c.reset}`);
|
|
121
|
+
console.log("");
|
|
122
|
+
menuChoices.forEach((choice, i) => {
|
|
123
|
+
const isSelected = i === selectedIndex;
|
|
124
|
+
const statusTag = choice.status === "coming_soon"
|
|
125
|
+
? ` ${c.yellow}[coming soon]${c.reset}`
|
|
126
|
+
: "";
|
|
127
|
+
if (isSelected) {
|
|
128
|
+
console.log(` ${c.brightCyan}${c.bold}❯ ${choice.icon} ${choice.label}${c.reset}${statusTag} ${c.dim}${c.gray}— ${choice.description}${c.reset}`);
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
console.log(` ${choice.icon} ${c.white}${choice.label}${c.reset}${statusTag} ${c.dim}${c.gray}— ${choice.description}${c.reset}`);
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
return new Promise((resolve) => {
|
|
136
|
+
let selectedIndex = 0;
|
|
137
|
+
// Enable raw mode for arrow key support
|
|
138
|
+
if (process.stdin.isTTY) {
|
|
139
|
+
process.stdin.setRawMode(true);
|
|
140
|
+
}
|
|
141
|
+
process.stdin.resume();
|
|
142
|
+
renderMenu(-1); // Initial render (no cursor-up needed)
|
|
143
|
+
process.stdin.on("data", async (key) => {
|
|
144
|
+
const str = key.toString();
|
|
145
|
+
if (str === "\x1b[A") {
|
|
146
|
+
// Arrow Up
|
|
147
|
+
selectedIndex = (selectedIndex - 1 + menuChoices.length) % menuChoices.length;
|
|
148
|
+
renderMenu(selectedIndex);
|
|
149
|
+
}
|
|
150
|
+
else if (str === "\x1b[B") {
|
|
151
|
+
// Arrow Down
|
|
152
|
+
selectedIndex = (selectedIndex + 1) % menuChoices.length;
|
|
153
|
+
renderMenu(selectedIndex);
|
|
154
|
+
}
|
|
155
|
+
else if (str === "\r" || str === "\n") {
|
|
156
|
+
// Enter
|
|
157
|
+
if (process.stdin.isTTY) {
|
|
158
|
+
process.stdin.setRawMode(false);
|
|
159
|
+
}
|
|
160
|
+
process.stdin.pause();
|
|
161
|
+
rl.close();
|
|
162
|
+
const selected = menuChoices[selectedIndex];
|
|
163
|
+
console.log("");
|
|
164
|
+
if (selected.value === "exit") {
|
|
165
|
+
console.log(` ${c.gray}${c.dim}Goodbye! 👋${c.reset}`);
|
|
166
|
+
console.log("");
|
|
167
|
+
resolve();
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
if (selected.status === "coming_soon") {
|
|
171
|
+
console.log(` ${c.yellow}⚠ ${selected.label}${c.reset} ${c.gray}is coming soon. Stay tuned!${c.reset}`);
|
|
172
|
+
console.log(` ${c.gray}Run ${c.cyan}kramscan --help${c.gray} for available commands.${c.reset}`);
|
|
173
|
+
console.log("");
|
|
174
|
+
resolve();
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
// Execute the selected command
|
|
178
|
+
console.log(` ${c.brightGreen}▶${c.reset} ${c.bold}Launching ${selected.label}...${c.reset}`);
|
|
179
|
+
console.log("");
|
|
180
|
+
// Re-run with the command argument
|
|
181
|
+
process.argv.push(selected.value);
|
|
182
|
+
const program = createProgram();
|
|
183
|
+
await program.parseAsync(process.argv);
|
|
184
|
+
resolve();
|
|
185
|
+
}
|
|
186
|
+
else if (str === "\x03") {
|
|
187
|
+
// Ctrl+C
|
|
188
|
+
if (process.stdin.isTTY) {
|
|
189
|
+
process.stdin.setRawMode(false);
|
|
190
|
+
}
|
|
191
|
+
process.stdin.pause();
|
|
192
|
+
rl.close();
|
|
193
|
+
console.log(`\n ${c.gray}${c.dim}Interrupted. Goodbye! 👋${c.reset}\n`);
|
|
194
|
+
process.exit(0);
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
// ─── Program Setup ─────────────────────────────────────────────────
|
|
200
|
+
function createProgram() {
|
|
201
|
+
const program = new commander_1.Command();
|
|
202
|
+
program
|
|
203
|
+
.name("kramscan")
|
|
204
|
+
.description("KramScan — AI-powered web app security testing")
|
|
205
|
+
.version(CLI_VERSION);
|
|
206
|
+
(0, onboard_1.registerOnboardCommand)(program);
|
|
207
|
+
(0, scan_1.registerScanCommand)(program);
|
|
208
|
+
(0, analyze_1.registerAnalyzeCommand)(program);
|
|
209
|
+
(0, report_1.registerReportCommand)(program);
|
|
210
|
+
(0, config_1.registerConfigCommand)(program);
|
|
211
|
+
(0, doctor_1.registerDoctorCommand)(program);
|
|
212
|
+
return program;
|
|
213
|
+
}
|
|
214
|
+
// ─── Entry Point ───────────────────────────────────────────────────
|
|
215
|
+
async function run() {
|
|
216
|
+
const args = process.argv.slice(2);
|
|
217
|
+
// If no command is provided, show the interactive dashboard
|
|
218
|
+
if (args.length === 0) {
|
|
219
|
+
await showInteractiveMenu();
|
|
220
|
+
}
|
|
221
|
+
else {
|
|
222
|
+
const program = createProgram();
|
|
223
|
+
await program.parseAsync();
|
|
224
|
+
}
|
|
225
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
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.registerAnalyzeCommand = registerAnalyzeCommand;
|
|
7
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
8
|
+
const ai_client_1 = require("../core/ai-client");
|
|
9
|
+
const logger_1 = require("../utils/logger");
|
|
10
|
+
const promises_1 = __importDefault(require("fs/promises"));
|
|
11
|
+
const path_1 = __importDefault(require("path"));
|
|
12
|
+
const os_1 = __importDefault(require("os"));
|
|
13
|
+
function registerAnalyzeCommand(program) {
|
|
14
|
+
program
|
|
15
|
+
.command("analyze [scan-file]")
|
|
16
|
+
.description("AI-powered analysis of scan results")
|
|
17
|
+
.option("-m, --model <name>", "Override default AI model")
|
|
18
|
+
.option("-v, --verbose", "Show detailed analysis")
|
|
19
|
+
.action(async (scanFile, options) => {
|
|
20
|
+
console.log("");
|
|
21
|
+
console.log(chalk_1.default.bold.cyan("🧠 AI Security Analysis"));
|
|
22
|
+
console.log(chalk_1.default.gray("─".repeat(50)));
|
|
23
|
+
console.log("");
|
|
24
|
+
try {
|
|
25
|
+
// Load scan results
|
|
26
|
+
let filepath;
|
|
27
|
+
if (scanFile) {
|
|
28
|
+
filepath = path_1.default.isAbsolute(scanFile)
|
|
29
|
+
? scanFile
|
|
30
|
+
: path_1.default.join(process.cwd(), scanFile);
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
// Find latest scan
|
|
34
|
+
const scanDir = path_1.default.join(os_1.default.homedir(), ".kramscan", "scans");
|
|
35
|
+
const files = await promises_1.default.readdir(scanDir);
|
|
36
|
+
const scanFiles = files.filter((f) => f.endsWith(".json"));
|
|
37
|
+
if (scanFiles.length === 0) {
|
|
38
|
+
logger_1.logger.error("No scan results found. Run 'kramscan scan <url>' first.");
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
// Get most recent
|
|
42
|
+
scanFiles.sort().reverse();
|
|
43
|
+
filepath = path_1.default.join(scanDir, scanFiles[0]);
|
|
44
|
+
logger_1.logger.info(`Using latest scan: ${scanFiles[0]}`);
|
|
45
|
+
}
|
|
46
|
+
const content = await promises_1.default.readFile(filepath, "utf-8");
|
|
47
|
+
const scanResult = JSON.parse(content);
|
|
48
|
+
if (scanResult.vulnerabilities.length === 0) {
|
|
49
|
+
logger_1.logger.success("No vulnerabilities to analyze!");
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
const spinner = logger_1.logger.spinner("Analyzing vulnerabilities with AI...");
|
|
53
|
+
// Create AI client
|
|
54
|
+
const aiClient = (0, ai_client_1.createAIClient)();
|
|
55
|
+
// Build analysis prompt
|
|
56
|
+
const prompt = buildAnalysisPrompt(scanResult);
|
|
57
|
+
// Get AI analysis
|
|
58
|
+
const response = await aiClient.analyze(prompt);
|
|
59
|
+
spinner.succeed("Analysis complete!");
|
|
60
|
+
console.log("");
|
|
61
|
+
console.log(chalk_1.default.bold("📝 AI Analysis"));
|
|
62
|
+
console.log(chalk_1.default.gray("─".repeat(50)));
|
|
63
|
+
console.log("");
|
|
64
|
+
console.log(response.content);
|
|
65
|
+
console.log("");
|
|
66
|
+
if (response.usage) {
|
|
67
|
+
console.log(chalk_1.default.gray("─".repeat(50)));
|
|
68
|
+
console.log(chalk_1.default.gray(`Tokens used: ${response.usage.totalTokens} (${response.usage.promptTokens} prompt + ${response.usage.completionTokens} completion)`));
|
|
69
|
+
console.log("");
|
|
70
|
+
}
|
|
71
|
+
// Save enhanced results
|
|
72
|
+
const enhancedResult = {
|
|
73
|
+
...scanResult,
|
|
74
|
+
aiAnalysis: {
|
|
75
|
+
timestamp: new Date().toISOString(),
|
|
76
|
+
analysis: response.content,
|
|
77
|
+
usage: response.usage,
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
await promises_1.default.writeFile(filepath, JSON.stringify(enhancedResult, null, 2));
|
|
81
|
+
logger_1.logger.success(`Enhanced results saved to ${filepath}`);
|
|
82
|
+
console.log("");
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
logger_1.logger.error(error.message);
|
|
86
|
+
process.exit(1);
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
function buildAnalysisPrompt(scanResult) {
|
|
91
|
+
const vulnList = scanResult.vulnerabilities
|
|
92
|
+
.map((v, i) => `${i + 1}. [${v.severity.toUpperCase()}] ${v.title}
|
|
93
|
+
URL: ${v.url}
|
|
94
|
+
Description: ${v.description}
|
|
95
|
+
${v.evidence ? `Evidence: ${v.evidence}` : ""}
|
|
96
|
+
${v.cwe ? `CWE: ${v.cwe}` : ""}`)
|
|
97
|
+
.join("\n\n");
|
|
98
|
+
return `You are a security expert analyzing web application vulnerabilities.
|
|
99
|
+
|
|
100
|
+
Target: ${scanResult.target}
|
|
101
|
+
Scan Date: ${scanResult.timestamp}
|
|
102
|
+
Total Vulnerabilities: ${scanResult.summary.total}
|
|
103
|
+
|
|
104
|
+
Vulnerabilities Found:
|
|
105
|
+
${vulnList}
|
|
106
|
+
|
|
107
|
+
Please provide:
|
|
108
|
+
1. **Executive Summary**: Brief overview of the security posture
|
|
109
|
+
2. **Risk Assessment**: Overall risk level and business impact
|
|
110
|
+
3. **Priority Recommendations**: Top 3-5 vulnerabilities to fix first, with specific remediation steps
|
|
111
|
+
4. **Attack Scenarios**: How an attacker could chain these vulnerabilities
|
|
112
|
+
5. **Remediation Roadmap**: Step-by-step plan to address all findings
|
|
113
|
+
|
|
114
|
+
Format your response in clear markdown with headers and bullet points.`;
|
|
115
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
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.registerConfigCommand = registerConfigCommand;
|
|
7
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
8
|
+
const inquirer_1 = __importDefault(require("inquirer"));
|
|
9
|
+
const config_1 = require("../core/config");
|
|
10
|
+
const logger_1 = require("../utils/logger");
|
|
11
|
+
function registerConfigCommand(program) {
|
|
12
|
+
const configCmd = program
|
|
13
|
+
.command("config")
|
|
14
|
+
.description("View or edit configuration");
|
|
15
|
+
configCmd
|
|
16
|
+
.command("get <key>")
|
|
17
|
+
.description("Get a configuration value")
|
|
18
|
+
.action((key) => {
|
|
19
|
+
const config = (0, config_1.getConfig)();
|
|
20
|
+
const value = getNestedValue(config, key);
|
|
21
|
+
if (value === undefined) {
|
|
22
|
+
logger_1.logger.error(`Configuration key '${key}' not found`);
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
console.log(chalk_1.default.cyan(key), "=", chalk_1.default.white(JSON.stringify(value, null, 2)));
|
|
26
|
+
});
|
|
27
|
+
configCmd
|
|
28
|
+
.command("set <key> <value>")
|
|
29
|
+
.description("Set a configuration value")
|
|
30
|
+
.action((key, value) => {
|
|
31
|
+
const config = (0, config_1.getConfig)();
|
|
32
|
+
// Parse value
|
|
33
|
+
let parsedValue = value;
|
|
34
|
+
if (value === "true")
|
|
35
|
+
parsedValue = true;
|
|
36
|
+
else if (value === "false")
|
|
37
|
+
parsedValue = false;
|
|
38
|
+
else if (!isNaN(Number(value)))
|
|
39
|
+
parsedValue = Number(value);
|
|
40
|
+
setNestedValue(config, key, parsedValue);
|
|
41
|
+
(0, config_1.setConfig)(config);
|
|
42
|
+
logger_1.logger.success(`Set ${chalk_1.default.cyan(key)} = ${chalk_1.default.white(JSON.stringify(parsedValue))}`);
|
|
43
|
+
});
|
|
44
|
+
configCmd
|
|
45
|
+
.command("list")
|
|
46
|
+
.description("List all configuration")
|
|
47
|
+
.action(() => {
|
|
48
|
+
const config = (0, config_1.getConfig)();
|
|
49
|
+
console.log("");
|
|
50
|
+
console.log(chalk_1.default.bold.cyan("📋 Current Configuration"));
|
|
51
|
+
console.log(chalk_1.default.gray("─".repeat(50)));
|
|
52
|
+
console.log("");
|
|
53
|
+
console.log(JSON.stringify(config, null, 2));
|
|
54
|
+
console.log("");
|
|
55
|
+
});
|
|
56
|
+
configCmd
|
|
57
|
+
.command("edit")
|
|
58
|
+
.description("Interactively edit configuration")
|
|
59
|
+
.action(async () => {
|
|
60
|
+
const config = (0, config_1.getConfig)();
|
|
61
|
+
console.log("");
|
|
62
|
+
console.log(chalk_1.default.bold.cyan("⚙️ Configuration Editor"));
|
|
63
|
+
console.log(chalk_1.default.gray("─".repeat(50)));
|
|
64
|
+
console.log("");
|
|
65
|
+
const answers = await inquirer_1.default.prompt([
|
|
66
|
+
{
|
|
67
|
+
type: "confirm",
|
|
68
|
+
name: "aiEnabled",
|
|
69
|
+
message: "Enable AI analysis?",
|
|
70
|
+
default: config.ai.enabled,
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
type: "list",
|
|
74
|
+
name: "aiProvider",
|
|
75
|
+
message: "AI provider:",
|
|
76
|
+
choices: ["openai", "anthropic"],
|
|
77
|
+
default: config.ai.provider,
|
|
78
|
+
when: (answers) => answers.aiEnabled,
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
type: "input",
|
|
82
|
+
name: "apiKey",
|
|
83
|
+
message: "API key:",
|
|
84
|
+
default: config.ai.apiKey,
|
|
85
|
+
when: (answers) => answers.aiEnabled,
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
type: "input",
|
|
89
|
+
name: "model",
|
|
90
|
+
message: "Default AI model:",
|
|
91
|
+
default: config.ai.defaultModel,
|
|
92
|
+
when: (answers) => answers.aiEnabled,
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
type: "list",
|
|
96
|
+
name: "reportFormat",
|
|
97
|
+
message: "Default report format:",
|
|
98
|
+
choices: ["word", "json", "txt"],
|
|
99
|
+
default: config.report.defaultFormat,
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
type: "number",
|
|
103
|
+
name: "rateLimit",
|
|
104
|
+
message: "Requests per second rate limit:",
|
|
105
|
+
default: config.scan.rateLimitPerSecond,
|
|
106
|
+
},
|
|
107
|
+
]);
|
|
108
|
+
// Update config
|
|
109
|
+
if (answers.aiEnabled !== undefined)
|
|
110
|
+
config.ai.enabled = answers.aiEnabled;
|
|
111
|
+
if (answers.aiProvider)
|
|
112
|
+
config.ai.provider = answers.aiProvider;
|
|
113
|
+
if (answers.apiKey)
|
|
114
|
+
config.ai.apiKey = answers.apiKey;
|
|
115
|
+
if (answers.model)
|
|
116
|
+
config.ai.defaultModel = answers.model;
|
|
117
|
+
if (answers.reportFormat)
|
|
118
|
+
config.report.defaultFormat = answers.reportFormat;
|
|
119
|
+
if (answers.rateLimit)
|
|
120
|
+
config.scan.rateLimitPerSecond = answers.rateLimit;
|
|
121
|
+
(0, config_1.setConfig)(config);
|
|
122
|
+
console.log("");
|
|
123
|
+
logger_1.logger.success("Configuration updated successfully!");
|
|
124
|
+
console.log("");
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
function getNestedValue(obj, path) {
|
|
128
|
+
return path.split(".").reduce((current, key) => current?.[key], obj);
|
|
129
|
+
}
|
|
130
|
+
function setNestedValue(obj, path, value) {
|
|
131
|
+
const keys = path.split(".");
|
|
132
|
+
const lastKey = keys.pop();
|
|
133
|
+
const target = keys.reduce((current, key) => {
|
|
134
|
+
if (!current[key])
|
|
135
|
+
current[key] = {};
|
|
136
|
+
return current[key];
|
|
137
|
+
}, obj);
|
|
138
|
+
target[lastKey] = value;
|
|
139
|
+
}
|