kramscan 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +81 -54
  3. package/dist/cli.js +6 -0
  4. package/dist/commands/dev.d.ts +2 -0
  5. package/dist/commands/dev.js +236 -0
  6. package/dist/commands/gate.d.ts +2 -0
  7. package/dist/commands/gate.js +109 -0
  8. package/dist/commands/scan.js +1 -0
  9. package/dist/commands/scans.js +4 -0
  10. package/dist/core/config-schema.js +1 -1
  11. package/dist/core/config.js +3 -3
  12. package/dist/core/diff-engine.d.ts +12 -0
  13. package/dist/core/diff-engine.js +47 -0
  14. package/dist/core/scan-index.d.ts +1 -0
  15. package/dist/core/scanner.js +7 -1
  16. package/dist/core/server-probe.d.ts +20 -0
  17. package/dist/core/server-probe.js +109 -0
  18. package/dist/core/vulnerability-detector.d.ts +6 -0
  19. package/dist/core/vulnerability-detector.js +21 -0
  20. package/dist/plugins/index.d.ts +5 -0
  21. package/dist/plugins/index.js +11 -1
  22. package/dist/plugins/vulnerabilities/CORSAnalyzerPlugin.d.ts +10 -0
  23. package/dist/plugins/vulnerabilities/CORSAnalyzerPlugin.js +67 -0
  24. package/dist/plugins/vulnerabilities/CookieSecurityPlugin.d.ts +10 -0
  25. package/dist/plugins/vulnerabilities/CookieSecurityPlugin.js +91 -0
  26. package/dist/plugins/vulnerabilities/DebugEndpointPlugin.d.ts +15 -0
  27. package/dist/plugins/vulnerabilities/DebugEndpointPlugin.js +222 -0
  28. package/dist/plugins/vulnerabilities/DirectoryTraversalPlugin.d.ts +13 -0
  29. package/dist/plugins/vulnerabilities/DirectoryTraversalPlugin.js +110 -0
  30. package/dist/plugins/vulnerabilities/OpenRedirectPlugin.d.ts +10 -0
  31. package/dist/plugins/vulnerabilities/OpenRedirectPlugin.js +69 -0
  32. package/dist/reports/PdfGenerator.js +26 -1
  33. package/dist/utils/theme.d.ts +1 -0
  34. package/dist/utils/theme.js +7 -1
  35. package/package.json +6 -3
@@ -0,0 +1,222 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DebugEndpointPlugin = void 0;
4
+ const types_1 = require("../types");
5
+ class DebugEndpointPlugin extends types_1.BaseVulnerabilityPlugin {
6
+ name = "Debug Endpoint Detector";
7
+ type = "info";
8
+ description = "Probes for common debug, admin, and development endpoints left exposed";
9
+ reportedPaths = new Set();
10
+ /**
11
+ * Common debug/dev endpoints that developers forget to disable before deployment.
12
+ * Each entry has a path, a human-readable name, and expected severity.
13
+ */
14
+ debugEndpoints = [
15
+ {
16
+ path: "/debug",
17
+ name: "Debug Panel",
18
+ severity: "high",
19
+ indicators: ["debug", "stack", "trace", "error"],
20
+ remediation: "Remove or protect the /debug endpoint behind authentication.",
21
+ },
22
+ {
23
+ path: "/__debug__",
24
+ name: "Django Debug Panel",
25
+ severity: "high",
26
+ indicators: ["django", "debug", "toolbar"],
27
+ remediation: "Set DEBUG=False in Django settings for production.",
28
+ },
29
+ {
30
+ path: "/phpinfo.php",
31
+ name: "PHP Info Page",
32
+ severity: "high",
33
+ indicators: ["phpinfo", "PHP Version", "Configuration"],
34
+ remediation: "Remove phpinfo.php from production servers.",
35
+ },
36
+ {
37
+ path: "/server-status",
38
+ name: "Apache Server Status",
39
+ severity: "medium",
40
+ indicators: ["Apache", "Server Version", "Current Time"],
41
+ remediation: "Restrict /server-status to internal IPs only.",
42
+ },
43
+ {
44
+ path: "/server-info",
45
+ name: "Apache Server Info",
46
+ severity: "medium",
47
+ indicators: ["Apache", "Server Information"],
48
+ remediation: "Disable mod_info or restrict access to internal IPs.",
49
+ },
50
+ {
51
+ path: "/graphql",
52
+ name: "GraphQL Endpoint (Introspection)",
53
+ severity: "medium",
54
+ indicators: ["graphql", "__schema", "query"],
55
+ remediation: "Disable GraphQL introspection in production.",
56
+ },
57
+ {
58
+ path: "/graphiql",
59
+ name: "GraphiQL IDE",
60
+ severity: "high",
61
+ indicators: ["graphiql", "GraphiQL"],
62
+ remediation: "Remove GraphiQL from production deployments.",
63
+ },
64
+ {
65
+ path: "/swagger",
66
+ name: "Swagger UI",
67
+ severity: "medium",
68
+ indicators: ["swagger", "api-docs", "openapi"],
69
+ remediation: "Protect Swagger UI behind authentication or remove from production.",
70
+ },
71
+ {
72
+ path: "/swagger-ui.html",
73
+ name: "Swagger UI HTML",
74
+ severity: "medium",
75
+ indicators: ["swagger", "api-docs"],
76
+ remediation: "Protect Swagger UI behind authentication or remove from production.",
77
+ },
78
+ {
79
+ path: "/api-docs",
80
+ name: "API Documentation",
81
+ severity: "low",
82
+ indicators: ["api", "docs", "openapi", "swagger"],
83
+ remediation: "Restrict API docs to authenticated users in production.",
84
+ },
85
+ {
86
+ path: "/actuator",
87
+ name: "Spring Boot Actuator",
88
+ severity: "high",
89
+ indicators: ["actuator", "beans", "health", "info"],
90
+ remediation: "Secure Spring Boot Actuator endpoints with authentication.",
91
+ },
92
+ {
93
+ path: "/actuator/env",
94
+ name: "Spring Boot Environment",
95
+ severity: "critical",
96
+ indicators: ["property", "source", "value"],
97
+ remediation: "Never expose /actuator/env publicly. It reveals environment variables and secrets.",
98
+ },
99
+ {
100
+ path: "/actuator/heapdump",
101
+ name: "Spring Boot Heap Dump",
102
+ severity: "critical",
103
+ indicators: [],
104
+ remediation: "Disable heap dump endpoint. It can expose sensitive memory contents.",
105
+ },
106
+ {
107
+ path: "/elmah.axd",
108
+ name: "ELMAH Error Log (.NET)",
109
+ severity: "high",
110
+ indicators: ["elmah", "error", "exception"],
111
+ remediation: "Restrict ELMAH access to authenticated administrators.",
112
+ },
113
+ {
114
+ path: "/trace.axd",
115
+ name: ".NET Trace Page",
116
+ severity: "high",
117
+ indicators: ["trace", "request", "response"],
118
+ remediation: "Disable tracing in production web.config.",
119
+ },
120
+ {
121
+ path: "/.env",
122
+ name: "Environment File",
123
+ severity: "critical",
124
+ indicators: ["=", "KEY", "SECRET", "PASSWORD", "DATABASE"],
125
+ remediation: "Never serve .env files. Add them to .gitignore and configure your server to deny access.",
126
+ },
127
+ {
128
+ path: "/wp-config.php",
129
+ name: "WordPress Config",
130
+ severity: "critical",
131
+ indicators: ["DB_NAME", "DB_PASSWORD", "AUTH_KEY"],
132
+ remediation: "Ensure wp-config.php is not accessible via HTTP.",
133
+ },
134
+ {
135
+ path: "/.git/config",
136
+ name: "Git Repository Config",
137
+ severity: "critical",
138
+ indicators: ["[core]", "[remote", "repositoryformatversion"],
139
+ remediation: "Block access to .git directory. Your source code is exposed.",
140
+ },
141
+ {
142
+ path: "/.git/HEAD",
143
+ name: "Git HEAD Reference",
144
+ severity: "high",
145
+ indicators: ["ref:", "refs/heads/"],
146
+ remediation: "Block access to the entire .git directory in your web server configuration.",
147
+ },
148
+ {
149
+ path: "/config.json",
150
+ name: "JSON Config File",
151
+ severity: "medium",
152
+ indicators: ["apiKey", "secret", "password", "database", "connection"],
153
+ remediation: "Do not serve config files via HTTP. Move them outside the web root.",
154
+ },
155
+ {
156
+ path: "/test",
157
+ name: "Test Endpoint",
158
+ severity: "low",
159
+ indicators: ["test", "debug", "hello"],
160
+ remediation: "Remove test endpoints before deploying to production.",
161
+ },
162
+ {
163
+ path: "/health",
164
+ name: "Health Check (Verbose)",
165
+ severity: "low",
166
+ indicators: ["database", "connection", "redis", "memory", "uptime"],
167
+ remediation: "Limit health check responses to simple status. Do not expose internal service details.",
168
+ },
169
+ {
170
+ path: "/metrics",
171
+ name: "Prometheus Metrics",
172
+ severity: "medium",
173
+ indicators: ["process_", "http_", "nodejs_", "go_"],
174
+ remediation: "Protect /metrics endpoint behind authentication or restrict to internal networks.",
175
+ },
176
+ ];
177
+ async analyzeContent(context, content) {
178
+ const vulnerabilities = [];
179
+ const baseUrl = new URL(context.url).origin;
180
+ for (const endpoint of this.debugEndpoints) {
181
+ const fullPath = `${baseUrl}${endpoint.path}`;
182
+ if (this.reportedPaths.has(fullPath))
183
+ continue;
184
+ try {
185
+ const response = await context.page.evaluate(async (url) => {
186
+ try {
187
+ const res = await fetch(url, {
188
+ method: "GET",
189
+ redirect: "follow",
190
+ credentials: "omit",
191
+ });
192
+ const text = await res.text();
193
+ return { status: res.status, body: text.substring(0, 2000) };
194
+ }
195
+ catch {
196
+ return { status: 0, body: "" };
197
+ }
198
+ }, fullPath);
199
+ // Check if the endpoint exists and contains debug indicators
200
+ if (response.status >= 200 && response.status < 400) {
201
+ const bodyLower = response.body.toLowerCase();
202
+ // For endpoints with no indicators (like heap dumps), 200 status is enough
203
+ const hasIndicators = endpoint.indicators.length === 0 ||
204
+ endpoint.indicators.some(ind => bodyLower.includes(ind.toLowerCase()));
205
+ if (hasIndicators) {
206
+ this.reportedPaths.add(fullPath);
207
+ vulnerabilities.push(this.createVulnerability(`Exposed ${endpoint.name}`, `The ${endpoint.name} endpoint is accessible at ${endpoint.path}. ` +
208
+ `This can expose sensitive internal information, configuration details, or debug data.`, fullPath, endpoint.severity, `HTTP ${response.status} at ${endpoint.path} (${response.body.substring(0, 100)}...)`, endpoint.remediation, "CWE-215"));
209
+ }
210
+ }
211
+ }
212
+ catch {
213
+ // Endpoint not reachable, skip silently
214
+ }
215
+ }
216
+ return vulnerabilities;
217
+ }
218
+ reset() {
219
+ this.reportedPaths.clear();
220
+ }
221
+ }
222
+ exports.DebugEndpointPlugin = DebugEndpointPlugin;
@@ -0,0 +1,13 @@
1
+ import { BaseVulnerabilityPlugin, PluginContext, VulnerabilityTestResult } from "../types";
2
+ export declare class DirectoryTraversalPlugin extends BaseVulnerabilityPlugin {
3
+ readonly name = "Directory Traversal / LFI Detector";
4
+ readonly type: "lfi";
5
+ readonly description = "Tests for path traversal and local file inclusion vulnerabilities";
6
+ /**
7
+ * Payloads designed to detect directory traversal / LFI.
8
+ * Each payload targets a well-known file with distinctive content markers.
9
+ */
10
+ private readonly payloads;
11
+ testParameter(context: PluginContext, param: string, value: string): Promise<VulnerabilityTestResult>;
12
+ private buildTestUrl;
13
+ }
@@ -0,0 +1,110 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DirectoryTraversalPlugin = void 0;
4
+ const types_1 = require("../types");
5
+ class DirectoryTraversalPlugin extends types_1.BaseVulnerabilityPlugin {
6
+ name = "Directory Traversal / LFI Detector";
7
+ type = "lfi";
8
+ description = "Tests for path traversal and local file inclusion vulnerabilities";
9
+ /**
10
+ * Payloads designed to detect directory traversal / LFI.
11
+ * Each payload targets a well-known file with distinctive content markers.
12
+ */
13
+ payloads = [
14
+ // Linux/Unix path traversal
15
+ {
16
+ payload: "../../../etc/passwd",
17
+ markers: ["root:", "/bin/bash", "/bin/sh", "nobody:"],
18
+ os: "linux",
19
+ },
20
+ {
21
+ payload: "....//....//....//etc/passwd",
22
+ markers: ["root:", "/bin/bash", "/bin/sh"],
23
+ os: "linux",
24
+ },
25
+ {
26
+ payload: "..%2F..%2F..%2Fetc%2Fpasswd",
27
+ markers: ["root:", "/bin/bash", "/bin/sh"],
28
+ os: "linux",
29
+ },
30
+ {
31
+ payload: "%2e%2e/%2e%2e/%2e%2e/etc/passwd",
32
+ markers: ["root:", "/bin/bash"],
33
+ os: "linux",
34
+ },
35
+ {
36
+ payload: "....\\\\....\\\\....\\\\etc\\\\passwd",
37
+ markers: ["root:", "/bin/bash"],
38
+ os: "linux",
39
+ },
40
+ // Windows path traversal
41
+ {
42
+ payload: "..\\..\\..\\windows\\win.ini",
43
+ markers: ["[fonts]", "[extensions]", "[mci extensions]"],
44
+ os: "windows",
45
+ },
46
+ {
47
+ payload: "..%5C..%5C..%5Cwindows%5Cwin.ini",
48
+ markers: ["[fonts]", "[extensions]"],
49
+ os: "windows",
50
+ },
51
+ {
52
+ payload: "../../../windows/win.ini",
53
+ markers: ["[fonts]", "[extensions]"],
54
+ os: "windows",
55
+ },
56
+ // Null byte injection (legacy PHP)
57
+ {
58
+ payload: "../../../etc/passwd%00",
59
+ markers: ["root:", "/bin/bash"],
60
+ os: "linux",
61
+ },
62
+ // Double encoding
63
+ {
64
+ payload: "%252e%252e%252f%252e%252e%252f%252e%252e%252fetc%252fpasswd",
65
+ markers: ["root:", "/bin/bash"],
66
+ os: "linux",
67
+ },
68
+ ];
69
+ async testParameter(context, param, value) {
70
+ for (const { payload, markers, os } of this.payloads) {
71
+ try {
72
+ const testUrl = this.buildTestUrl(context.url, param, payload);
73
+ const response = await context.page.evaluate(async (url) => {
74
+ try {
75
+ const res = await fetch(url, {
76
+ method: "GET",
77
+ redirect: "follow",
78
+ credentials: "omit",
79
+ });
80
+ const text = await res.text();
81
+ return { status: res.status, body: text.substring(0, 5000) };
82
+ }
83
+ catch {
84
+ return { status: 0, body: "" };
85
+ }
86
+ }, testUrl);
87
+ if (response.status >= 200 && response.status < 500) {
88
+ const bodyLower = response.body.toLowerCase();
89
+ const matchedMarkers = markers.filter(m => bodyLower.includes(m.toLowerCase()));
90
+ if (matchedMarkers.length >= 2) {
91
+ return this.success(this.createVulnerability("Directory Traversal / Local File Inclusion", `The parameter '${param}' is vulnerable to directory traversal. ` +
92
+ `An attacker can read arbitrary files from the server filesystem (${os}).`, context.url, "critical", `Payload: ${param}=${payload} — matched markers: ${matchedMarkers.join(", ")}`, "Validate and sanitize all file path inputs. Use a whitelist of allowed files/directories. " +
93
+ "Never pass user input directly to file system operations. " +
94
+ "Use path.resolve() and verify the resolved path starts with the expected base directory.", "CWE-22"));
95
+ }
96
+ }
97
+ }
98
+ catch {
99
+ // Skip this payload, try next
100
+ }
101
+ }
102
+ return this.failure();
103
+ }
104
+ buildTestUrl(baseUrl, param, payload) {
105
+ const url = new URL(baseUrl);
106
+ url.searchParams.set(param, payload);
107
+ return url.toString();
108
+ }
109
+ }
110
+ exports.DirectoryTraversalPlugin = DirectoryTraversalPlugin;
@@ -0,0 +1,10 @@
1
+ import { BaseVulnerabilityPlugin, PluginContext } from "../types";
2
+ import { Vulnerability } from "../../core/vulnerability-detector";
3
+ export declare class OpenRedirectPlugin extends BaseVulnerabilityPlugin {
4
+ readonly name = "Open Redirect Detector";
5
+ readonly type: "redirect";
6
+ readonly description = "Detects open redirect vulnerabilities in URL parameters";
7
+ private readonly redirectParams;
8
+ private readonly testDomains;
9
+ analyzeContent(context: PluginContext, content: string): Promise<Vulnerability[]>;
10
+ }
@@ -0,0 +1,69 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.OpenRedirectPlugin = void 0;
4
+ const types_1 = require("../types");
5
+ class OpenRedirectPlugin extends types_1.BaseVulnerabilityPlugin {
6
+ name = "Open Redirect Detector";
7
+ type = "redirect";
8
+ description = "Detects open redirect vulnerabilities in URL parameters";
9
+ redirectParams = [
10
+ "url", "redirect", "redirect_url", "redirect_uri", "return", "return_url",
11
+ "returnTo", "next", "next_url", "goto", "target", "dest", "destination",
12
+ "rurl", "redir", "out", "continue", "forward", "callback", "callback_url",
13
+ "path", "ref", "site", "view", "to", "link", "logout_redirect",
14
+ ];
15
+ testDomains = [
16
+ "https://evil.com",
17
+ "//evil.com",
18
+ "https://evil.com%2f%2f",
19
+ "/\\evil.com",
20
+ "https:evil.com",
21
+ ];
22
+ async analyzeContent(context, content) {
23
+ const vulnerabilities = [];
24
+ const url = new URL(context.url);
25
+ // Check if the current URL uses any redirect-like parameters
26
+ for (const param of this.redirectParams) {
27
+ const value = url.searchParams.get(param);
28
+ if (value) {
29
+ // Current URL has a redirect param — test it
30
+ for (const testDomain of this.testDomains) {
31
+ try {
32
+ const testUrl = new URL(context.url);
33
+ testUrl.searchParams.set(param, testDomain);
34
+ const result = await context.page.evaluate(async (targetUrl) => {
35
+ try {
36
+ const res = await fetch(targetUrl, {
37
+ method: "GET",
38
+ redirect: "manual",
39
+ credentials: "omit",
40
+ });
41
+ const location = res.headers.get("location") || "";
42
+ return {
43
+ status: res.status,
44
+ location,
45
+ };
46
+ }
47
+ catch {
48
+ return { status: 0, location: "" };
49
+ }
50
+ }, testUrl.toString());
51
+ if (result.status >= 300 && result.status < 400 &&
52
+ result.location.includes("evil.com")) {
53
+ vulnerabilities.push(this.createVulnerability("Open Redirect", `The parameter '${param}' allows redirection to arbitrary external URLs. ` +
54
+ `Attackers can use this for phishing by crafting legitimate-looking URLs that redirect to malicious sites.`, context.url, "medium", `${param}=${testDomain} → Location: ${result.location}`, "Validate redirect URLs against a whitelist of allowed domains. " +
55
+ "Use relative paths instead of full URLs. " +
56
+ "Never redirect to user-supplied URLs without validation.", "CWE-601"));
57
+ break; // One proof is enough for this param
58
+ }
59
+ }
60
+ catch {
61
+ // Skip this test
62
+ }
63
+ }
64
+ }
65
+ }
66
+ return vulnerabilities;
67
+ }
68
+ }
69
+ exports.OpenRedirectPlugin = OpenRedirectPlugin;
@@ -40,7 +40,17 @@ exports.pdfGenerator = exports.PdfGenerator = void 0;
40
40
  const puppeteer_1 = __importDefault(require("puppeteer"));
41
41
  const scan_storage_1 = require("../core/scan-storage");
42
42
  const path_1 = __importDefault(require("path"));
43
- function escapeHtml(text) { return text; }
43
+ function escapeHtml(text) {
44
+ if (!text)
45
+ return "";
46
+ return text
47
+ .toString()
48
+ .replace(/&/g, "&amp;")
49
+ .replace(/</g, "&lt;")
50
+ .replace(/>/g, "&gt;")
51
+ .replace(/"/g, "&quot;")
52
+ .replace(/'/g, "&#039;");
53
+ }
44
54
  function sanitizeFilenamePart(value) {
45
55
  return value
46
56
  .replace(/[<>:"\/\\|?*\x00-\x1F]/g, "_")
@@ -307,6 +317,21 @@ function buildPdfHtml(data) {
307
317
  </div>
308
318
 
309
319
  <div class="sub">Crawled URLs: <span class="mono">${scanResult.metadata.crawledUrls}</span> | Forms tested: <span class="mono">${scanResult.metadata.testedForms}</span> | Requests: <span class="mono">${scanResult.metadata.requestsMade}</span> | Duration: <span class="mono">${(scanResult.duration / 1000).toFixed(2)}s</span></div>
320
+
321
+ <div style="margin-top: 20px; display: flex; align-items: center; gap: 15px; background: rgba(17,26,51,0.55); padding: 15px; border-radius: 12px; border: 1px solid var(--line);">
322
+ <div style="font-size: 24px; font-weight: 900; color: ${scanResult.score > 80 ? "#52c41a" : (scanResult.score > 50 ? "#faad14" : "#ff4d4f")};">
323
+ ${scanResult.score}/100
324
+ </div>
325
+ <div style="flex-grow: 1;">
326
+ <div style="font-size: 11px; text-transform: uppercase; color: var(--muted); letter-spacing: 0.5px; margin-bottom: 5px;">Security Posture</div>
327
+ <div style="height: 6px; background: rgba(255,255,255,0.1); border-radius: 3px; overflow: hidden;">
328
+ <div style="width: ${scanResult.score}%; height: 100%; background: ${scanResult.score > 80 ? "#52c41a" : (scanResult.score > 50 ? "#faad14" : "#ff4d4f")};"></div>
329
+ </div>
330
+ </div>
331
+ <div style="font-size: 12px; font-weight: 700; color: var(--muted);">
332
+ ${scanResult.score > 80 ? "EXCELLENT" : (scanResult.score > 50 ? "FAIR" : "POOR")}
333
+ </div>
334
+ </div>
310
335
 
311
336
  <div class="cards">
312
337
  ${rows || `<div class="card"><div class="title">No vulnerabilities found</div><div class="v">The scanner did not detect issues in the tested scope.</div></div>`}
@@ -43,6 +43,7 @@ export declare function displayScanSummary(result: {
43
43
  low: number;
44
44
  info: number;
45
45
  };
46
+ score: number;
46
47
  vulnerabilities: Array<{
47
48
  severity: string;
48
49
  title: string;
@@ -127,7 +127,7 @@ function getSeverityColor(severity) {
127
127
  }
128
128
  // ─── Scan Summary Display ──────────────────────────────────────────
129
129
  function displayScanSummary(result) {
130
- const { target, duration, metadata, summary, vulnerabilities, filepath, pdfPath } = result;
130
+ const { target, duration, metadata, summary, vulnerabilities, filepath, pdfPath, score } = result;
131
131
  // Scan Summary
132
132
  console.log("");
133
133
  console.log(exports.theme.brightWhite.bold("📊 Scan Summary"));
@@ -138,6 +138,12 @@ function displayScanSummary(result) {
138
138
  console.log(exports.theme.white("URLs Crawled:"), exports.theme.cyan(metadata.crawledUrls));
139
139
  console.log(exports.theme.white("Forms Tested:"), exports.theme.cyan(metadata.testedForms));
140
140
  console.log(exports.theme.white("Requests Made:"), exports.theme.cyan(metadata.requestsMade));
141
+ // Security Score Display
142
+ const scoreColor = score > 80 ? exports.theme.success : (score > 50 ? exports.theme.warning : exports.theme.error);
143
+ const scoreLabel = score > 80 ? "EXCELLENT" : (score > 50 ? "FAIR" : "POOR");
144
+ console.log("");
145
+ console.log(` ${exports.theme.white("Security Score:")} ${scoreColor.bold(score + "/100")} ${exports.theme.gray(`(${scoreLabel})`)}`);
146
+ console.log(` ${scoreColor("█".repeat(Math.round(score / 5)) + exports.theme.dim("█".repeat(20 - Math.round(score / 5))))}`);
141
147
  console.log("");
142
148
  // Vulnerability summary
143
149
  console.log(exports.theme.brightWhite.bold("🛡️ Vulnerabilities Found"));
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "kramscan",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "KramScan CLI — AI-powered web app security testing",
5
- "author": "Akram Shaikh <akramshaikh.me>",
5
+ "author": "Akram Shaikh (https://akramshaikh.me)",
6
6
  "license": "MIT",
7
7
  "keywords": [
8
8
  "security",
@@ -13,6 +13,9 @@
13
13
  "web-security",
14
14
  "analysis"
15
15
  ],
16
+ "publishConfig": {
17
+ "access": "public"
18
+ },
16
19
  "repository": {
17
20
  "type": "git",
18
21
  "url": "https://github.com/shaikhakramshakil/kramscan.git"
@@ -45,7 +48,7 @@
45
48
  "lint": "eslint src --ext .ts",
46
49
  "lint:fix": "eslint src --ext .ts --fix",
47
50
  "format": "prettier --write \"src/**/*.ts\"",
48
- "prepublishOnly": "npm run clean && npm run build"
51
+ "prepublishOnly": "npm test && npm run clean && npm run build"
49
52
  },
50
53
  "dependencies": {
51
54
  "@anthropic-ai/sdk": "^0.31.0",