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