kramscan 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +392 -87
- package/dist/agent/confirmation.d.ts +38 -0
- package/dist/agent/confirmation.js +210 -0
- package/dist/agent/context.d.ts +81 -0
- package/dist/agent/context.js +227 -0
- package/dist/agent/index.d.ts +10 -0
- package/dist/agent/index.js +32 -0
- package/dist/agent/orchestrator.d.ts +63 -0
- package/dist/agent/orchestrator.js +370 -0
- package/dist/agent/prompts/system.d.ts +6 -0
- package/dist/agent/prompts/system.js +116 -0
- package/dist/agent/skill-registry.d.ts +78 -0
- package/dist/agent/skill-registry.js +202 -0
- package/dist/agent/skills/analyze-findings.d.ts +22 -0
- package/dist/agent/skills/analyze-findings.js +191 -0
- package/dist/agent/skills/generate-report.d.ts +26 -0
- package/dist/agent/skills/generate-report.js +436 -0
- package/dist/agent/skills/health-check.d.ts +28 -0
- package/dist/agent/skills/health-check.js +344 -0
- package/dist/agent/skills/index.d.ts +9 -0
- package/dist/agent/skills/index.js +17 -0
- package/dist/agent/skills/verify-finding.d.ts +17 -0
- package/dist/agent/skills/verify-finding.js +91 -0
- package/dist/agent/skills/web-scan.d.ts +22 -0
- package/dist/agent/skills/web-scan.js +203 -0
- package/dist/agent/types.d.ts +141 -0
- package/dist/agent/types.js +16 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.js +176 -139
- package/dist/commands/agent.d.ts +6 -0
- package/dist/commands/agent.js +250 -0
- package/dist/commands/ai.d.ts +2 -0
- package/dist/commands/ai.js +112 -0
- package/dist/commands/analyze.js +104 -55
- package/dist/commands/config.js +63 -37
- package/dist/commands/doctor.js +22 -17
- package/dist/commands/onboard.js +190 -125
- package/dist/commands/report.js +69 -77
- package/dist/commands/scan.js +261 -81
- package/dist/commands/scans.d.ts +2 -0
- package/dist/commands/scans.js +51 -0
- package/dist/core/ai-client.d.ts +7 -2
- package/dist/core/ai-client.js +231 -20
- package/dist/core/ai-payloads.d.ts +17 -0
- package/dist/core/ai-payloads.js +54 -0
- package/dist/core/config-schema.d.ts +197 -0
- package/dist/core/config-schema.js +68 -0
- package/dist/core/config-schema.test.d.ts +1 -0
- package/dist/core/config-schema.test.js +151 -0
- package/dist/core/config.d.ts +17 -36
- package/dist/core/config.js +261 -20
- package/dist/core/errors.d.ts +71 -0
- package/dist/core/errors.js +162 -0
- package/dist/core/scan-index.d.ts +19 -0
- package/dist/core/scan-index.js +52 -0
- package/dist/core/scan-storage.d.ts +11 -0
- package/dist/core/scan-storage.js +69 -0
- package/dist/core/scanner.d.ts +101 -4
- package/dist/core/scanner.js +432 -63
- package/dist/core/vulnerability-detector.d.ts +18 -2
- package/dist/core/vulnerability-detector.js +349 -38
- package/dist/core/vulnerability-detector.test.d.ts +1 -0
- package/dist/core/vulnerability-detector.test.js +210 -0
- package/dist/index.js +3 -0
- package/dist/plugins/PluginManager.d.ts +27 -0
- package/dist/plugins/PluginManager.js +166 -0
- package/dist/plugins/index.d.ts +7 -0
- package/dist/plugins/index.js +19 -0
- package/dist/plugins/types.d.ts +55 -0
- package/dist/plugins/types.js +25 -0
- package/dist/plugins/vulnerabilities/CSRFPlugin.d.ts +8 -0
- package/dist/plugins/vulnerabilities/CSRFPlugin.js +34 -0
- package/dist/plugins/vulnerabilities/SQLInjectionPlugin.d.ts +11 -0
- package/dist/plugins/vulnerabilities/SQLInjectionPlugin.js +109 -0
- package/dist/plugins/vulnerabilities/SecurityHeadersPlugin.d.ts +11 -0
- package/dist/plugins/vulnerabilities/SecurityHeadersPlugin.js +63 -0
- package/dist/plugins/vulnerabilities/SensitiveDataPlugin.d.ts +9 -0
- package/dist/plugins/vulnerabilities/SensitiveDataPlugin.js +32 -0
- package/dist/plugins/vulnerabilities/XSSPlugin.d.ts +15 -0
- package/dist/plugins/vulnerabilities/XSSPlugin.js +81 -0
- package/dist/reports/PdfGenerator.d.ts +36 -0
- package/dist/reports/PdfGenerator.js +379 -0
- package/dist/utils/logger.d.ts +33 -1
- package/dist/utils/logger.js +127 -8
- package/dist/utils/theme.d.ts +55 -0
- package/dist/utils/theme.js +195 -0
- package/package.json +27 -6
- package/dist/core/executor.d.ts +0 -2
- package/dist/core/executor.js +0 -74
- package/dist/core/logger.d.ts +0 -12
- package/dist/core/logger.js +0 -51
- package/dist/core/registry.d.ts +0 -3
- package/dist/core/registry.js +0 -35
- package/dist/core/storage.d.ts +0 -4
- package/dist/core/storage.js +0 -39
- package/dist/core/types.d.ts +0 -24
- package/dist/core/types.js +0 -2
- package/dist/skills/base.d.ts +0 -8
- package/dist/skills/base.js +0 -6
- package/dist/skills/builtin.d.ts +0 -4
- package/dist/skills/builtin.js +0 -71
- package/dist/skills/loader.d.ts +0 -2
- package/dist/skills/loader.js +0 -27
- package/dist/skills/types.d.ts +0 -46
- package/dist/skills/types.js +0 -2
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const vulnerability_detector_1 = require("./vulnerability-detector");
|
|
4
|
+
describe("VulnerabilityDetector", () => {
|
|
5
|
+
let detector;
|
|
6
|
+
beforeEach(() => {
|
|
7
|
+
detector = new vulnerability_detector_1.VulnerabilityDetector();
|
|
8
|
+
});
|
|
9
|
+
// ─── detectXSS ─────────────────────────────────────────────────
|
|
10
|
+
describe("detectXSS", () => {
|
|
11
|
+
it("should detect reflected XSS when payload is in response", () => {
|
|
12
|
+
const payload = "<script>alert('XSS')</script>";
|
|
13
|
+
detector.detectXSS("https://example.com/search?q=test", "q", payload, `<html><body>${payload}</body></html>`);
|
|
14
|
+
const vulns = detector.getVulnerabilities();
|
|
15
|
+
expect(vulns).toHaveLength(1);
|
|
16
|
+
expect(vulns[0].type).toBe("xss");
|
|
17
|
+
expect(vulns[0].severity).toBe("high");
|
|
18
|
+
expect(vulns[0].cwe).toBe("CWE-79");
|
|
19
|
+
});
|
|
20
|
+
it("should not report XSS when payload is not reflected", () => {
|
|
21
|
+
detector.detectXSS("https://example.com/search", "q", "<script>alert(1)</script>", "<html><body>Safe content</body></html>");
|
|
22
|
+
expect(detector.getVulnerabilities()).toHaveLength(0);
|
|
23
|
+
});
|
|
24
|
+
it("should report XSS for each detection call (no deduplication)", () => {
|
|
25
|
+
const payload = "<script>alert('XSS')</script>";
|
|
26
|
+
const response = `<html>${payload}</html>`;
|
|
27
|
+
detector.detectXSS("https://example.com", "q", payload, response);
|
|
28
|
+
detector.detectXSS("https://example.com", "q", payload, response);
|
|
29
|
+
// XSS detection does not deduplicate — each call adds a separate finding
|
|
30
|
+
expect(detector.getVulnerabilities()).toHaveLength(2);
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
// ─── detectSQLi ────────────────────────────────────────────────
|
|
34
|
+
describe("detectSQLi", () => {
|
|
35
|
+
it("should detect SQL injection from error messages", () => {
|
|
36
|
+
detector.detectSQLi("https://example.com/users?id=1", "id", "You have an error in your SQL syntax near '1'");
|
|
37
|
+
const vulns = detector.getVulnerabilities();
|
|
38
|
+
expect(vulns).toHaveLength(1);
|
|
39
|
+
expect(vulns[0].type).toBe("sqli");
|
|
40
|
+
expect(vulns[0].severity).toBe("critical");
|
|
41
|
+
expect(vulns[0].cwe).toBe("CWE-89");
|
|
42
|
+
});
|
|
43
|
+
it("should detect PostgreSQL errors", () => {
|
|
44
|
+
detector.detectSQLi("https://example.com/api", "q", "ERROR: PostgreSQL syntax error at position 42");
|
|
45
|
+
expect(detector.getVulnerabilities()).toHaveLength(1);
|
|
46
|
+
});
|
|
47
|
+
it("should not report when no SQL errors found", () => {
|
|
48
|
+
detector.detectSQLi("https://example.com", "q", "<html><body>Normal content</body></html>");
|
|
49
|
+
expect(detector.getVulnerabilities()).toHaveLength(0);
|
|
50
|
+
});
|
|
51
|
+
it("should deduplicate SQLi findings for the same URL", () => {
|
|
52
|
+
detector.detectSQLi("https://example.com", "id", "SQL syntax error");
|
|
53
|
+
detector.detectSQLi("https://example.com", "name", "ORA-00933 error");
|
|
54
|
+
expect(detector.getVulnerabilities()).toHaveLength(1);
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
// ─── detectBlindSQLi ───────────────────────────────────────────
|
|
58
|
+
describe("detectBlindSQLi", () => {
|
|
59
|
+
it("should detect time-based blind SQLi when delay exceeds threshold", () => {
|
|
60
|
+
detector.detectBlindSQLi("https://example.com", "id", 500, 4000);
|
|
61
|
+
const vulns = detector.getVulnerabilities();
|
|
62
|
+
expect(vulns).toHaveLength(1);
|
|
63
|
+
expect(vulns[0].type).toBe("sqli");
|
|
64
|
+
expect(vulns[0].title).toContain("Blind");
|
|
65
|
+
});
|
|
66
|
+
it("should not report when delay is within threshold", () => {
|
|
67
|
+
detector.detectBlindSQLi("https://example.com", "id", 500, 2000);
|
|
68
|
+
expect(detector.getVulnerabilities()).toHaveLength(0);
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
// ─── detectCSRF ────────────────────────────────────────────────
|
|
72
|
+
describe("detectCSRF", () => {
|
|
73
|
+
it("should detect missing CSRF token", () => {
|
|
74
|
+
detector.detectCSRF("https://example.com/form", '<form method="POST"><input name="email" /></form>');
|
|
75
|
+
const vulns = detector.getVulnerabilities();
|
|
76
|
+
expect(vulns).toHaveLength(1);
|
|
77
|
+
expect(vulns[0].type).toBe("csrf");
|
|
78
|
+
expect(vulns[0].severity).toBe("medium");
|
|
79
|
+
});
|
|
80
|
+
it("should not report when CSRF token is present", () => {
|
|
81
|
+
detector.detectCSRF("https://example.com/form", '<form method="POST"><input name="csrf_token" /><input name="email" /></form>');
|
|
82
|
+
expect(detector.getVulnerabilities()).toHaveLength(0);
|
|
83
|
+
});
|
|
84
|
+
it("should recognize _token as a valid CSRF token", () => {
|
|
85
|
+
detector.detectCSRF("https://example.com", '<form><input name="_token" value="abc123" /></form>');
|
|
86
|
+
expect(detector.getVulnerabilities()).toHaveLength(0);
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
// ─── analyzeSecurityHeaders ────────────────────────────────────
|
|
90
|
+
describe("analyzeSecurityHeaders", () => {
|
|
91
|
+
it("should detect missing security headers", () => {
|
|
92
|
+
detector.analyzeSecurityHeaders("https://example.com", {});
|
|
93
|
+
const vulns = detector.getVulnerabilities();
|
|
94
|
+
expect(vulns.length).toBeGreaterThanOrEqual(3);
|
|
95
|
+
expect(vulns.every((v) => v.type === "header")).toBe(true);
|
|
96
|
+
});
|
|
97
|
+
it("should not report when all headers are present", () => {
|
|
98
|
+
detector.analyzeSecurityHeaders("https://example.com", {
|
|
99
|
+
"content-security-policy": "default-src 'self'",
|
|
100
|
+
"x-frame-options": "DENY",
|
|
101
|
+
"strict-transport-security": "max-age=31536000",
|
|
102
|
+
"x-content-type-options": "nosniff",
|
|
103
|
+
"referrer-policy": "strict-origin-when-cross-origin",
|
|
104
|
+
"permissions-policy": "camera=()",
|
|
105
|
+
});
|
|
106
|
+
expect(detector.getVulnerabilities()).toHaveLength(0);
|
|
107
|
+
});
|
|
108
|
+
it("should only check headers once per host", () => {
|
|
109
|
+
detector.analyzeSecurityHeaders("https://example.com/page1", {});
|
|
110
|
+
const count1 = detector.getVulnerabilities().length;
|
|
111
|
+
detector.analyzeSecurityHeaders("https://example.com/page2", {});
|
|
112
|
+
const count2 = detector.getVulnerabilities().length;
|
|
113
|
+
expect(count2).toBe(count1);
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
// ─── detectSensitiveData ──────────────────────────────────────
|
|
117
|
+
describe("detectSensitiveData", () => {
|
|
118
|
+
it("should detect exposed AWS access keys", () => {
|
|
119
|
+
detector.detectSensitiveData("https://example.com", 'config = { key: "AKIAIOSFODNN7EXAMPLE" }');
|
|
120
|
+
const vulns = detector.getVulnerabilities();
|
|
121
|
+
expect(vulns).toHaveLength(1);
|
|
122
|
+
expect(vulns[0].type).toBe("sensitive_data");
|
|
123
|
+
expect(vulns[0].severity).toBe("critical");
|
|
124
|
+
});
|
|
125
|
+
it("should detect exposed JWT tokens", () => {
|
|
126
|
+
detector.detectSensitiveData("https://example.com", 'let token = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0"');
|
|
127
|
+
const vulns = detector.getVulnerabilities();
|
|
128
|
+
expect(vulns).toHaveLength(1);
|
|
129
|
+
expect(vulns[0].title).toContain("JWT");
|
|
130
|
+
});
|
|
131
|
+
it("should detect private keys", () => {
|
|
132
|
+
detector.detectSensitiveData("https://example.com", "-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAK...");
|
|
133
|
+
const vulns = detector.getVulnerabilities();
|
|
134
|
+
expect(vulns).toHaveLength(1);
|
|
135
|
+
expect(vulns[0].severity).toBe("critical");
|
|
136
|
+
});
|
|
137
|
+
it("should not report on clean responses", () => {
|
|
138
|
+
detector.detectSensitiveData("https://example.com", "<html><body>Hello World</body></html>");
|
|
139
|
+
expect(detector.getVulnerabilities()).toHaveLength(0);
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
// ─── getSummary ────────────────────────────────────────────────
|
|
143
|
+
describe("getSummary", () => {
|
|
144
|
+
it("should return correct severity counts", () => {
|
|
145
|
+
// Add mixed severity vulns
|
|
146
|
+
detector.addVulnerability({
|
|
147
|
+
type: "xss", severity: "high", title: "XSS",
|
|
148
|
+
description: "test", url: "https://example.com",
|
|
149
|
+
});
|
|
150
|
+
detector.addVulnerability({
|
|
151
|
+
type: "sqli", severity: "critical", title: "SQLi",
|
|
152
|
+
description: "test", url: "https://example.com",
|
|
153
|
+
});
|
|
154
|
+
detector.addVulnerability({
|
|
155
|
+
type: "header", severity: "low", title: "Header",
|
|
156
|
+
description: "test", url: "https://example.com",
|
|
157
|
+
});
|
|
158
|
+
detector.addVulnerability({
|
|
159
|
+
type: "info", severity: "info", title: "Info",
|
|
160
|
+
description: "test", url: "https://example.com",
|
|
161
|
+
});
|
|
162
|
+
const summary = detector.getSummary();
|
|
163
|
+
expect(summary.total).toBe(4);
|
|
164
|
+
expect(summary.critical).toBe(1);
|
|
165
|
+
expect(summary.high).toBe(1);
|
|
166
|
+
expect(summary.low).toBe(1);
|
|
167
|
+
expect(summary.info).toBe(1);
|
|
168
|
+
expect(summary.medium).toBe(0);
|
|
169
|
+
});
|
|
170
|
+
it("should return zeros when no vulnerabilities", () => {
|
|
171
|
+
const summary = detector.getSummary();
|
|
172
|
+
expect(summary.total).toBe(0);
|
|
173
|
+
expect(summary.critical).toBe(0);
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
// ─── clear ─────────────────────────────────────────────────────
|
|
177
|
+
describe("clear", () => {
|
|
178
|
+
it("should remove all vulnerabilities and reset state", () => {
|
|
179
|
+
detector.addVulnerability({
|
|
180
|
+
type: "xss", severity: "high", title: "XSS",
|
|
181
|
+
description: "test", url: "https://example.com",
|
|
182
|
+
});
|
|
183
|
+
expect(detector.getVulnerabilities()).toHaveLength(1);
|
|
184
|
+
detector.clear();
|
|
185
|
+
expect(detector.getVulnerabilities()).toHaveLength(0);
|
|
186
|
+
expect(detector.getSummary().total).toBe(0);
|
|
187
|
+
});
|
|
188
|
+
it("should allow re-detecting after clear", () => {
|
|
189
|
+
detector.analyzeSecurityHeaders("https://example.com", {});
|
|
190
|
+
const count1 = detector.getVulnerabilities().length;
|
|
191
|
+
detector.clear();
|
|
192
|
+
detector.analyzeSecurityHeaders("https://example.com", {});
|
|
193
|
+
const count2 = detector.getVulnerabilities().length;
|
|
194
|
+
expect(count2).toBe(count1);
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
// ─── Callback ──────────────────────────────────────────────────
|
|
198
|
+
describe("onVulnerabilityFound callback", () => {
|
|
199
|
+
it("should call callback when vulnerability is added", () => {
|
|
200
|
+
const callback = jest.fn();
|
|
201
|
+
detector.setOnVulnerabilityFound(callback);
|
|
202
|
+
detector.addVulnerability({
|
|
203
|
+
type: "xss", severity: "high", title: "XSS",
|
|
204
|
+
description: "test", url: "https://example.com",
|
|
205
|
+
});
|
|
206
|
+
expect(callback).toHaveBeenCalledTimes(1);
|
|
207
|
+
expect(callback).toHaveBeenCalledWith(expect.objectContaining({ type: "xss", severity: "high" }));
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
});
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const cli_1 = require("./cli");
|
|
4
|
+
const errors_1 = require("./core/errors");
|
|
5
|
+
// Ensure uncaught exceptions and unhandled rejections produce useful output
|
|
6
|
+
(0, errors_1.setupGlobalErrorHandlers)();
|
|
4
7
|
(0, cli_1.run)().catch((err) => {
|
|
5
8
|
console.error("Fatal error:", err);
|
|
6
9
|
process.exit(1);
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { VulnerabilityPlugin, PluginContext, FormData } from "./types";
|
|
2
|
+
import { Vulnerability } from "../core/vulnerability-detector";
|
|
3
|
+
export interface PluginExecutionResult {
|
|
4
|
+
plugin: string;
|
|
5
|
+
vulnerabilities: Vulnerability[];
|
|
6
|
+
errors: Array<{
|
|
7
|
+
url: string;
|
|
8
|
+
error: string;
|
|
9
|
+
}>;
|
|
10
|
+
duration: number;
|
|
11
|
+
}
|
|
12
|
+
export declare class PluginManager {
|
|
13
|
+
private plugins;
|
|
14
|
+
private enabledPlugins;
|
|
15
|
+
register(plugin: VulnerabilityPlugin): void;
|
|
16
|
+
unregister(pluginName: string): boolean;
|
|
17
|
+
enable(pluginName: string): boolean;
|
|
18
|
+
disable(pluginName: string): boolean;
|
|
19
|
+
getPlugin(name: string): VulnerabilityPlugin | undefined;
|
|
20
|
+
getAllPlugins(): VulnerabilityPlugin[];
|
|
21
|
+
getEnabledPlugins(): VulnerabilityPlugin[];
|
|
22
|
+
testParameter(context: PluginContext, param: string, value: string): Promise<PluginExecutionResult[]>;
|
|
23
|
+
testFormInput(context: PluginContext, formData: FormData): Promise<PluginExecutionResult[]>;
|
|
24
|
+
analyzeContent(context: PluginContext, content: string): Promise<PluginExecutionResult[]>;
|
|
25
|
+
analyzeHeaders(context: PluginContext, headers: Record<string, string>): Promise<PluginExecutionResult[]>;
|
|
26
|
+
}
|
|
27
|
+
export declare const pluginManager: PluginManager;
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.pluginManager = exports.PluginManager = void 0;
|
|
4
|
+
class PluginManager {
|
|
5
|
+
plugins = new Map();
|
|
6
|
+
enabledPlugins = new Set();
|
|
7
|
+
register(plugin) {
|
|
8
|
+
this.plugins.set(plugin.name, plugin);
|
|
9
|
+
if (plugin.enabled) {
|
|
10
|
+
this.enabledPlugins.add(plugin.name);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
unregister(pluginName) {
|
|
14
|
+
this.enabledPlugins.delete(pluginName);
|
|
15
|
+
return this.plugins.delete(pluginName);
|
|
16
|
+
}
|
|
17
|
+
enable(pluginName) {
|
|
18
|
+
if (this.plugins.has(pluginName)) {
|
|
19
|
+
this.enabledPlugins.add(pluginName);
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
disable(pluginName) {
|
|
25
|
+
return this.enabledPlugins.delete(pluginName);
|
|
26
|
+
}
|
|
27
|
+
getPlugin(name) {
|
|
28
|
+
return this.plugins.get(name);
|
|
29
|
+
}
|
|
30
|
+
getAllPlugins() {
|
|
31
|
+
return Array.from(this.plugins.values());
|
|
32
|
+
}
|
|
33
|
+
getEnabledPlugins() {
|
|
34
|
+
return Array.from(this.enabledPlugins)
|
|
35
|
+
.map(name => this.plugins.get(name))
|
|
36
|
+
.filter((p) => p !== undefined);
|
|
37
|
+
}
|
|
38
|
+
async testParameter(context, param, value) {
|
|
39
|
+
const results = [];
|
|
40
|
+
for (const plugin of this.getEnabledPlugins()) {
|
|
41
|
+
if (!plugin.testParameter)
|
|
42
|
+
continue;
|
|
43
|
+
const startTime = Date.now();
|
|
44
|
+
const vulnerabilities = [];
|
|
45
|
+
const errors = [];
|
|
46
|
+
try {
|
|
47
|
+
const result = await plugin.testParameter(context, param, value);
|
|
48
|
+
if (result.found && result.vulnerability) {
|
|
49
|
+
vulnerabilities.push(result.vulnerability);
|
|
50
|
+
}
|
|
51
|
+
if (result.error) {
|
|
52
|
+
errors.push({ url: context.url, error: result.error });
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
errors.push({
|
|
57
|
+
url: context.url,
|
|
58
|
+
error: error.message
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
results.push({
|
|
62
|
+
plugin: plugin.name,
|
|
63
|
+
vulnerabilities,
|
|
64
|
+
errors,
|
|
65
|
+
duration: Date.now() - startTime,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
return results;
|
|
69
|
+
}
|
|
70
|
+
async testFormInput(context, formData) {
|
|
71
|
+
const results = [];
|
|
72
|
+
for (const plugin of this.getEnabledPlugins()) {
|
|
73
|
+
if (!plugin.testFormInput)
|
|
74
|
+
continue;
|
|
75
|
+
const startTime = Date.now();
|
|
76
|
+
const vulnerabilities = [];
|
|
77
|
+
const errors = [];
|
|
78
|
+
try {
|
|
79
|
+
const result = await plugin.testFormInput(context, formData);
|
|
80
|
+
if (result.found && result.vulnerability) {
|
|
81
|
+
vulnerabilities.push(result.vulnerability);
|
|
82
|
+
}
|
|
83
|
+
if (result.error) {
|
|
84
|
+
errors.push({ url: context.url, error: result.error });
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
errors.push({
|
|
89
|
+
url: context.url,
|
|
90
|
+
error: error.message
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
results.push({
|
|
94
|
+
plugin: plugin.name,
|
|
95
|
+
vulnerabilities,
|
|
96
|
+
errors,
|
|
97
|
+
duration: Date.now() - startTime,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
return results;
|
|
101
|
+
}
|
|
102
|
+
async analyzeContent(context, content) {
|
|
103
|
+
const results = [];
|
|
104
|
+
for (const plugin of this.getEnabledPlugins()) {
|
|
105
|
+
if (!plugin.analyzeContent)
|
|
106
|
+
continue;
|
|
107
|
+
const startTime = Date.now();
|
|
108
|
+
const errors = [];
|
|
109
|
+
try {
|
|
110
|
+
const vulnerabilities = await plugin.analyzeContent(context, content);
|
|
111
|
+
results.push({
|
|
112
|
+
plugin: plugin.name,
|
|
113
|
+
vulnerabilities,
|
|
114
|
+
errors,
|
|
115
|
+
duration: Date.now() - startTime,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
catch (error) {
|
|
119
|
+
errors.push({
|
|
120
|
+
url: context.url,
|
|
121
|
+
error: error.message
|
|
122
|
+
});
|
|
123
|
+
results.push({
|
|
124
|
+
plugin: plugin.name,
|
|
125
|
+
vulnerabilities: [],
|
|
126
|
+
errors,
|
|
127
|
+
duration: Date.now() - startTime,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return results;
|
|
132
|
+
}
|
|
133
|
+
async analyzeHeaders(context, headers) {
|
|
134
|
+
const results = [];
|
|
135
|
+
for (const plugin of this.getEnabledPlugins()) {
|
|
136
|
+
if (!plugin.analyzeHeaders)
|
|
137
|
+
continue;
|
|
138
|
+
const startTime = Date.now();
|
|
139
|
+
const errors = [];
|
|
140
|
+
try {
|
|
141
|
+
const vulnerabilities = await plugin.analyzeHeaders(context, headers);
|
|
142
|
+
results.push({
|
|
143
|
+
plugin: plugin.name,
|
|
144
|
+
vulnerabilities,
|
|
145
|
+
errors,
|
|
146
|
+
duration: Date.now() - startTime,
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
catch (error) {
|
|
150
|
+
errors.push({
|
|
151
|
+
url: context.url,
|
|
152
|
+
error: error.message
|
|
153
|
+
});
|
|
154
|
+
results.push({
|
|
155
|
+
plugin: plugin.name,
|
|
156
|
+
vulnerabilities: [],
|
|
157
|
+
errors,
|
|
158
|
+
duration: Date.now() - startTime,
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return results;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
exports.PluginManager = PluginManager;
|
|
166
|
+
exports.pluginManager = new PluginManager();
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { VulnerabilityPlugin, PluginContext, BaseVulnerabilityPlugin, VulnerabilityTestResult, FormData } from "./types";
|
|
2
|
+
export { PluginManager, PluginExecutionResult, pluginManager } from "./PluginManager";
|
|
3
|
+
export { XSSPlugin } from "./vulnerabilities/XSSPlugin";
|
|
4
|
+
export { SQLInjectionPlugin } from "./vulnerabilities/SQLInjectionPlugin";
|
|
5
|
+
export { SecurityHeadersPlugin } from "./vulnerabilities/SecurityHeadersPlugin";
|
|
6
|
+
export { SensitiveDataPlugin } from "./vulnerabilities/SensitiveDataPlugin";
|
|
7
|
+
export { CSRFPlugin } from "./vulnerabilities/CSRFPlugin";
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CSRFPlugin = exports.SensitiveDataPlugin = exports.SecurityHeadersPlugin = exports.SQLInjectionPlugin = exports.XSSPlugin = exports.pluginManager = exports.PluginManager = exports.BaseVulnerabilityPlugin = void 0;
|
|
4
|
+
var types_1 = require("./types");
|
|
5
|
+
Object.defineProperty(exports, "BaseVulnerabilityPlugin", { enumerable: true, get: function () { return types_1.BaseVulnerabilityPlugin; } });
|
|
6
|
+
var PluginManager_1 = require("./PluginManager");
|
|
7
|
+
Object.defineProperty(exports, "PluginManager", { enumerable: true, get: function () { return PluginManager_1.PluginManager; } });
|
|
8
|
+
Object.defineProperty(exports, "pluginManager", { enumerable: true, get: function () { return PluginManager_1.pluginManager; } });
|
|
9
|
+
// Vulnerability plugins
|
|
10
|
+
var XSSPlugin_1 = require("./vulnerabilities/XSSPlugin");
|
|
11
|
+
Object.defineProperty(exports, "XSSPlugin", { enumerable: true, get: function () { return XSSPlugin_1.XSSPlugin; } });
|
|
12
|
+
var SQLInjectionPlugin_1 = require("./vulnerabilities/SQLInjectionPlugin");
|
|
13
|
+
Object.defineProperty(exports, "SQLInjectionPlugin", { enumerable: true, get: function () { return SQLInjectionPlugin_1.SQLInjectionPlugin; } });
|
|
14
|
+
var SecurityHeadersPlugin_1 = require("./vulnerabilities/SecurityHeadersPlugin");
|
|
15
|
+
Object.defineProperty(exports, "SecurityHeadersPlugin", { enumerable: true, get: function () { return SecurityHeadersPlugin_1.SecurityHeadersPlugin; } });
|
|
16
|
+
var SensitiveDataPlugin_1 = require("./vulnerabilities/SensitiveDataPlugin");
|
|
17
|
+
Object.defineProperty(exports, "SensitiveDataPlugin", { enumerable: true, get: function () { return SensitiveDataPlugin_1.SensitiveDataPlugin; } });
|
|
18
|
+
var CSRFPlugin_1 = require("./vulnerabilities/CSRFPlugin");
|
|
19
|
+
Object.defineProperty(exports, "CSRFPlugin", { enumerable: true, get: function () { return CSRFPlugin_1.CSRFPlugin; } });
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { Page } from "puppeteer";
|
|
2
|
+
import { Vulnerability, VulnerabilityType, Severity } from "../core/vulnerability-detector";
|
|
3
|
+
export interface PluginContext {
|
|
4
|
+
page: Page;
|
|
5
|
+
url: string;
|
|
6
|
+
baseUrl: string;
|
|
7
|
+
timeout: number;
|
|
8
|
+
userAgent: string;
|
|
9
|
+
payloadGenerator?: any;
|
|
10
|
+
}
|
|
11
|
+
export interface VulnerabilityTestResult {
|
|
12
|
+
found: boolean;
|
|
13
|
+
vulnerability?: Vulnerability;
|
|
14
|
+
error?: string;
|
|
15
|
+
}
|
|
16
|
+
export interface VulnerabilityPlugin {
|
|
17
|
+
readonly name: string;
|
|
18
|
+
readonly type: VulnerabilityType;
|
|
19
|
+
readonly description: string;
|
|
20
|
+
readonly enabled: boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Test a URL parameter for this vulnerability
|
|
23
|
+
*/
|
|
24
|
+
testParameter?(context: PluginContext, param: string, value: string): Promise<VulnerabilityTestResult>;
|
|
25
|
+
/**
|
|
26
|
+
* Test a form input for this vulnerability
|
|
27
|
+
*/
|
|
28
|
+
testFormInput?(context: PluginContext, formData: FormData): Promise<VulnerabilityTestResult>;
|
|
29
|
+
/**
|
|
30
|
+
* Analyze page content for this vulnerability
|
|
31
|
+
*/
|
|
32
|
+
analyzeContent?(context: PluginContext, content: string): Promise<Vulnerability[]>;
|
|
33
|
+
/**
|
|
34
|
+
* Analyze HTTP headers for this vulnerability
|
|
35
|
+
*/
|
|
36
|
+
analyzeHeaders?(context: PluginContext, headers: Record<string, string>): Promise<Vulnerability[]>;
|
|
37
|
+
}
|
|
38
|
+
export interface FormData {
|
|
39
|
+
action: string;
|
|
40
|
+
method: string;
|
|
41
|
+
inputs: Array<{
|
|
42
|
+
name: string;
|
|
43
|
+
type: string;
|
|
44
|
+
value?: string;
|
|
45
|
+
}>;
|
|
46
|
+
}
|
|
47
|
+
export declare abstract class BaseVulnerabilityPlugin implements VulnerabilityPlugin {
|
|
48
|
+
abstract readonly name: string;
|
|
49
|
+
abstract readonly type: VulnerabilityType;
|
|
50
|
+
abstract readonly description: string;
|
|
51
|
+
enabled: boolean;
|
|
52
|
+
protected createVulnerability(title: string, description: string, url: string, severity: Severity, evidence?: string, remediation?: string, cwe?: string): Vulnerability;
|
|
53
|
+
protected success(vulnerability: Vulnerability): VulnerabilityTestResult;
|
|
54
|
+
protected failure(error?: string): VulnerabilityTestResult;
|
|
55
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.BaseVulnerabilityPlugin = void 0;
|
|
4
|
+
class BaseVulnerabilityPlugin {
|
|
5
|
+
enabled = true;
|
|
6
|
+
createVulnerability(title, description, url, severity, evidence, remediation, cwe) {
|
|
7
|
+
return {
|
|
8
|
+
type: this.type,
|
|
9
|
+
severity,
|
|
10
|
+
title,
|
|
11
|
+
description,
|
|
12
|
+
url,
|
|
13
|
+
evidence,
|
|
14
|
+
remediation,
|
|
15
|
+
cwe,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
success(vulnerability) {
|
|
19
|
+
return { found: true, vulnerability };
|
|
20
|
+
}
|
|
21
|
+
failure(error) {
|
|
22
|
+
return { found: false, error };
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
exports.BaseVulnerabilityPlugin = BaseVulnerabilityPlugin;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { BaseVulnerabilityPlugin, PluginContext } from "../types";
|
|
2
|
+
export declare class CSRFPlugin extends BaseVulnerabilityPlugin {
|
|
3
|
+
readonly name = "CSRF Detector";
|
|
4
|
+
readonly type: "csrf";
|
|
5
|
+
readonly description = "Detects missing CSRF protection in forms";
|
|
6
|
+
private readonly csrfTokenPatterns;
|
|
7
|
+
analyzeContent(context: PluginContext, content: string): Promise<import("../../core/vulnerability-detector").Vulnerability[]>;
|
|
8
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CSRFPlugin = void 0;
|
|
4
|
+
const types_1 = require("../types");
|
|
5
|
+
class CSRFPlugin extends types_1.BaseVulnerabilityPlugin {
|
|
6
|
+
name = "CSRF Detector";
|
|
7
|
+
type = "csrf";
|
|
8
|
+
description = "Detects missing CSRF protection in forms";
|
|
9
|
+
csrfTokenPatterns = [
|
|
10
|
+
'name="csrf',
|
|
11
|
+
'name="_token',
|
|
12
|
+
'name="authenticity_token',
|
|
13
|
+
'name="_csrf',
|
|
14
|
+
];
|
|
15
|
+
async analyzeContent(context, content) {
|
|
16
|
+
// Look for forms in the content
|
|
17
|
+
const formRegex = /<form[^>]*>([\s\S]*?)<\/form>/gi;
|
|
18
|
+
const forms = content.match(formRegex);
|
|
19
|
+
if (!forms)
|
|
20
|
+
return [];
|
|
21
|
+
const vulnerabilities = [];
|
|
22
|
+
for (const form of forms) {
|
|
23
|
+
const hasCSRFToken = this.csrfTokenPatterns.some(pattern => form.toLowerCase().includes(pattern.toLowerCase()));
|
|
24
|
+
if (!hasCSRFToken) {
|
|
25
|
+
// Extract form action for better reporting
|
|
26
|
+
const actionMatch = form.match(/action=["']([^"']*)["']/i);
|
|
27
|
+
const action = actionMatch ? actionMatch[1] : context.url;
|
|
28
|
+
vulnerabilities.push(this.createVulnerability("Missing CSRF Protection", "Form lacks CSRF tokens. Attackers can forge requests to perform unauthorized actions.", new URL(action, context.url).toString(), "medium", "Form HTML does not contain CSRF token", "Implement anti-CSRF tokens. Use SameSite cookies.", "CWE-352"));
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return vulnerabilities;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
exports.CSRFPlugin = CSRFPlugin;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { BaseVulnerabilityPlugin, PluginContext } from "../types";
|
|
2
|
+
export declare class SQLInjectionPlugin extends BaseVulnerabilityPlugin {
|
|
3
|
+
readonly name = "SQL Injection Detector";
|
|
4
|
+
readonly type: "sqli";
|
|
5
|
+
readonly description = "Detects SQL Injection vulnerabilities";
|
|
6
|
+
private readonly errorBasedPayloads;
|
|
7
|
+
private readonly timeBasedPayloads;
|
|
8
|
+
private getErrorPayloads;
|
|
9
|
+
private readonly sqlErrors;
|
|
10
|
+
testParameter(context: PluginContext, param: string, _value: string): Promise<import("../types").VulnerabilityTestResult>;
|
|
11
|
+
}
|