kramscan 0.3.1 β†’ 0.4.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 CHANGED
@@ -3,298 +3,160 @@
3
3
 
4
4
  <h3 align="center">AI-Powered Web Application Security Testing CLI</h3>
5
5
 
6
- <br />
7
-
6
+ [![CI](https://img.shields.io/github/actions/workflow/status/shaikhakramshakil/kramscan/ci.yml?branch=main&style=for-the-badge&logo=github-actions&logoColor=white&label=CI)](https://github.com/shaikhakramshakil/kramscan/actions)
8
7
  [![npm version](https://img.shields.io/npm/v/kramscan?style=for-the-badge&logo=npm&logoColor=white&color=cb3837)](https://www.npmjs.com/package/kramscan)
9
8
  [![npm downloads](https://img.shields.io/npm/dm/kramscan?style=for-the-badge&logo=npm&logoColor=white&color=blue)](https://www.npmjs.com/package/kramscan)
10
9
  [![License](https://img.shields.io/github/license/shaikhakramshakil/kramscan?style=for-the-badge&logo=github&logoColor=white&color=green)](https://github.com/shaikhakramshakil/kramscan/blob/main/LICENSE)
11
- [![Stars](https://img.shields.io/github/stars/shaikhakramshakil/kramscan?style=for-the-badge&logo=github&logoColor=white&color=yellow)](https://github.com/shaikhakramshakil/kramscan)
12
10
  [![TypeScript](https://img.shields.io/badge/TypeScript-5.4-3178c6?style=for-the-badge&logo=typescript&logoColor=white)](https://www.typescriptlang.org)
13
11
  [![Node.js](https://img.shields.io/badge/Node.js-%3E%3D18-brightgreen?style=for-the-badge&logo=nodedotjs&logoColor=white)](https://nodejs.org)
14
12
 
15
- <br />
16
-
17
- πŸ”¬ **A next-generation security auditing tool that combines automated vulnerability scanning with multi-provider AI analysis.**
18
-
19
- *Empowering developers and security researchers with institutional-grade insights, modular plugin architecture, and an interactive AI agent.*
20
-
21
- <br />
22
-
23
- [🌐 NPM Package](https://www.npmjs.com/package/kramscan) Β· [πŸ“– Documentation](#-usage) Β· [🐞 Report Bug](https://github.com/shaikhakramshakil/kramscan/issues)
24
-
25
13
  </div>
26
14
 
27
15
  ---
28
16
 
29
- <br />
17
+ KramScan is a command-line security auditing tool that combines automated vulnerability scanning with multi-provider AI analysis. It orchestrates headless browser crawling, runs a modular plugin system against discovered pages, and passes findings through a generative AI layer (OpenAI, Gemini, Anthropic, and others) to produce actionable, context-aware reports.
30
18
 
31
- ## πŸš€ The Problem We Solve
32
- Web security is complex and often fragmented. Developers rely on multiple disjointed tools for scanning, manual testing, and reporting. Traditional automated scanners generate noise without context, and manual pentesting is time-consuming and expensive.
33
-
34
- **KramScan bridges this gap.** We provide a unified command-line interface that orchestrates headless browser scanning, scrapes critical security headers, leverages **Generative AI** (OpenAI, Gemini, Anthropic) for analysis, and features a **modular plugin system** for extensibility. It delivers actionable, human-readable insights alongside raw vulnerability dataβ€”all in seconds.
35
-
36
- <br />
19
+ [NPM Package](https://www.npmjs.com/package/kramscan) Β· [Documentation](#usage--commands) Β· [Report Bug](https://github.com/shaikhakramshakil/kramscan/issues) Β· [Request Feature](https://github.com/shaikhakramshakil/kramscan/issues)
37
20
 
38
21
  ---
39
22
 
40
- <br />
41
-
42
- ## ✨ Key Features
43
- | Feature | Description |
44
- | :--- | :--- |
45
- | πŸ” **Automated Vulnerability Engine** | Detects XSS, SQL Injection, CSRF, insecure headers, CORS misconfigs, open redirects, and more. |
46
- | πŸ”Œ **10 Built-in Security Plugins** | CORS, debug endpoints, directory traversal, cookie auditing, open redirects, sensitive data, and more. |
47
- | πŸ› οΈ **Dev Mode (Watch Scanner)** | Watch your localhost for file changes and auto re-scan with diff reports (new vs resolved vulns). |
48
- | 🚧 **CI/CD Security Gate** | `kramscan gate` exits with code 1 if vulnerabilities exceed your threshold. Plug into any pipeline. |
49
- | πŸ€– **Interactive AI Agent** | A conversational security assistant with **Autonomous Verification** skills to confirm findings live. |
50
- | 🧠 **Multi-Provider AI Analysis** | Supports OpenAI, Anthropic, Google Gemini, Mistral, OpenRouter, and more for results auditing. |
51
- | πŸ“ **AI Executive Summaries** | Automatically generates business-oriented summaries for Word, JSON, and TXT reports. |
52
- | πŸ“Š **Event-Driven Feedback** | Real-time progress updates with dynamic spinners and live vulnerability alerts during scanning. |
53
- | πŸ“„ **Professional Reporting** | Generates detailed PDF, DOCX, TXT, and JSON reports with remediation steps and error tracking. |
54
- | 🌐 **Headless Browser Testing** | Renders modern SPAs (Single Page Applications) to find vulnerabilities in dynamic content. |
55
- | ⚑ **Smarter User Flow** | Interactive menu and post-scan "Golden Path" prompts for a guided experience. |
56
- | πŸ›‘οΈ **Error Resilience** | Robust configuration defaults and graceful recovery if individual URLs or plugins fail. |
57
-
58
- <br />
23
+ ## Features
24
+
25
+ - **Automated vulnerability detection** β€” XSS, SQL injection, CSRF, insecure headers, CORS misconfigurations, open redirects, and more.
26
+ - **10 built-in security plugins** β€” CORS, debug endpoints, directory traversal, cookie auditing, open redirects, sensitive data exposure, and others. Easily extensible.
27
+ - **Dev mode (watch scanner)** β€” Watches your localhost for file changes and auto re-scans, showing a diff of new vs. resolved findings.
28
+ - **CI/CD security gate** β€” `kramscan gate` exits with code 1 when vulnerabilities exceed a configurable threshold.
29
+ - **Interactive AI agent** β€” Conversational security assistant with autonomous verification capabilities to confirm findings live.
30
+ - **Multi-provider AI analysis** β€” Supports OpenAI, Anthropic, Google Gemini, Mistral, OpenRouter, Groq, and Kimi.
31
+ - **AI executive summaries** β€” Generates business-oriented summaries included in Word, JSON, and TXT reports.
32
+ - **Professional reporting** β€” PDF, DOCX, Markdown, TXT, and JSON output with remediation steps and error tracking.
33
+ - **Headless browser testing** β€” Renders SPAs via Puppeteer to find vulnerabilities in dynamically generated content.
34
+ - **Real-time feedback** β€” Event-driven progress with live spinners and vulnerability alerts during scanning.
35
+ - **Error resilience** β€” Graceful recovery when individual URLs or plugins fail; errors are logged but never halt a scan.
59
36
 
60
37
  ---
61
38
 
62
- <br />
39
+ ## Quick Start
63
40
 
64
- ## πŸ—οΈ Architecture & Workflow
41
+ ### Installation
65
42
 
66
- ```mermaid
67
- graph LR
68
- A[User Command] --> B{CLI Controller};
69
- B --> C[Scanner Module<br/>Puppeteer / Plugin System];
70
- B --> D[AI Agent<br/>NLP Processing];
71
-
72
- C --> E[Plugin Manager<br/>XSS / SQLi / Headers / CSRF];
73
- E --> F[Vulnerability Detection];
74
- C --> G[Event System<br/>Progress / Results];
75
-
76
- F & G --> H[AI Analysis Engine<br/>LLM Provider];
77
-
78
- H --> I[Risk Assessment<br/>Confidence Scoring];
79
- I --> J[Report Generator<br/>PDF / DOCX / JSON / TXT];
80
- J --> K((Final Output<br/>+ Error Report));
43
+ ```bash
44
+ npm install -g kramscan
81
45
  ```
82
46
 
83
- <br />
84
-
85
- ### Plugin Architecture
47
+ Or run directly without installing:
86
48
 
87
- KramScan is built on a modular plugin system that makes extending vulnerability detection effortless:
88
-
89
- ```
90
- src/plugins/
91
- β”œβ”€β”€ types.ts # Base interfaces and types
92
- β”œβ”€β”€ PluginManager.ts # Plugin orchestration
93
- β”œβ”€β”€ index.ts # Plugin exports
94
- └── vulnerabilities/ # Built-in plugins
95
- β”œβ”€β”€ XSSPlugin.ts # Cross-Site Scripting
96
- β”œβ”€β”€ SQLInjectionPlugin.ts # SQL Injection
97
- β”œβ”€β”€ SecurityHeadersPlugin.ts # Missing security headers
98
- β”œβ”€β”€ SensitiveDataPlugin.ts # Exposed secrets & API keys
99
- β”œβ”€β”€ CSRFPlugin.ts # Cross-Site Request Forgery
100
- β”œβ”€β”€ CORSAnalyzerPlugin.ts # CORS misconfiguration
101
- β”œβ”€β”€ DebugEndpointPlugin.ts # Exposed debug/dev endpoints
102
- β”œβ”€β”€ DirectoryTraversalPlugin.ts # Path traversal / LFI
103
- β”œβ”€β”€ CookieSecurityPlugin.ts # Insecure cookie flags
104
- └── OpenRedirectPlugin.ts # Open redirect detection
49
+ ```bash
50
+ npx kramscan scan https://example.com
105
51
  ```
106
52
 
107
- **Creating a custom plugin:**
108
-
109
- ```typescript
110
- import { BaseVulnerabilityPlugin, PluginContext } from 'kramscan/plugins';
53
+ ### First-Time Setup
111
54
 
112
- export class MyCustomPlugin extends BaseVulnerabilityPlugin {
113
- readonly name = "Custom Detector";
114
- readonly type = "custom";
115
- readonly description = "Detects custom vulnerability";
116
-
117
- async testParameter(context: PluginContext, param: string, value: string) {
118
- // Your detection logic here
119
- if (/* vulnerability found */) {
120
- return this.success(this.createVulnerability(
121
- "Custom Vulnerability",
122
- "Description...",
123
- context.url,
124
- "high",
125
- "Evidence...",
126
- "Remediation..."
127
- ));
128
- }
129
- return this.failure();
130
- }
131
- }
55
+ ```bash
56
+ kramscan onboard
132
57
  ```
133
58
 
134
- <br />
135
-
136
- ---
137
-
138
- <br />
139
-
140
- ## πŸ§ͺ Tech Stack
141
- <div align="center">
142
-
143
- | Component | Technology |
144
- | :--- | :--- |
145
- | **Runtime** | Node.js β‰₯ 18 |
146
- | **Language** | TypeScript 5.4 |
147
- | **CLI Framework** | Commander.js, Inquirer.js |
148
- | **Browser Automation** | Puppeteer (Headless Chrome) |
149
- | **AI Integration** | OpenAI SDK, Google Generative AI, Anthropic SDK |
150
- | **Schema Validation** | Zod |
151
- | **Reporting** | Docx, Puppeteer (PDF), Chalk |
152
- | **Package Manager** | NPM / Yarn / PNPM |
153
-
154
- </div>
155
-
156
- <br />
157
-
158
- ---
159
-
160
- <br />
161
-
162
- ## 🧠 Supported AI Providers
163
-
164
- | Provider | SDK / Integration | Default Model |
165
- | :--- | :--- | :--- |
166
- | **OpenAI** | `openai` | `gpt-4` |
167
- | **Anthropic** | `@anthropic-ai/sdk` | `claude-3-5-sonnet-20241022` |
168
- | **Google Gemini** | `@google/generative-ai` | `gemini-2.0-flash` |
169
- | **Mistral** | `@mistralai/mistralai` | `mistral-large-latest` |
170
- | **OpenRouter** | OpenAI-compatible | `anthropic/claude-3.5-sonnet` |
171
- | **Kimi** | OpenAI-compatible | `moonshot-v1-8k` |
172
- | **Groq** | OpenAI-compatible | `llama-3.1-8b-instant` |
173
-
174
- > Switch providers instantly with `kramscan onboard` or by editing `~/.kramscan/config.json`.
175
-
176
- ### API Key Environment Variables
177
- You can provide API keys via environment variables (useful for CI/CD) instead of saving them locally:
59
+ This runs the configuration wizard to set up your AI provider and API keys. KramScan auto-detects keys already present in your environment variables.
178
60
 
179
- | Provider | Env Var |
180
- | :--- | :--- |
181
- | OpenAI | `OPENAI_API_KEY` |
182
- | Anthropic | `ANTHROPIC_API_KEY` |
183
- | Gemini | `GEMINI_API_KEY` |
184
- | Mistral | `MISTRAL_API_KEY` |
185
- | OpenRouter | `OPENROUTER_API_KEY` |
186
- | Kimi | `KIMI_API_KEY` |
187
- | Groq | `GROQ_API_KEY` |
188
-
189
- ### Smart Environment Detection
190
- KramScan automatically detects API keys in your environment variables. During `kramscan onboard`, the tool will identify and pre-configure providers like OpenAI, Anthropic, and Gemini if their keys are found in your session.
191
-
192
- ### AI-Powered Context-Aware Payloads
193
- The scanning engine utilizes AI to generate payloads tailored to the specific context of your application, significantly increasing detection rates against filtered inputs and complex WAFs.
194
-
195
- ### Autonomous Finding Verification
196
- The `kramscan agent` independently verifies reported vulnerabilities using non-destructive, context-aware payloads to differentiate between theoretical findings and exploitable risks.
197
-
198
- <br />
199
-
200
- ---
201
-
202
- <br />
203
-
204
- ## πŸš€ Quick Start
205
-
206
- ### 1. Installation
207
- Install KramScan globally using npm:
61
+ ### Run a Scan
208
62
 
209
63
  ```bash
210
- npm install -g kramscan
64
+ kramscan scan https://example.com
211
65
  ```
212
66
 
213
- ### 2. First-Time Setup
214
- Initialize the configuration wizard to set up your AI provider and API keys:
67
+ ### View Results
215
68
 
216
69
  ```bash
217
- kramscan onboard
70
+ kramscan scans list # List recent scans
71
+ kramscan scans latest # View the latest scan
218
72
  ```
219
73
 
220
- ### 3. Run a Scan
221
- Execute a full security scan on a target URL:
74
+ ---
222
75
 
223
- ```bash
224
- kramscan scan https://example.com
76
+ ## Usage & Commands
77
+
78
+ ```
79
+ kramscan Interactive dashboard menu
80
+ kramscan scan <url> Full vulnerability scan with post-scan prompts
81
+ kramscan dev [url] Watch-mode localhost scanner with diff reports
82
+ kramscan gate <url> CI/CD security gate (exits 1 on threshold breach)
83
+ kramscan agent AI security assistant with autonomous verification
84
+ kramscan analyze AI-powered analysis of scan results
85
+ kramscan report Generate reports with optional AI executive summaries
86
+ kramscan onboard Setup wizard with environment key detection
87
+ kramscan doctor Verify environment health and dependencies
88
+ kramscan config View and edit configuration
89
+ kramscan scans List and inspect recent scans
90
+ kramscan init Generate a .kramscanrc config for this project
91
+ kramscan ai AI helpers (model listing, connectivity test)
225
92
  ```
226
93
 
227
- <br />
94
+ ### Project Configuration
228
95
 
229
- ---
96
+ Run `kramscan init` in your project root to generate a `.kramscanrc` file with team-shareable defaults:
230
97
 
231
- <br />
232
-
233
- ## πŸ“– Usage & Commands
98
+ ```bash
99
+ kramscan init # interactive setup
100
+ kramscan init -y # generate with defaults
101
+ kramscan init --force # overwrite an existing .kramscanrc
102
+ ```
234
103
 
235
- | Command | Description |
236
- | :--- | :--- |
237
- | `kramscan` | Launch the interactive dashboard menu with smart argument prompting. |
238
- | `kramscan scan <url>` | Run a comprehensive vulnerability scan with post-scan prompts. |
239
- | `kramscan dev [url]` | Watch-mode localhost scanner with diff reports and desktop notifications. |
240
- | `kramscan gate <url>` | CI/CD security quality gate β€” exits with code 1 on threshold breach. |
241
- | `kramscan agent` | Start the AI security assistant with autonomous verification skills. |
242
- | `kramscan analyze` | AI-powered analysis with proactive onboarding redirection. |
243
- | `kramscan report` | Generate professional reports with optional AI executive summaries. |
244
- | `kramscan onboard` | Smart setup wizard with environment key detection. |
245
- | `kramscan doctor` | Verify environment health and check for Docker dependencies. |
246
- | `kramscan config` | View and edit current configuration with robust schema defaults. |
247
- | `kramscan scans` | List and inspect recent scans from the persistent index. |
248
- | `kramscan ai` | AI helpers (model listing and connectivity test). |
104
+ The generated file looks like this:
105
+
106
+ ```json
107
+ {
108
+ "scan": {
109
+ "defaultProfile": "balanced",
110
+ "defaultTimeout": 30000,
111
+ "strictScope": true,
112
+ "exclude": ["logout", "signout", "delete"]
113
+ },
114
+ "report": {
115
+ "defaultFormat": "markdown",
116
+ "companyName": "Your Company"
117
+ },
118
+ "gate": {
119
+ "failOn": "high",
120
+ "maxVulns": 0
121
+ },
122
+ "plugins": {
123
+ "disabled": []
124
+ }
125
+ }
126
+ ```
249
127
 
250
- <br />
128
+ Project-level settings override the global config (`~/.kramscan/config.json`). API keys are never stored in `.kramscanrc` β€” use `kramscan onboard` or environment variables for credentials. Commit the file to version control so your team shares the same scan settings.
251
129
 
252
- ### πŸ› οΈ Dev Mode β€” Localhost Watch Scanner
130
+ ### Dev Mode
253
131
 
254
- Scan your local dev server continuously. KramScan watches for file changes and **auto re-scans**, showing a diff of new vs. resolved vulnerabilities:
132
+ Scan your local dev server continuously. KramScan watches for file changes and auto re-scans, showing a diff of new vs. resolved vulnerabilities:
255
133
 
256
134
  ```bash
257
- # Watch-mode with port shorthand
258
135
  kramscan dev --port 3000
259
-
260
- # Full URL with notifications
261
136
  kramscan dev http://localhost:3000 --watch-dir ./src --notify
262
-
263
- # Single scan (no watching)
264
137
  kramscan dev http://localhost:8080 --no-watch --fail-on high
265
138
  ```
266
139
 
267
- **How it works:**
268
- 1. Probes your server until it's ready (auto-detects Express, Next.js, Django, etc.)
269
- 2. Runs an initial security scan
270
- 3. Watches `--watch-dir` for file changes (debounced)
271
- 4. Re-scans and shows only **new** and **resolved** vulnerabilities
140
+ It probes your server until it's ready (auto-detects Express, Next.js, Django, etc.), runs an initial scan, then watches the specified directory for changes and re-scans on each update.
272
141
 
273
- ### 🚧 CI/CD Security Gate
142
+ ### CI/CD Security Gate
274
143
 
275
- Block deployments with vulnerabilities above your threshold:
144
+ Block deployments when vulnerabilities exceed your threshold:
276
145
 
277
146
  ```bash
278
- # Fail if any high+ vulnerabilities found
279
147
  kramscan gate http://localhost:3000 --fail-on high
280
-
281
- # JSON output for pipeline processing
282
148
  kramscan gate $APP_URL --fail-on medium --json
283
-
284
- # Allow up to 3 low-severity findings
285
149
  kramscan gate http://staging.example.com --fail-on low --max-vulns 3
286
150
  ```
287
151
 
288
- **Pipeline example (GitHub Actions):**
152
+ GitHub Actions example:
153
+
289
154
  ```yaml
290
155
  - name: Security Gate
291
156
  run: npx kramscan gate http://localhost:3000 --fail-on high
292
157
  ```
293
158
 
294
- <br />
295
-
296
- ### Scan Profiles and Limits
297
- KramScan supports profiles for quick tuning:
159
+ ### Scan Profiles
298
160
 
299
161
  ```bash
300
162
  kramscan scan https://example.com --profile quick
@@ -302,7 +164,7 @@ kramscan scan https://example.com --profile balanced
302
164
  kramscan scan https://example.com --profile deep
303
165
  ```
304
166
 
305
- You can also control crawl limits and URL scope:
167
+ Control crawl limits and URL scope:
306
168
 
307
169
  ```bash
308
170
  kramscan scan https://example.com --max-pages 30 --max-links-per-page 50
@@ -310,45 +172,18 @@ kramscan scan https://example.com --exclude "logout|signout"
310
172
  kramscan scan https://example.com --include "^https://example\.com/docs"
311
173
  ```
312
174
 
313
- ### Automatic PDF Report After Scan
314
- After each scan, KramScan automatically generates a PDF report (no separate command required).
175
+ ### Automatic PDF Reports
315
176
 
316
- The file is saved to:
177
+ After each scan, a PDF report is generated automatically:
317
178
 
318
179
  - JSON: `~/.kramscan/scans/scan-<timestamp>.json`
319
180
  - PDF: `~/.kramscan/reports/scanreport_<hostname>_<timestamp>.pdf`
320
181
 
321
- You can disable it with:
322
-
323
- ```bash
324
- kramscan scan https://example.com --no-pdf
325
- ```
326
-
327
- ### Error Tracking and Recovery
328
- KramScan features comprehensive error handling:
329
-
330
- - **Continue on Failure**: Scan continues even if individual URLs fail to load
331
- - **Plugin Error Isolation**: If one vulnerability plugin fails, others continue working
332
- - **Error Reports**: PDF reports include a "⚠️ Scan Errors & Skipped Items" section
333
- - **CLI Feedback**: Real-time error messages during scanning
334
-
335
- ### Event-Driven Progress Feedback
336
- Watch your scan progress in real-time:
337
-
338
- ```
339
- πŸ” Starting Security Scan
340
- ──────────────────────────────────────────────────
341
-
342
- βœ” Initializing scanner...
343
- β ΄ Crawling: https://example.com (5/30)
344
- ⚠️ Found high vulnerability: Reflected Cross-Site Scripting (XSS)
345
- β ΄ Continuing scan (1 vulns found)...
346
- β ΄ Testing forms on https://example.com/login (3 forms)...
347
- βœ” Scan complete!
348
- ```
182
+ Disable with `--no-pdf`.
349
183
 
350
184
  ### Scan History
351
- Every scan is indexed in `~/.kramscan/scans/index.json`.
185
+
186
+ Every scan is indexed in `~/.kramscan/scans/index.json`:
352
187
 
353
188
  ```bash
354
189
  kramscan scans list -n 10
@@ -356,64 +191,151 @@ kramscan scans latest
356
191
  ```
357
192
 
358
193
  ### AI Diagnostics
359
- List models and test your configured provider/model:
360
194
 
361
195
  ```bash
362
196
  kramscan ai models -n 10
363
197
  kramscan ai test
364
198
  ```
365
199
 
366
- ### Example Agent Session
367
- ```bash
368
- $ kramscan agent
369
- > scan https://example.com
370
-
371
- Agent: I'll perform a comprehensive security scan of https://example.com.
372
- Checking for XSS, SQLi, and missing headers...
373
- [Scanning...]
374
-
375
- Agent: Scan complete! Found 2 High severity issues.
376
- Would you like me to generate a report?
200
+ ---
201
+
202
+ ## Architecture
203
+
204
+ ```mermaid
205
+ graph LR
206
+ A[User Command] --> B{CLI Controller};
207
+ B --> C[Scanner Module<br/>Puppeteer / Plugin System];
208
+ B --> D[AI Agent<br/>NLP Processing];
209
+
210
+ C --> E[Plugin Manager<br/>XSS / SQLi / Headers / CSRF];
211
+ E --> F[Vulnerability Detection];
212
+ C --> G[Event System<br/>Progress / Results];
213
+
214
+ F & G --> H[AI Analysis Engine<br/>LLM Provider];
215
+
216
+ H --> I[Risk Assessment<br/>Confidence Scoring];
217
+ I --> J[Report Generator<br/>PDF / DOCX / JSON / TXT];
218
+ J --> K((Final Output<br/>+ Error Report));
377
219
  ```
378
220
 
379
- <br />
221
+ ### Plugin System
222
+
223
+ KramScan's detection layer is built on a modular plugin architecture:
224
+
225
+ ```
226
+ src/plugins/
227
+ β”œβ”€β”€ types.ts # Base interfaces and types
228
+ β”œβ”€β”€ PluginManager.ts # Plugin orchestration
229
+ β”œβ”€β”€ index.ts # Plugin exports
230
+ └── vulnerabilities/
231
+ β”œβ”€β”€ XSSPlugin.ts
232
+ β”œβ”€β”€ SQLInjectionPlugin.ts
233
+ β”œβ”€β”€ SecurityHeadersPlugin.ts
234
+ β”œβ”€β”€ SensitiveDataPlugin.ts
235
+ β”œβ”€β”€ CSRFPlugin.ts
236
+ β”œβ”€β”€ CORSAnalyzerPlugin.ts
237
+ β”œβ”€β”€ DebugEndpointPlugin.ts
238
+ β”œβ”€β”€ DirectoryTraversalPlugin.ts
239
+ β”œβ”€β”€ CookieSecurityPlugin.ts
240
+ └── OpenRedirectPlugin.ts
241
+ ```
242
+
243
+ To add a custom plugin:
244
+
245
+ ```typescript
246
+ import { BaseVulnerabilityPlugin, PluginContext } from 'kramscan/plugins';
247
+
248
+ export class MyCustomPlugin extends BaseVulnerabilityPlugin {
249
+ readonly name = "Custom Detector";
250
+ readonly type = "custom";
251
+ readonly description = "Detects custom vulnerability";
252
+
253
+ async testParameter(context: PluginContext, param: string, value: string) {
254
+ // Your detection logic here
255
+ if (/* vulnerability found */) {
256
+ return this.success(this.createVulnerability(
257
+ "Custom Vulnerability",
258
+ "Description...",
259
+ context.url,
260
+ "high",
261
+ "Evidence...",
262
+ "Remediation..."
263
+ ));
264
+ }
265
+ return this.failure();
266
+ }
267
+ }
268
+ ```
380
269
 
381
270
  ---
382
271
 
272
+ ## Supported AI Providers
383
273
 
274
+ KramScan supports the following AI providers out of the box. Switch providers with `kramscan onboard` or by editing `~/.kramscan/config.json`.
384
275
 
385
- ## πŸ”’ Security & Privacy
386
- - **Local Execution:** All scanning logic runs locally on your machine.
387
- - **API Key Safety:** AI provider API keys are stored securely in your local home directory and are never sent to our servers.
388
- - **Data Privacy:** Scan data is sent only to your chosen AI provider for analysis and is not stored by KramScan.
389
- - **Error Tracking:** Failed scan attempts are logged locally for debugging but never transmitted.
276
+ - **OpenAI** β€” `gpt-4` (env: `OPENAI_API_KEY`)
277
+ - **Anthropic** β€” `claude-3-5-sonnet-20241022` (env: `ANTHROPIC_API_KEY`)
278
+ - **Google Gemini** β€” `gemini-2.0-flash` (env: `GEMINI_API_KEY`)
279
+ - **Mistral** β€” `mistral-large-latest` (env: `MISTRAL_API_KEY`)
280
+ - **OpenRouter** β€” `anthropic/claude-3.5-sonnet` (env: `OPENROUTER_API_KEY`)
281
+ - **Kimi** β€” `moonshot-v1-8k` (env: `KIMI_API_KEY`)
282
+ - **Groq** β€” `llama-3.1-8b-instant` (env: `GROQ_API_KEY`)
390
283
 
391
- <br />
284
+ API keys can be provided via environment variables (useful for CI/CD) or saved locally during onboarding. KramScan auto-detects keys present in your environment.
285
+
286
+ The scanning engine can also use AI to generate context-aware payloads, improving detection rates against filtered inputs and WAFs. The `kramscan agent` independently verifies reported vulnerabilities using non-destructive payloads to separate theoretical findings from exploitable risks.
392
287
 
393
288
  ---
394
289
 
395
- <br />
290
+ ## Tech Stack
396
291
 
397
- ## πŸ‘€ Author
398
- <div align="center">
292
+ - **Runtime:** Node.js >= 18
293
+ - **Language:** TypeScript 5.4
294
+ - **CLI Framework:** Commander.js, Inquirer.js
295
+ - **Browser Automation:** Puppeteer (Headless Chrome)
296
+ - **AI Integration:** OpenAI SDK, Google Generative AI, Anthropic SDK
297
+ - **Schema Validation:** Zod
298
+ - **Reporting:** Docx, Puppeteer (PDF), Chalk
299
+ - **Testing:** Jest, ts-jest
300
+ - **CI/CD:** GitHub Actions (lint, build, test on Node 18/20/22, security audit)
399
301
 
400
- **Akram Shaikh**
302
+ ---
401
303
 
402
- [![Website](https://img.shields.io/badge/Website-akramshaikh.me-blue?style=for-the-badge&logo=google-chrome&logoColor=white)](https://akramshaikh.me)
403
- [![GitHub](https://img.shields.io/badge/GitHub-shaikhakramshakil-181717?style=for-the-badge&logo=github&logoColor=white)](https://github.com/shaikhakramshakil)
404
- [![LinkedIn](https://img.shields.io/badge/LinkedIn-0077B5?style=for-the-badge&logo=linkedin&logoColor=white)](https://www.linkedin.com/in/shaikhakramshakil/)
304
+ ## Security & Privacy
405
305
 
406
- </div>
306
+ - All scanning logic runs locally on your machine.
307
+ - API keys are stored in your local home directory and are never sent to our servers.
308
+ - Scan data is sent only to your chosen AI provider for analysis and is not stored by KramScan.
309
+ - Failed scan attempts are logged locally for debugging but never transmitted.
310
+
311
+ ---
407
312
 
408
- <br />
313
+ ## Contributing
314
+
315
+ ```bash
316
+ git clone https://github.com/shaikhakramshakil/kramscan.git
317
+ cd kramscan
318
+ npm install
319
+ npm run build
320
+ npm test
321
+ ```
322
+
323
+ Before submitting a PR:
324
+
325
+ 1. Run `npm run lint` β€” ensure zero ESLint errors.
326
+ 2. Run `npm test` β€” ensure all tests pass.
327
+ 3. Add tests for new features when possible.
409
328
 
410
329
  ---
411
330
 
412
- <br />
331
+ ## Author
413
332
 
414
- ## πŸ“„ License
415
- This project is licensed under the **MIT License** β€” see the [LICENSE](LICENSE) file for details.
333
+ **Akram Shaikh**
416
334
 
417
- <div align="center">
418
- <sub>Made with ❀️ by Akram Shaikh</sub>
419
- </div>
335
+ [![Website](https://img.shields.io/badge/Website-akramshaikh.me-blue?style=for-the-badge&logo=google-chrome&logoColor=white)](https://akramshaikh.me)
336
+ [![GitHub](https://img.shields.io/badge/GitHub-shaikhakramshakil-181717?style=for-the-badge&logo=github&logoColor=white)](https://github.com/shaikhakramshakil)
337
+ [![LinkedIn](https://img.shields.io/badge/LinkedIn-0077B5?style=for-the-badge&logo=linkedin&logoColor=white)](https://www.linkedin.com/in/shaikhakramshakil/)
338
+
339
+ ## License
340
+
341
+ This project is licensed under the MIT License β€” see the [LICENSE](LICENSE) file for details.
@@ -199,7 +199,7 @@ class GenerateReportSkill {
199
199
  if (!docx) {
200
200
  throw new Error("DOCX generation requires the 'docx' package. Please install it: npm install docx");
201
201
  }
202
- const { Document, Paragraph, TextRun, HeadingLevel, Table, TableCell, TableRow, WidthType, BorderStyle, } = docx;
202
+ const { Document, Paragraph, TextRun, HeadingLevel, } = docx;
203
203
  const findings = scanData.findings || [];
204
204
  const analysisFinding = includeAnalysis
205
205
  ? findings.find((f) => f.skillId === "analyze_findings")
@@ -12,11 +12,11 @@ export declare class HealthCheckSkill implements AgentSkill {
12
12
  riskLevel: "low";
13
13
  estimatedDuration: number;
14
14
  toolDefinition: ToolDefinition;
15
- validateParameters(params: Record<string, unknown>): {
15
+ validateParameters(_params: Record<string, unknown>): {
16
16
  valid: boolean;
17
17
  errors: string[];
18
18
  };
19
- execute(params: Record<string, unknown>, context: AgentContext): Promise<SkillResult>;
19
+ execute(params: Record<string, unknown>, _context: AgentContext): Promise<SkillResult>;
20
20
  private checkNodeVersion;
21
21
  private checkConfiguration;
22
22
  private checkAIProvider;
@@ -64,11 +64,11 @@ class HealthCheckSkill {
64
64
  },
65
65
  ],
66
66
  };
67
- validateParameters(params) {
67
+ validateParameters(_params) {
68
68
  // No required parameters
69
69
  return { valid: true, errors: [] };
70
70
  }
71
- async execute(params, context) {
71
+ async execute(params, _context) {
72
72
  const verbose = params.verbose ?? false;
73
73
  logger_1.logger.info("Running health check...");
74
74
  const checks = [];
@@ -48,7 +48,7 @@ class VerifyFindingSkill {
48
48
  // Get type from finding metadata or title
49
49
  const vulnType = finding.metadata?.type || (finding.title.toLowerCase().includes("sql") ? "sqli" : "xss");
50
50
  // Generate non-destructive verification payloads
51
- const payloads = await payloadGenerator.generatePayloads(vulnType, {
51
+ await payloadGenerator.generatePayloads(vulnType, {
52
52
  parameterName: finding.metadata?.parameter || "verify",
53
53
  url: finding.metadata?.url || context.currentTarget || "",
54
54
  });
@@ -17,6 +17,6 @@ export declare class WebScanSkill implements AgentSkill {
17
17
  valid: boolean;
18
18
  errors: string[];
19
19
  };
20
- execute(params: Record<string, unknown>, context: AgentContext): Promise<SkillResult>;
20
+ execute(params: Record<string, unknown>, _context: AgentContext): Promise<SkillResult>;
21
21
  run(): Promise<SkillResult>;
22
22
  }
@@ -126,7 +126,7 @@ class WebScanSkill {
126
126
  errors,
127
127
  };
128
128
  }
129
- async execute(params, context) {
129
+ async execute(params, _context) {
130
130
  const targetUrl = params.targetUrl;
131
131
  const depth = params.depth ?? 2;
132
132
  const timeout = params.timeout ?? 30000;
package/dist/cli.js CHANGED
@@ -53,6 +53,7 @@ const scans_1 = require("./commands/scans");
53
53
  const ai_1 = require("./commands/ai");
54
54
  const dev_1 = require("./commands/dev");
55
55
  const gate_1 = require("./commands/gate");
56
+ const init_1 = require("./commands/init");
56
57
  const config_2 = require("./core/config");
57
58
  const theme_1 = require("./utils/theme");
58
59
  let verboseMode = false;
@@ -71,6 +72,7 @@ function debugLog(...args) {
71
72
  const menuChoices = [
72
73
  { label: "Agent", value: "agent", description: "AI-powered interactive security assistant", icon: "πŸ€–", status: "active" },
73
74
  { label: "Onboard", value: "onboard", description: "First-time setup wizard", icon: "⚑", status: "active" },
75
+ { label: "Init", value: "init", description: "Generate .kramscanrc for this project", icon: "πŸ“", status: "active" },
74
76
  { label: "Scan", value: "scan", description: "Scan a target URL for vulnerabilities", icon: "πŸ”", status: "active" },
75
77
  { label: "Dev", value: "dev", description: "Watch-mode scanning for localhost dev servers", icon: "πŸ› οΈ", status: "active" },
76
78
  { label: "Gate", value: "gate", description: "CI/CD security quality gate", icon: "🚧", status: "active" },
@@ -107,7 +109,7 @@ async function showInteractiveMenu() {
107
109
  console.log(theme_1.theme.gray(` Run ${theme_1.theme.cyan("kramscan --help")} for available commands.\n`));
108
110
  return;
109
111
  }
110
- let args = [action];
112
+ const args = [action];
111
113
  // Specific handling for commands that need input
112
114
  if (action === "scan") {
113
115
  const { url } = await inquirer_1.default.prompt([
@@ -179,6 +181,7 @@ async function showInteractiveMenu() {
179
181
  // Error handling is managed by the commands themselves or global handlers
180
182
  }
181
183
  }
184
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
182
185
  async function showDirectCommandInput() {
183
186
  (0, theme_1.printBanner)();
184
187
  (0, theme_1.printInfo)();
@@ -227,6 +230,7 @@ function createProgram() {
227
230
  (0, ai_1.registerAiCommand)(program);
228
231
  (0, dev_1.registerDevCommand)(program);
229
232
  (0, gate_1.registerGateCommand)(program);
233
+ (0, init_1.registerInitCommand)(program);
230
234
  // Version subcommand with detailed environment info
231
235
  program
232
236
  .command("version")
@@ -138,7 +138,7 @@ async function checkPuppeteer() {
138
138
  }
139
139
  async function checkConfig() {
140
140
  try {
141
- const config = await (0, config_1.getConfig)();
141
+ await (0, config_1.getConfig)();
142
142
  return {
143
143
  name: "Configuration",
144
144
  status: "pass",
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Init Command
3
+ * Generates a .kramscanrc project configuration file in the current directory.
4
+ */
5
+ import { Command } from "commander";
6
+ export declare function registerInitCommand(program: Command): void;
@@ -0,0 +1,191 @@
1
+ "use strict";
2
+ /**
3
+ * Init Command
4
+ * Generates a .kramscanrc project configuration file in the current directory.
5
+ */
6
+ var __importDefault = (this && this.__importDefault) || function (mod) {
7
+ return (mod && mod.__esModule) ? mod : { "default": mod };
8
+ };
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.registerInitCommand = registerInitCommand;
11
+ const chalk_1 = __importDefault(require("chalk"));
12
+ const inquirer_1 = __importDefault(require("inquirer"));
13
+ const promises_1 = __importDefault(require("fs/promises"));
14
+ const path_1 = __importDefault(require("path"));
15
+ const project_config_1 = require("../core/project-config");
16
+ const logger_1 = require("../utils/logger");
17
+ function registerInitCommand(program) {
18
+ program
19
+ .command("init")
20
+ .description("Generate a .kramscanrc project configuration file")
21
+ .option("-y, --yes", "Skip prompts and generate with defaults")
22
+ .option("--force", "Overwrite existing .kramscanrc file")
23
+ .action(async (options) => {
24
+ const targetPath = path_1.default.join(process.cwd(), project_config_1.PROJECT_CONFIG_FILENAME);
25
+ console.log("");
26
+ console.log(chalk_1.default.bold.cyan("KramScan Project Setup"));
27
+ console.log(chalk_1.default.gray("─".repeat(50)));
28
+ console.log("");
29
+ // Check if file already exists
30
+ try {
31
+ await promises_1.default.access(targetPath);
32
+ if (!options.force) {
33
+ console.log(chalk_1.default.yellow(` A ${project_config_1.PROJECT_CONFIG_FILENAME} file already exists in this directory.`));
34
+ console.log(chalk_1.default.gray(` Use ${chalk_1.default.white("--force")} to overwrite it.`));
35
+ console.log("");
36
+ return;
37
+ }
38
+ }
39
+ catch {
40
+ // File doesn't exist β€” good
41
+ }
42
+ let config;
43
+ if (options.yes) {
44
+ config = getDefaultProjectConfig();
45
+ }
46
+ else {
47
+ config = await runInteractiveSetup();
48
+ }
49
+ // Write the file
50
+ const content = JSON.stringify(config, null, 2) + "\n";
51
+ await promises_1.default.writeFile(targetPath, content, "utf-8");
52
+ console.log("");
53
+ logger_1.logger.success(`Created ${project_config_1.PROJECT_CONFIG_FILENAME} in ${process.cwd()}`);
54
+ console.log("");
55
+ console.log(chalk_1.default.gray(" This file configures KramScan for this project."));
56
+ console.log(chalk_1.default.gray(" Commit it to version control so your team shares the same settings."));
57
+ console.log(chalk_1.default.gray(` API keys are never stored here β€” use ${chalk_1.default.white("kramscan onboard")} or env vars.`));
58
+ console.log("");
59
+ });
60
+ }
61
+ function getDefaultProjectConfig() {
62
+ return {
63
+ scan: {
64
+ defaultProfile: "balanced",
65
+ defaultTimeout: 30000,
66
+ strictScope: true,
67
+ exclude: [
68
+ "logout",
69
+ "signout",
70
+ "delete",
71
+ ],
72
+ },
73
+ report: {
74
+ defaultFormat: "markdown",
75
+ companyName: "Your Company",
76
+ },
77
+ gate: {
78
+ failOn: "high",
79
+ maxVulns: 0,
80
+ },
81
+ plugins: {
82
+ disabled: [],
83
+ },
84
+ };
85
+ }
86
+ async function runInteractiveSetup() {
87
+ const answers = await inquirer_1.default.prompt([
88
+ {
89
+ type: "list",
90
+ name: "profile",
91
+ message: "Default scan profile:",
92
+ choices: [
93
+ { name: "quick β€” fast surface-level scan", value: "quick" },
94
+ { name: "balanced β€” good coverage, moderate speed", value: "balanced" },
95
+ { name: "deep β€” thorough crawl, slower", value: "deep" },
96
+ ],
97
+ default: "balanced",
98
+ },
99
+ {
100
+ type: "input",
101
+ name: "timeout",
102
+ message: "Default request timeout (ms):",
103
+ default: "30000",
104
+ validate: (input) => {
105
+ const n = parseInt(input, 10);
106
+ if (isNaN(n) || n < 1000)
107
+ return "Must be a number >= 1000";
108
+ return true;
109
+ },
110
+ filter: (input) => parseInt(input, 10),
111
+ },
112
+ {
113
+ type: "confirm",
114
+ name: "strictScope",
115
+ message: "Stay within the target domain? (strict scope)",
116
+ default: true,
117
+ },
118
+ {
119
+ type: "input",
120
+ name: "exclude",
121
+ message: "URL patterns to exclude (comma-separated, e.g. logout,signout):",
122
+ default: "logout,signout,delete",
123
+ filter: (input) => input
124
+ .split(",")
125
+ .map((s) => s.trim())
126
+ .filter(Boolean),
127
+ },
128
+ {
129
+ type: "list",
130
+ name: "reportFormat",
131
+ message: "Default report format:",
132
+ choices: [
133
+ { name: "markdown", value: "markdown" },
134
+ { name: "word (.docx)", value: "word" },
135
+ { name: "json", value: "json" },
136
+ { name: "txt", value: "txt" },
137
+ ],
138
+ default: "markdown",
139
+ },
140
+ {
141
+ type: "input",
142
+ name: "companyName",
143
+ message: "Company or project name (for report headers):",
144
+ default: path_1.default.basename(process.cwd()),
145
+ },
146
+ {
147
+ type: "list",
148
+ name: "gateFailOn",
149
+ message: "CI/CD gate β€” fail on severity at or above:",
150
+ choices: [
151
+ { name: "critical β€” only block on critical issues", value: "critical" },
152
+ { name: "high β€” block on high and critical", value: "high" },
153
+ { name: "medium β€” block on medium and above", value: "medium" },
154
+ { name: "low β€” block on everything except info", value: "low" },
155
+ ],
156
+ default: "high",
157
+ },
158
+ {
159
+ type: "input",
160
+ name: "gateMaxVulns",
161
+ message: "CI/CD gate β€” max allowed vulnerabilities before failing:",
162
+ default: "0",
163
+ validate: (input) => {
164
+ const n = parseInt(input, 10);
165
+ if (isNaN(n) || n < 0)
166
+ return "Must be a non-negative number";
167
+ return true;
168
+ },
169
+ filter: (input) => parseInt(input, 10),
170
+ },
171
+ ]);
172
+ return {
173
+ scan: {
174
+ defaultProfile: answers.profile,
175
+ defaultTimeout: answers.timeout,
176
+ strictScope: answers.strictScope,
177
+ exclude: answers.exclude,
178
+ },
179
+ report: {
180
+ defaultFormat: answers.reportFormat,
181
+ companyName: answers.companyName,
182
+ },
183
+ gate: {
184
+ failOn: answers.gateFailOn,
185
+ maxVulns: answers.gateMaxVulns,
186
+ },
187
+ plugins: {
188
+ disabled: [],
189
+ },
190
+ };
191
+ }
@@ -119,23 +119,18 @@ function registerScanCommand(program) {
119
119
  console.log("");
120
120
  }
121
121
  const scanner = new scanner_1.Scanner(options.plugins !== false);
122
- // Set up event listeners for progress feedback
123
- let currentStage = "initializing";
124
122
  let vulnerabilitiesFound = 0;
125
123
  scanner.on("scan:start", () => {
126
124
  if (spinner)
127
125
  spinner.text = `Starting scan of ${url}...`;
128
- currentStage = "scanning";
129
126
  });
130
127
  scanner.on("crawl:page", (data) => {
131
128
  if (spinner)
132
129
  spinner.text = `Crawling: ${data.url} (${data.crawledCount}/${data.maxPages})`;
133
- currentStage = "crawling";
134
130
  });
135
131
  scanner.on("form:test", (data) => {
136
132
  if (spinner)
137
133
  spinner.text = `Testing forms on ${data.url} (${data.formCount} forms)...`;
138
- currentStage = "testing forms";
139
134
  });
140
135
  scanner.on("vuln:found", (data) => {
141
136
  vulnerabilitiesFound++;
@@ -49,6 +49,7 @@ const fs = __importStar(require("fs"));
49
49
  const path = __importStar(require("path"));
50
50
  const os = __importStar(require("os"));
51
51
  const config_schema_1 = require("./config-schema");
52
+ const project_config_1 = require("./project-config");
52
53
  const theme_1 = require("../utils/theme");
53
54
  var config_schema_2 = require("./config-schema");
54
55
  Object.defineProperty(exports, "scanProfiles", { enumerable: true, get: function () { return config_schema_2.defaultScanProfiles; } });
@@ -363,7 +364,13 @@ function getConfigStore() {
363
364
  }
364
365
  async function getConfig() {
365
366
  await ensureInitialized();
366
- return store.store;
367
+ const globalConfig = store.store;
368
+ // Merge project-level .kramscanrc if present
369
+ const project = (0, project_config_1.findProjectConfig)();
370
+ if (project) {
371
+ return (0, project_config_1.deepMerge)(globalConfig, project.config);
372
+ }
373
+ return globalConfig;
367
374
  }
368
375
  async function getConfigValue(key) {
369
376
  await ensureInitialized();
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Project-level configuration (.kramscanrc)
3
+ *
4
+ * Discovers and loads a .kramscanrc file from the current working directory
5
+ * (or any parent directory) and merges it with the global config. Project
6
+ * settings override global settings, but sensitive fields (ai.apiKey) are
7
+ * never read from the project file.
8
+ */
9
+ export declare const PROJECT_CONFIG_FILENAME = ".kramscanrc";
10
+ /**
11
+ * Represents the subset of config fields that can be set at the project level.
12
+ * Intentionally excludes ai.apiKey for security.
13
+ */
14
+ export interface ProjectConfig {
15
+ scan?: {
16
+ defaultProfile?: string;
17
+ defaultTimeout?: number;
18
+ maxThreads?: number;
19
+ followRedirects?: boolean;
20
+ verifySSL?: boolean;
21
+ rateLimitPerSecond?: number;
22
+ strictScope?: boolean;
23
+ include?: string[];
24
+ exclude?: string[];
25
+ profiles?: Record<string, {
26
+ depth?: number;
27
+ timeout?: number;
28
+ maxPages?: number;
29
+ maxLinksPerPage?: number;
30
+ }>;
31
+ };
32
+ report?: {
33
+ defaultFormat?: string;
34
+ companyName?: string;
35
+ includeScreenshots?: boolean;
36
+ severityThreshold?: string;
37
+ };
38
+ gate?: {
39
+ failOn?: string;
40
+ maxVulns?: number;
41
+ };
42
+ plugins?: {
43
+ disabled?: string[];
44
+ };
45
+ }
46
+ /**
47
+ * Search for a .kramscanrc file starting from `startDir` and walking up
48
+ * to the filesystem root. Returns the parsed contents and file path,
49
+ * or null if no file is found.
50
+ */
51
+ export declare function findProjectConfig(startDir?: string): {
52
+ config: ProjectConfig;
53
+ filepath: string;
54
+ } | null;
55
+ /**
56
+ * Deep merge `source` into `target`, returning a new object. Arrays are
57
+ * replaced, not concatenated. Undefined values in source are skipped.
58
+ */
59
+ export declare function deepMerge<T extends Record<string, unknown>>(target: T, source: Record<string, unknown>): T;
@@ -0,0 +1,104 @@
1
+ "use strict";
2
+ /**
3
+ * Project-level configuration (.kramscanrc)
4
+ *
5
+ * Discovers and loads a .kramscanrc file from the current working directory
6
+ * (or any parent directory) and merges it with the global config. Project
7
+ * settings override global settings, but sensitive fields (ai.apiKey) are
8
+ * never read from the project file.
9
+ */
10
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
11
+ if (k2 === undefined) k2 = k;
12
+ var desc = Object.getOwnPropertyDescriptor(m, k);
13
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
14
+ desc = { enumerable: true, get: function() { return m[k]; } };
15
+ }
16
+ Object.defineProperty(o, k2, desc);
17
+ }) : (function(o, m, k, k2) {
18
+ if (k2 === undefined) k2 = k;
19
+ o[k2] = m[k];
20
+ }));
21
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
22
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
23
+ }) : function(o, v) {
24
+ o["default"] = v;
25
+ });
26
+ var __importStar = (this && this.__importStar) || (function () {
27
+ var ownKeys = function(o) {
28
+ ownKeys = Object.getOwnPropertyNames || function (o) {
29
+ var ar = [];
30
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
31
+ return ar;
32
+ };
33
+ return ownKeys(o);
34
+ };
35
+ return function (mod) {
36
+ if (mod && mod.__esModule) return mod;
37
+ var result = {};
38
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
39
+ __setModuleDefault(result, mod);
40
+ return result;
41
+ };
42
+ })();
43
+ Object.defineProperty(exports, "__esModule", { value: true });
44
+ exports.PROJECT_CONFIG_FILENAME = void 0;
45
+ exports.findProjectConfig = findProjectConfig;
46
+ exports.deepMerge = deepMerge;
47
+ const fs = __importStar(require("fs"));
48
+ const path = __importStar(require("path"));
49
+ exports.PROJECT_CONFIG_FILENAME = ".kramscanrc";
50
+ /**
51
+ * Search for a .kramscanrc file starting from `startDir` and walking up
52
+ * to the filesystem root. Returns the parsed contents and file path,
53
+ * or null if no file is found.
54
+ */
55
+ function findProjectConfig(startDir = process.cwd()) {
56
+ for (let dir = path.resolve(startDir);; dir = path.dirname(dir)) {
57
+ const candidate = path.join(dir, exports.PROJECT_CONFIG_FILENAME);
58
+ if (fs.existsSync(candidate)) {
59
+ try {
60
+ const raw = fs.readFileSync(candidate, "utf-8");
61
+ const parsed = JSON.parse(raw);
62
+ // Strip any ai.apiKey that might have been added by mistake
63
+ const sanitized = parsed;
64
+ if (sanitized.ai && typeof sanitized.ai === "object") {
65
+ delete sanitized.ai.apiKey;
66
+ }
67
+ return { config: parsed, filepath: candidate };
68
+ }
69
+ catch {
70
+ // Malformed file β€” skip it silently
71
+ return null;
72
+ }
73
+ }
74
+ const parent = path.dirname(dir);
75
+ if (parent === dir)
76
+ break; // reached filesystem root
77
+ }
78
+ return null;
79
+ }
80
+ /**
81
+ * Deep merge `source` into `target`, returning a new object. Arrays are
82
+ * replaced, not concatenated. Undefined values in source are skipped.
83
+ */
84
+ function deepMerge(target, source) {
85
+ const result = { ...target };
86
+ for (const key of Object.keys(source)) {
87
+ const srcVal = source[key];
88
+ const tgtVal = result[key];
89
+ if (srcVal === undefined)
90
+ continue;
91
+ if (srcVal !== null &&
92
+ typeof srcVal === "object" &&
93
+ !Array.isArray(srcVal) &&
94
+ tgtVal !== null &&
95
+ typeof tgtVal === "object" &&
96
+ !Array.isArray(tgtVal)) {
97
+ result[key] = deepMerge(tgtVal, srcVal);
98
+ }
99
+ else {
100
+ result[key] = srcVal;
101
+ }
102
+ }
103
+ return result;
104
+ }
@@ -15,7 +15,6 @@ async function probeServer(url, options = {}) {
15
15
  const { timeout = 30000, interval = 1000, maxAttempts = 20 } = options;
16
16
  const startTime = Date.now();
17
17
  let attempts = 0;
18
- let lastError = null;
19
18
  while (attempts < maxAttempts && Date.now() - startTime < timeout) {
20
19
  attempts++;
21
20
  try {
@@ -23,10 +22,9 @@ async function probeServer(url, options = {}) {
23
22
  if (result.reachable) {
24
23
  return result;
25
24
  }
26
- lastError = `HTTP ${result.statusCode}`;
27
25
  }
28
26
  catch (err) {
29
- lastError = err.message;
27
+ // error logged silently
30
28
  }
31
29
  // Exponential backoff with cap
32
30
  const delay = Math.min(interval * Math.pow(1.5, attempts - 1), 5000);
@@ -17,7 +17,6 @@ class CORSAnalyzerPlugin extends types_1.BaseVulnerabilityPlugin {
17
17
  const acao = headers["access-control-allow-origin"];
18
18
  const acac = headers["access-control-allow-credentials"];
19
19
  const acam = headers["access-control-allow-methods"];
20
- const acah = headers["access-control-allow-headers"];
21
20
  // Check for wildcard origin
22
21
  if (acao === "*") {
23
22
  vulnerabilities.push(this.createVulnerability("CORS: Wildcard Origin Allowed", `The server at ${host} allows requests from any origin (Access-Control-Allow-Origin: *). ` +
@@ -10,6 +10,6 @@ export declare class DebugEndpointPlugin extends BaseVulnerabilityPlugin {
10
10
  * Each entry has a path, a human-readable name, and expected severity.
11
11
  */
12
12
  private readonly debugEndpoints;
13
- analyzeContent(context: PluginContext, content: string): Promise<Vulnerability[]>;
13
+ analyzeContent(context: PluginContext, _content: string): Promise<Vulnerability[]>;
14
14
  reset(): void;
15
15
  }
@@ -174,7 +174,7 @@ class DebugEndpointPlugin extends types_1.BaseVulnerabilityPlugin {
174
174
  remediation: "Protect /metrics endpoint behind authentication or restrict to internal networks.",
175
175
  },
176
176
  ];
177
- async analyzeContent(context, content) {
177
+ async analyzeContent(context, _content) {
178
178
  const vulnerabilities = [];
179
179
  const baseUrl = new URL(context.url).origin;
180
180
  for (const endpoint of this.debugEndpoints) {
@@ -8,6 +8,6 @@ export declare class DirectoryTraversalPlugin extends BaseVulnerabilityPlugin {
8
8
  * Each payload targets a well-known file with distinctive content markers.
9
9
  */
10
10
  private readonly payloads;
11
- testParameter(context: PluginContext, param: string, value: string): Promise<VulnerabilityTestResult>;
11
+ testParameter(context: PluginContext, param: string, _value: string): Promise<VulnerabilityTestResult>;
12
12
  private buildTestUrl;
13
13
  }
@@ -66,7 +66,7 @@ class DirectoryTraversalPlugin extends types_1.BaseVulnerabilityPlugin {
66
66
  os: "linux",
67
67
  },
68
68
  ];
69
- async testParameter(context, param, value) {
69
+ async testParameter(context, param, _value) {
70
70
  for (const { payload, markers, os } of this.payloads) {
71
71
  try {
72
72
  const testUrl = this.buildTestUrl(context.url, param, payload);
@@ -6,5 +6,5 @@ export declare class OpenRedirectPlugin extends BaseVulnerabilityPlugin {
6
6
  readonly description = "Detects open redirect vulnerabilities in URL parameters";
7
7
  private readonly redirectParams;
8
8
  private readonly testDomains;
9
- analyzeContent(context: PluginContext, content: string): Promise<Vulnerability[]>;
9
+ analyzeContent(context: PluginContext, _content: string): Promise<Vulnerability[]>;
10
10
  }
@@ -19,7 +19,7 @@ class OpenRedirectPlugin extends types_1.BaseVulnerabilityPlugin {
19
19
  "/\\evil.com",
20
20
  "https:evil.com",
21
21
  ];
22
- async analyzeContent(context, content) {
22
+ async analyzeContent(context, _content) {
23
23
  const vulnerabilities = [];
24
24
  const url = new URL(context.url);
25
25
  // Check if the current URL uses any redirect-like parameters
@@ -53,6 +53,7 @@ function escapeHtml(text) {
53
53
  }
54
54
  function sanitizeFilenamePart(value) {
55
55
  return value
56
+ // eslint-disable-next-line no-control-regex, no-useless-escape
56
57
  .replace(/[<>:"\/\\|?*\x00-\x1F]/g, "_")
57
58
  .replace(/\s+/g, "_")
58
59
  .replace(/_+/g, "_")
@@ -55,10 +55,10 @@ function outputLog(entry) {
55
55
  console.log(JSON.stringify(entry));
56
56
  }
57
57
  else {
58
- const { timestamp, level, message, ...rest } = entry;
58
+ const { message, context: ctx } = entry;
59
59
  let output = message;
60
- if (process.env.LOG_INCLUDE_CONTEXT === "true" && Object.keys(rest).length > 0) {
61
- output += ` ${JSON.stringify(rest)}`;
60
+ if (process.env.LOG_INCLUDE_CONTEXT === "true" && ctx && Object.keys(ctx).length > 0) {
61
+ output += ` ${JSON.stringify(ctx)}`;
62
62
  }
63
63
  console.log(output);
64
64
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kramscan",
3
- "version": "0.3.1",
3
+ "version": "0.4.0",
4
4
  "description": "KramScan CLI β€” AI-powered web app security testing",
5
5
  "author": "Akram Shaikh (https://akramshaikh.me)",
6
6
  "license": "MIT",