cyberaudit-skill 3.1.4 ā 3.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 +84 -25
- package/dist/cli.d.ts +10 -0
- package/dist/cli.js +555 -170
- package/dist/cli.test.d.ts +1 -0
- package/dist/cli.test.js +100 -0
- package/dist/mcp-server.d.ts +135 -1
- package/dist/mcp-server.js +185 -96
- package/dist/mcp-server.test.d.ts +1 -0
- package/dist/mcp-server.test.js +77 -0
- package/dist/report/generator.d.ts +2 -0
- package/dist/report/generator.js +260 -0
- package/dist/report/generator.test.d.ts +1 -0
- package/dist/report/generator.test.js +73 -0
- package/dist/report/types.d.ts +31 -0
- package/dist/report/types.js +1 -0
- package/dist/scanners/secrets.d.ts +32 -0
- package/dist/scanners/secrets.js +437 -0
- package/dist/scanners/secrets.test.d.ts +1 -0
- package/dist/scanners/secrets.test.js +96 -0
- package/dist/scanners/types.d.ts +22 -0
- package/dist/scanners/types.js +1 -0
- package/dist/scanners/web.d.ts +37 -0
- package/dist/scanners/web.js +327 -0
- package/dist/scanners/web.test.d.ts +1 -0
- package/dist/scanners/web.test.js +67 -0
- package/package.json +15 -3
- package/skills/cyberaudit/AGENT-BOOT.md +1 -1
- package/skills/cyberaudit/COMMANDS.md +11 -1
- package/skills/cyberaudit/INSTALL.md +7 -6
- package/skills/cyberaudit/README.md +20 -5
- package/skills/cyberaudit/SKILL.md +20 -5
- package/skills/cyberaudit/cloud/CLOUD-CHECKLIST.md +85 -0
- package/skills/cyberaudit/cloud/CLOUD-PHILOSOPHY.md +91 -0
- package/skills/cyberaudit/cloud/CLOUD-REMEDIATION-LIBRARY.md +145 -0
- package/skills/cyberaudit/commands/audit-angular.md +11 -0
- package/skills/cyberaudit/commands/audit-api-bola.md +11 -0
- package/skills/cyberaudit/commands/audit-api-bopla.md +11 -0
- package/skills/cyberaudit/commands/audit-api-graphql.md +11 -0
- package/skills/cyberaudit/commands/audit-api-inventory.md +11 -0
- package/skills/cyberaudit/commands/audit-api-rate-limit.md +11 -0
- package/skills/cyberaudit/commands/audit-api-rest.md +11 -0
- package/skills/cyberaudit/commands/audit-api-third-party.md +11 -0
- package/skills/cyberaudit/commands/audit-api-ws.md +11 -0
- package/skills/cyberaudit/commands/audit-auth-api.md +11 -0
- package/skills/cyberaudit/commands/audit-auth-mobile.md +11 -0
- package/skills/cyberaudit/commands/audit-auth.md +12 -0
- package/skills/cyberaudit/commands/audit-binary.md +11 -0
- package/skills/cyberaudit/commands/audit-cis.md +11 -0
- package/skills/cyberaudit/commands/audit-cloud-iam.md +11 -0
- package/skills/cyberaudit/commands/audit-cloud-network.md +11 -0
- package/skills/cyberaudit/commands/audit-cloud-s3.md +11 -0
- package/skills/cyberaudit/commands/audit-cloud.md +10 -0
- package/skills/cyberaudit/commands/audit-cors.md +11 -0
- package/skills/cyberaudit/commands/audit-crypto-mobile.md +11 -0
- package/skills/cyberaudit/commands/audit-crypto.md +10 -0
- package/skills/cyberaudit/commands/audit-csrf.md +11 -0
- package/skills/cyberaudit/commands/audit-deeplinks.md +11 -0
- package/skills/cyberaudit/commands/audit-deps.md +11 -0
- package/skills/cyberaudit/commands/audit-deserial.md +11 -0
- package/skills/cyberaudit/commands/audit-exec.md +11 -0
- package/skills/cyberaudit/commands/audit-expo.md +11 -0
- package/skills/cyberaudit/commands/audit-express.md +11 -0
- package/skills/cyberaudit/commands/audit-flutter.md +11 -0
- package/skills/cyberaudit/commands/audit-headers.md +11 -0
- package/skills/cyberaudit/commands/audit-hipaa.md +11 -0
- package/skills/cyberaudit/commands/audit-idor.md +11 -0
- package/skills/cyberaudit/commands/audit-injection.md +12 -0
- package/skills/cyberaudit/commands/audit-ionic.md +11 -0
- package/skills/cyberaudit/commands/audit-laravel.md +11 -0
- package/skills/cyberaudit/commands/audit-logic.md +11 -0
- package/skills/cyberaudit/commands/audit-masvs.md +11 -0
- package/skills/cyberaudit/commands/audit-nestjs.md +11 -0
- package/skills/cyberaudit/commands/audit-network.md +11 -0
- package/skills/cyberaudit/commands/audit-nextjs.md +11 -0
- package/skills/cyberaudit/commands/audit-owasp.md +11 -0
- package/skills/cyberaudit/commands/audit-pci.md +11 -0
- package/skills/cyberaudit/commands/audit-permissions.md +11 -0
- package/skills/cyberaudit/commands/audit-react-native.md +11 -0
- package/skills/cyberaudit/commands/audit-react.md +11 -0
- package/skills/cyberaudit/commands/audit-rgpd.md +11 -0
- package/skills/cyberaudit/commands/audit-runtime.md +11 -0
- package/skills/cyberaudit/commands/audit-secrets.md +11 -0
- package/skills/cyberaudit/commands/audit-ssrf.md +11 -0
- package/skills/cyberaudit/commands/audit-storage.md +11 -0
- package/skills/cyberaudit/commands/audit-vue.md +11 -0
- package/skills/cyberaudit/commands/audit-xss.md +11 -0
- package/skills/cyberaudit/commands/audit-xxe.md +11 -0
- package/skills/cyberaudit/reports/EXECUTIVE-SUMMARY-TEMPLATE.md +1 -1
- package/skills/cyberaudit/reports/REPORT-TEMPLATE-CLOUD.md +103 -0
- package/skills/cyberaudit/reports/REPORT-TEMPLATE-MOBILE.md +2 -2
- package/skills/cyberaudit/reports/REPORT-TEMPLATE-WEB.md +2 -2
package/dist/cli.js
CHANGED
|
@@ -1,268 +1,653 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { Command } from "commander";
|
|
3
|
-
import { existsSync, mkdirSync, cpSync, readdirSync, writeFileSync, readFileSync, unlinkSync } from "fs";
|
|
4
|
-
import { createInterface } from "readline";
|
|
3
|
+
import { existsSync, mkdirSync, cpSync, readdirSync, writeFileSync, readFileSync, rmSync, unlinkSync, copyFileSync } from "fs";
|
|
5
4
|
import { homedir } from "os";
|
|
6
|
-
import { join, dirname } from "path";
|
|
5
|
+
import { join, dirname, resolve, relative } from "path";
|
|
7
6
|
import { fileURLToPath } from "url";
|
|
7
|
+
import { startMcpServer as startMcpServerImpl } from "./mcp-server.js";
|
|
8
|
+
import { scanSecrets, formatFindingsText } from "./scanners/secrets.js";
|
|
9
|
+
import { scanWeb, formatWebFindingsText } from "./scanners/web.js";
|
|
8
10
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
9
11
|
const PKG_ROOT = join(__dirname, "..");
|
|
10
12
|
const PKG = JSON.parse(readFileSync(join(PKG_ROOT, "package.json"), "utf-8"));
|
|
11
13
|
const SKILL_SRC = join(PKG_ROOT, "skills", "cyberaudit");
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
14
|
+
const COMMANDS_SRC = join(SKILL_SRC, "commands");
|
|
15
|
+
const VERSION = PKG.version || "3.1.5";
|
|
16
|
+
const MAIN_COMMANDS = [
|
|
17
|
+
'audit.md',
|
|
18
|
+
'audit-web.md',
|
|
19
|
+
'audit-mobile.md',
|
|
20
|
+
'audit-api.md',
|
|
21
|
+
'audit-cloud.md',
|
|
22
|
+
'audit-quick.md',
|
|
23
|
+
'audit-report.md',
|
|
24
|
+
'audit-help.md',
|
|
25
|
+
];
|
|
26
|
+
const H = homedir();
|
|
27
|
+
function j(...parts) {
|
|
28
|
+
return join(H, ...parts);
|
|
29
|
+
}
|
|
30
|
+
export const AGENT_CONFIG = {
|
|
31
|
+
"opencode": {
|
|
32
|
+
displayName: "OpenCode",
|
|
33
|
+
skillPaths: [j(".agents", "skills", "cyberaudit"), j(".config", "opencode", "skills", "cyberaudit")],
|
|
34
|
+
commandPaths: [j(".config", "opencode", "commands")],
|
|
35
|
+
},
|
|
36
|
+
"claude-code": {
|
|
37
|
+
displayName: "Claude Code",
|
|
38
|
+
skillPaths: [j(".claude", "skills", "cyberaudit")],
|
|
39
|
+
commandPaths: [j(".claude", "commands")],
|
|
40
|
+
},
|
|
41
|
+
"cursor": {
|
|
42
|
+
displayName: "Cursor",
|
|
43
|
+
skillPaths: [j(".cursor", "skills", "cyberaudit")],
|
|
44
|
+
commandPaths: [j(".cursor", "commands")],
|
|
45
|
+
mcpPath: j(".cursor", "mcp.json"),
|
|
46
|
+
},
|
|
47
|
+
"windsurf": {
|
|
48
|
+
displayName: "Windsurf",
|
|
49
|
+
skillPaths: [j(".windsurf", "skills", "cyberaudit"), j(".codeium", "windsurf", "skills", "cyberaudit")],
|
|
50
|
+
commandPaths: [j(".windsurf", "commands")],
|
|
51
|
+
workflowPaths: [j(".windsurf", "workflows")],
|
|
52
|
+
},
|
|
53
|
+
"antigravity": {
|
|
54
|
+
displayName: "Antigravity",
|
|
55
|
+
skillPaths: [j(".gemini", "antigravity", "skills", "cyberaudit"), j(".agent", "skills", "cyberaudit")],
|
|
56
|
+
commandPaths: [j(".gemini", "antigravity", "commands"), j(".agent", "commands")],
|
|
57
|
+
workflowPaths: [j(".agent", "workflows")],
|
|
58
|
+
},
|
|
59
|
+
"antigravity-cli": {
|
|
60
|
+
displayName: "Antigravity CLI",
|
|
61
|
+
skillPaths: [j(".gemini", "antigravity-cli", "skills", "cyberaudit")],
|
|
62
|
+
commandPaths: [j(".gemini", "antigravity-cli", "commands")],
|
|
63
|
+
},
|
|
64
|
+
"copilot": {
|
|
65
|
+
displayName: "GitHub Copilot",
|
|
66
|
+
skillPaths: [j(".copilot", "skills", "cyberaudit"), j(".github", "copilot", "skills", "cyberaudit")],
|
|
67
|
+
commandPaths: [j(".copilot", "commands"), j(".github", "copilot", "commands")],
|
|
68
|
+
},
|
|
69
|
+
"kiro": {
|
|
70
|
+
displayName: "Kiro",
|
|
71
|
+
skillPaths: [j(".kiro", "skills", "cyberaudit")],
|
|
72
|
+
commandPaths: [j(".kiro", "commands")],
|
|
73
|
+
},
|
|
74
|
+
"codex": {
|
|
75
|
+
displayName: "Codex CLI",
|
|
76
|
+
skillPaths: [j(".codex", "skills", "cyberaudit")],
|
|
77
|
+
commandPaths: [j(".codex", "commands")],
|
|
78
|
+
},
|
|
79
|
+
"qoder": {
|
|
80
|
+
displayName: "Qoder",
|
|
81
|
+
skillPaths: [j(".qoder", "skills", "cyberaudit")],
|
|
82
|
+
commandPaths: [j(".qoder", "commands")],
|
|
83
|
+
},
|
|
84
|
+
"roocode": {
|
|
85
|
+
displayName: "Roo Code",
|
|
86
|
+
skillPaths: [j(".roo", "skills", "cyberaudit"), j(".roocode", "skills", "cyberaudit")],
|
|
87
|
+
commandPaths: [j(".roo", "commands"), j(".roocode", "commands")],
|
|
88
|
+
},
|
|
89
|
+
"gemini": {
|
|
90
|
+
displayName: "Gemini CLI",
|
|
91
|
+
skillPaths: [j(".gemini", "skills", "cyberaudit")],
|
|
92
|
+
commandPaths: [j(".gemini", "commands")],
|
|
93
|
+
},
|
|
94
|
+
"trae": {
|
|
95
|
+
displayName: "Trae",
|
|
96
|
+
skillPaths: [j(".trae", "skills", "cyberaudit")],
|
|
97
|
+
commandPaths: [j(".trae", "commands")],
|
|
98
|
+
},
|
|
99
|
+
"continue": {
|
|
100
|
+
displayName: "Continue",
|
|
101
|
+
skillPaths: [j(".continue", "skills", "cyberaudit")],
|
|
102
|
+
commandPaths: [j(".continue", "commands")],
|
|
103
|
+
},
|
|
104
|
+
"codebuddy": {
|
|
105
|
+
displayName: "CodeBuddy",
|
|
106
|
+
skillPaths: [j(".codebuddy", "skills", "cyberaudit")],
|
|
107
|
+
commandPaths: [j(".codebuddy", "commands")],
|
|
108
|
+
},
|
|
109
|
+
"droid": {
|
|
110
|
+
displayName: "Droid (Factory)",
|
|
111
|
+
skillPaths: [j(".factory", "skills", "cyberaudit"), j(".factory", "droid", "skills", "cyberaudit")],
|
|
112
|
+
commandPaths: [j(".factory", "commands"), j(".factory", "droid", "commands")],
|
|
113
|
+
},
|
|
114
|
+
"kilocode": {
|
|
115
|
+
displayName: "KiloCode",
|
|
116
|
+
skillPaths: [j(".kilocode", "skills", "cyberaudit"), j(".kilo", "skills", "cyberaudit")],
|
|
117
|
+
commandPaths: [j(".kilocode", "commands"), j(".kilo", "commands")],
|
|
118
|
+
},
|
|
119
|
+
"warp": {
|
|
120
|
+
displayName: "Warp",
|
|
121
|
+
skillPaths: [j(".warp", "skills", "cyberaudit")],
|
|
122
|
+
commandPaths: [j(".warp", "commands")],
|
|
123
|
+
},
|
|
124
|
+
"augment": {
|
|
125
|
+
displayName: "Augment",
|
|
126
|
+
skillPaths: [j(".augment", "skills", "cyberaudit")],
|
|
127
|
+
commandPaths: [j(".augment", "commands")],
|
|
128
|
+
},
|
|
129
|
+
"codewhale": {
|
|
130
|
+
displayName: "CodeWhale",
|
|
131
|
+
skillPaths: [j(".codewhale", "skills", "cyberaudit")],
|
|
132
|
+
commandPaths: [j(".codewhale", "commands")],
|
|
133
|
+
},
|
|
134
|
+
"cline": {
|
|
135
|
+
displayName: "Cline",
|
|
136
|
+
skillPaths: [j(".cline", "skills", "cyberaudit")],
|
|
137
|
+
commandPaths: [j(".cline", "commands")],
|
|
138
|
+
},
|
|
139
|
+
"aider": {
|
|
140
|
+
displayName: "Aider",
|
|
141
|
+
skillPaths: [j(".aider", "skills", "cyberaudit")],
|
|
142
|
+
commandPaths: [j(".aider", "commands")],
|
|
143
|
+
},
|
|
20
144
|
};
|
|
145
|
+
export const AGENT_TARGETS = Object.fromEntries(Object.entries(AGENT_CONFIG).map(([k, v]) => [k, v.skillPaths]));
|
|
146
|
+
function isSafeInsideHome(p) {
|
|
147
|
+
const home = resolve(H);
|
|
148
|
+
const resolved = resolve(p);
|
|
149
|
+
return resolved.startsWith(home);
|
|
150
|
+
}
|
|
151
|
+
function isSafeInsideCwd(p, cwd) {
|
|
152
|
+
return resolve(p).startsWith(resolve(cwd));
|
|
153
|
+
}
|
|
154
|
+
function isSafeSkillPath(p, mode = 'global', cwd = process.cwd()) {
|
|
155
|
+
if (mode === 'local') {
|
|
156
|
+
return isSafeInsideCwd(p, cwd) && p.includes("cyberaudit");
|
|
157
|
+
}
|
|
158
|
+
return isSafeInsideHome(p) && p.includes("cyberaudit");
|
|
159
|
+
}
|
|
160
|
+
function isSafeCommandPath(p, mode = 'global', cwd = process.cwd()) {
|
|
161
|
+
const inside = mode === 'local' ? isSafeInsideCwd(p, cwd) : isSafeInsideHome(p);
|
|
162
|
+
if (!inside)
|
|
163
|
+
return false;
|
|
164
|
+
return p.includes("commands") || p.includes("workflows") || p.includes("skills");
|
|
165
|
+
}
|
|
166
|
+
function toLocalPath(globalPath, cwd) {
|
|
167
|
+
// ~/.claude/skills/cyberaudit -> ./.claude/skills/cyberaudit
|
|
168
|
+
try {
|
|
169
|
+
const rel = relative(H, globalPath);
|
|
170
|
+
if (rel.startsWith('..'))
|
|
171
|
+
return join(cwd, globalPath.split('/').pop() || '');
|
|
172
|
+
return join(cwd, rel);
|
|
173
|
+
}
|
|
174
|
+
catch {
|
|
175
|
+
return join(cwd, '.' + globalPath.split('/').pop());
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
function getPathsForMode(agent, mode, cwd = process.cwd()) {
|
|
179
|
+
const cfg = AGENT_CONFIG[agent];
|
|
180
|
+
if (mode === 'global')
|
|
181
|
+
return cfg;
|
|
182
|
+
// local: transform each path to be relative to cwd
|
|
183
|
+
return {
|
|
184
|
+
displayName: cfg.displayName,
|
|
185
|
+
skillPaths: cfg.skillPaths.map(p => toLocalPath(p, cwd)),
|
|
186
|
+
commandPaths: cfg.commandPaths.map(p => toLocalPath(p, cwd)),
|
|
187
|
+
workflowPaths: cfg.workflowPaths?.map(p => toLocalPath(p, cwd)),
|
|
188
|
+
mcpPath: cfg.mcpPath ? toLocalPath(cfg.mcpPath, cwd) : undefined,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
21
191
|
function detectInstalledAgents() {
|
|
22
192
|
const found = [];
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
193
|
+
for (const [agent, cfg] of Object.entries(AGENT_CONFIG)) {
|
|
194
|
+
const checkPaths = [
|
|
195
|
+
...cfg.skillPaths.map(p => dirname(p)),
|
|
196
|
+
...cfg.commandPaths.map(p => p),
|
|
197
|
+
...(cfg.workflowPaths || []).map(p => p),
|
|
198
|
+
...(cfg.mcpPath ? [dirname(cfg.mcpPath)] : []),
|
|
199
|
+
];
|
|
200
|
+
if (checkPaths.some(parent => existsSync(parent))) {
|
|
28
201
|
found.push(agent);
|
|
29
202
|
}
|
|
30
203
|
}
|
|
31
|
-
if (existsSync(
|
|
204
|
+
if (existsSync(j(".agents")) && !found.includes("opencode"))
|
|
205
|
+
found.push("opencode");
|
|
206
|
+
if (existsSync(j(".claude")) && !found.includes("claude-code"))
|
|
207
|
+
found.push("claude-code");
|
|
208
|
+
if (existsSync(j(".cursor")) && !found.includes("cursor"))
|
|
32
209
|
found.push("cursor");
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
210
|
+
return [...new Set(found)];
|
|
211
|
+
}
|
|
212
|
+
function detectByHomeFolders() {
|
|
213
|
+
const mapping = {
|
|
214
|
+
".agents": "opencode",
|
|
215
|
+
".claude": "claude-code",
|
|
216
|
+
".cursor": "cursor",
|
|
217
|
+
".windsurf": "windsurf",
|
|
218
|
+
".codeium": "windsurf",
|
|
219
|
+
".agent": "antigravity",
|
|
220
|
+
".copilot": "copilot",
|
|
221
|
+
".kiro": "kiro",
|
|
222
|
+
".codex": "codex",
|
|
223
|
+
".qoder": "qoder",
|
|
224
|
+
".roo": "roocode",
|
|
225
|
+
".roocode": "roocode",
|
|
226
|
+
".gemini": "gemini",
|
|
227
|
+
".trae": "trae",
|
|
228
|
+
".continue": "continue",
|
|
229
|
+
".codebuddy": "codebuddy",
|
|
230
|
+
".factory": "droid",
|
|
231
|
+
".kilocode": "kilocode",
|
|
232
|
+
".kilo": "kilocode",
|
|
233
|
+
".warp": "warp",
|
|
234
|
+
".augment": "augment",
|
|
235
|
+
".codewhale": "codewhale",
|
|
236
|
+
".cline": "cline",
|
|
237
|
+
".aider": "aider",
|
|
238
|
+
};
|
|
239
|
+
const found = [];
|
|
240
|
+
for (const [folder, agent] of Object.entries(mapping)) {
|
|
241
|
+
if (existsSync(j(folder))) {
|
|
242
|
+
if (!found.includes(agent))
|
|
243
|
+
found.push(agent);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
if (existsSync(j(".config", "opencode")) && !found.includes("opencode"))
|
|
247
|
+
found.push("opencode");
|
|
39
248
|
return found;
|
|
40
249
|
}
|
|
41
|
-
function
|
|
42
|
-
if (!existsSync(
|
|
43
|
-
|
|
44
|
-
|
|
250
|
+
function installDir(src, dst, mode = 'global', cwd = process.cwd(), allowCommandPath = false) {
|
|
251
|
+
if (!existsSync(src))
|
|
252
|
+
throw new Error(`Source not found: ${src}`);
|
|
253
|
+
const safeCheck = allowCommandPath
|
|
254
|
+
? (p) => isSafeCommandPath(p, mode, cwd)
|
|
255
|
+
: (p) => isSafeSkillPath(p, mode, cwd);
|
|
256
|
+
if (!safeCheck(dst)) {
|
|
257
|
+
console.error(`ā Unsafe destination blocked: ${dst} (mode=${mode})`);
|
|
258
|
+
throw new Error(`Unsafe path: ${dst}`);
|
|
259
|
+
}
|
|
260
|
+
if (existsSync(dst)) {
|
|
261
|
+
console.log(` ā»ļø Cleaning previous install at ${dst}`);
|
|
262
|
+
rmSync(dst, { recursive: true, force: true });
|
|
263
|
+
}
|
|
264
|
+
mkdirSync(dirname(dst), { recursive: true });
|
|
265
|
+
cpSync(src, dst, { recursive: true });
|
|
266
|
+
}
|
|
267
|
+
function installCommands(commandFiles, targetDir, dryRun, agentName, mode = 'global', cwd = process.cwd()) {
|
|
268
|
+
if (!isSafeCommandPath(targetDir, mode, cwd)) {
|
|
269
|
+
console.error(` ā Unsafe command destination blocked: ${targetDir}`);
|
|
270
|
+
return;
|
|
45
271
|
}
|
|
46
272
|
if (dryRun) {
|
|
47
|
-
console.log(` ā Would install to ${targetDir} (--dry-run)`);
|
|
48
|
-
return
|
|
273
|
+
console.log(` ā Would install ${commandFiles.length} commands to ${targetDir} (--dry-run) [${mode}]`);
|
|
274
|
+
return;
|
|
49
275
|
}
|
|
50
|
-
// Install/update skill files
|
|
51
276
|
mkdirSync(targetDir, { recursive: true });
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
const cmdSrc = join(PKG_ROOT, "skills", "cyberaudit", "commands");
|
|
59
|
-
if (existsSync(cmdSrc)) {
|
|
60
|
-
mkdirSync(cmdDir, { recursive: true });
|
|
61
|
-
// Remove stale colon-named files (pre-v3.1.3)
|
|
62
|
-
for (const f of readdirSync(cmdDir)) {
|
|
63
|
-
if (typeof f === "string" && f.startsWith("audit:") && f.endsWith(".md")) {
|
|
277
|
+
try {
|
|
278
|
+
if (existsSync(targetDir)) {
|
|
279
|
+
const stale = readdirSync(targetDir).filter(f => typeof f === "string" && f.startsWith("audit") && f.endsWith(".md"));
|
|
280
|
+
if (stale.length > 0) {
|
|
281
|
+
console.log(` ā»ļø Removing ${stale.length} stale audit commands from ${agentName}: ${stale.join(", ")}`);
|
|
282
|
+
for (const f of stale) {
|
|
64
283
|
try {
|
|
65
|
-
unlinkSync(join(
|
|
284
|
+
unlinkSync(join(targetDir, f));
|
|
66
285
|
}
|
|
67
286
|
catch { }
|
|
68
287
|
}
|
|
69
288
|
}
|
|
70
|
-
cpSync(cmdSrc, cmdDir, { recursive: true });
|
|
71
|
-
console.log(` ā Commands installed to opencode`);
|
|
72
289
|
}
|
|
73
|
-
const skillDir = join(opencodeConf, "skills", "cyberaudit");
|
|
74
|
-
mkdirSync(skillDir, { recursive: true });
|
|
75
|
-
cpSync(SKILL_SRC, skillDir, { recursive: true });
|
|
76
|
-
console.log(` ā Skill installed to opencode`);
|
|
77
290
|
}
|
|
78
|
-
|
|
291
|
+
catch { }
|
|
292
|
+
for (const fileName of commandFiles) {
|
|
293
|
+
const src = join(COMMANDS_SRC, fileName);
|
|
294
|
+
if (!existsSync(src))
|
|
295
|
+
continue;
|
|
296
|
+
const dst = join(targetDir, fileName);
|
|
297
|
+
try {
|
|
298
|
+
mkdirSync(dirname(dst), { recursive: true });
|
|
299
|
+
copyFileSync(src, dst);
|
|
300
|
+
}
|
|
301
|
+
catch (e) {
|
|
302
|
+
console.error(` ā Failed to copy command ${fileName} to ${targetDir}: ${e.message}`);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
console.log(` ā Commands (${commandFiles.length}) installed to ${agentName} (${targetDir}) [${mode}] ā "/" will show ${commandFiles.length} main commands`);
|
|
306
|
+
}
|
|
307
|
+
function installSkillForAgent(agent, dryRun, mode = 'global', cwd = process.cwd()) {
|
|
308
|
+
const cfg = getPathsForMode(agent, mode, cwd);
|
|
309
|
+
if (!cfg)
|
|
310
|
+
return false;
|
|
311
|
+
if (!existsSync(SKILL_SRC)) {
|
|
312
|
+
console.error(`ā Skill source not found at ${SKILL_SRC}.`);
|
|
313
|
+
return false;
|
|
314
|
+
}
|
|
315
|
+
if (dryRun) {
|
|
316
|
+
for (const sp of cfg.skillPaths)
|
|
317
|
+
console.log(` ā Would install skill to ${sp} (--dry-run) [${mode}]`);
|
|
318
|
+
for (const cp of cfg.commandPaths)
|
|
319
|
+
console.log(` ā Would install ${MAIN_COMMANDS.length} main commands to ${cp} (--dry-run) [${mode}]`);
|
|
320
|
+
if (cfg.workflowPaths)
|
|
321
|
+
for (const wp of cfg.workflowPaths)
|
|
322
|
+
console.log(` ā Would install ${MAIN_COMMANDS.length} workflows to ${wp} (--dry-run) [${mode}]`);
|
|
323
|
+
if (cfg.mcpPath)
|
|
324
|
+
console.log(` ā Would configure MCP at ${cfg.mcpPath} (--dry-run) [${mode}]`);
|
|
325
|
+
return true;
|
|
326
|
+
}
|
|
327
|
+
let ok = false;
|
|
328
|
+
for (const skillPath of cfg.skillPaths) {
|
|
329
|
+
try {
|
|
330
|
+
installDir(SKILL_SRC, skillPath, mode, cwd, false);
|
|
331
|
+
console.log(` ā Skill installed at ${skillPath} [${mode}]`);
|
|
332
|
+
ok = true;
|
|
333
|
+
}
|
|
334
|
+
catch (e) {
|
|
335
|
+
console.error(` ā Failed skill install at ${skillPath}: ${e.message}`);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
const isOpenCode = agent === "opencode";
|
|
339
|
+
const commandsToInstall = isOpenCode ? readdirSync(COMMANDS_SRC).filter(f => f.endsWith('.md')) : MAIN_COMMANDS;
|
|
340
|
+
for (const cmdPath of cfg.commandPaths) {
|
|
341
|
+
try {
|
|
342
|
+
installCommands(commandsToInstall, cmdPath, dryRun, agent, mode, cwd);
|
|
343
|
+
ok = true;
|
|
344
|
+
}
|
|
345
|
+
catch (e) {
|
|
346
|
+
console.error(` ā Failed commands install at ${cmdPath}: ${e.message}`);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
if (cfg.workflowPaths) {
|
|
350
|
+
for (const wfPath of cfg.workflowPaths) {
|
|
351
|
+
try {
|
|
352
|
+
installCommands(commandsToInstall, wfPath, dryRun, agent, mode, cwd);
|
|
353
|
+
ok = true;
|
|
354
|
+
}
|
|
355
|
+
catch { }
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
if (agent === "cursor") {
|
|
359
|
+
const cursorOk = installForCursor(dryRun, mode, cwd);
|
|
360
|
+
ok = ok || cursorOk;
|
|
361
|
+
}
|
|
362
|
+
return ok;
|
|
79
363
|
}
|
|
80
|
-
function installForCursor(dryRun) {
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
|
|
364
|
+
function installForCursor(dryRun, mode = 'global', cwd = process.cwd()) {
|
|
365
|
+
const baseDir = mode === 'local' ? cwd : H;
|
|
366
|
+
const cursorDir = mode === 'local' ? join(cwd, ".cursor") : join(H, ".cursor");
|
|
367
|
+
// For local, .cursor folder may not exist, but we should still create mcp.json
|
|
368
|
+
if (mode === 'global' && !existsSync(cursorDir)) {
|
|
369
|
+
console.log(" ~ Cursor not found (no ~/.cursor/) ā skipping MCP, but commands may still install");
|
|
84
370
|
return false;
|
|
85
371
|
}
|
|
86
372
|
const mcpPath = join(cursorDir, "mcp.json");
|
|
87
373
|
let mcpConfig = { mcpServers: {} };
|
|
88
374
|
if (existsSync(mcpPath)) {
|
|
89
|
-
|
|
375
|
+
try {
|
|
376
|
+
const raw = readFileSync(mcpPath, "utf-8");
|
|
377
|
+
if (raw.trim().length > 0)
|
|
378
|
+
mcpConfig = JSON.parse(raw);
|
|
379
|
+
}
|
|
380
|
+
catch (e) {
|
|
381
|
+
console.error(` ā Cursor mcp.json is corrupted (${e.message}) ā backing up`);
|
|
382
|
+
if (!dryRun) {
|
|
383
|
+
try {
|
|
384
|
+
const bak = `${mcpPath}.bak.${Date.now()}`;
|
|
385
|
+
copyFileSync(mcpPath, bak);
|
|
386
|
+
console.log(` ā³ Backup saved to ${bak}`);
|
|
387
|
+
}
|
|
388
|
+
catch { }
|
|
389
|
+
}
|
|
390
|
+
mcpConfig = { mcpServers: {} };
|
|
391
|
+
}
|
|
90
392
|
}
|
|
91
393
|
if (mcpConfig.mcpServers?.["cyberaudit-skill"]) {
|
|
92
|
-
console.log(` ā Cursor MCP already configured`);
|
|
394
|
+
console.log(` ā Cursor MCP already configured (${mcpPath}) [${mode}]`);
|
|
93
395
|
return true;
|
|
94
396
|
}
|
|
95
397
|
if (dryRun) {
|
|
96
|
-
console.log(` ā Would add Cursor MCP entry to ${mcpPath}`);
|
|
398
|
+
console.log(` ā Would add Cursor MCP entry to ${mcpPath} [${mode}]`);
|
|
97
399
|
return true;
|
|
98
400
|
}
|
|
401
|
+
if (existsSync(mcpPath)) {
|
|
402
|
+
try {
|
|
403
|
+
copyFileSync(mcpPath, `${mcpPath}.bak`);
|
|
404
|
+
}
|
|
405
|
+
catch { }
|
|
406
|
+
}
|
|
99
407
|
mcpConfig.mcpServers = mcpConfig.mcpServers || {};
|
|
100
408
|
mcpConfig.mcpServers["cyberaudit-skill"] = {
|
|
101
409
|
command: "npx",
|
|
102
410
|
args: ["-y", "cyberaudit-skill", "serve"],
|
|
103
411
|
};
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
const paths = AGENT_TARGETS[agent];
|
|
114
|
-
if (!paths?.length)
|
|
115
|
-
return false;
|
|
116
|
-
return installSkill(paths[0], agent, dryRun);
|
|
117
|
-
}
|
|
412
|
+
try {
|
|
413
|
+
mkdirSync(dirname(mcpPath), { recursive: true });
|
|
414
|
+
writeFileSync(mcpPath, JSON.stringify(mcpConfig, null, 2));
|
|
415
|
+
console.log(` ā Added Cursor MCP entry to ${mcpPath} [${mode}]`);
|
|
416
|
+
return true;
|
|
417
|
+
}
|
|
418
|
+
catch (e) {
|
|
419
|
+
console.error(` ā Failed to write Cursor MCP config: ${e.message}`);
|
|
420
|
+
return false;
|
|
118
421
|
}
|
|
119
422
|
}
|
|
120
423
|
async function main() {
|
|
121
424
|
const program = new Command();
|
|
122
425
|
program
|
|
123
426
|
.name("cyberaudit-skill")
|
|
124
|
-
.description("CyberAudit Skill ā
|
|
125
|
-
.version(
|
|
427
|
+
.description("CyberAudit Skill ā 22 agents like ui-ux-pro-max-skill, 8 main commands on '/'")
|
|
428
|
+
.version(VERSION);
|
|
126
429
|
program
|
|
127
430
|
.command("install")
|
|
128
|
-
.description("Install CyberAudit Skill for AI agent(s)")
|
|
129
|
-
.option("-a, --agent <agent>", "Target
|
|
431
|
+
.description("Install CyberAudit Skill for AI agent(s) ā supports 22 agents like ui-ux-pro-max-skill, with --global and --local modes")
|
|
432
|
+
.option("-a, --agent <agent>", "Target agent or all", "all")
|
|
130
433
|
.option("--dry-run", "Show changes without applying", false)
|
|
434
|
+
.option("--global", "Install globally to ~/ (default)", false)
|
|
435
|
+
.option("--local", "Install locally to ./ (project) like ui-ux-pro ā ensures '/' shows commands", false)
|
|
131
436
|
.action((options) => {
|
|
132
437
|
const dryRun = !!options.dryRun;
|
|
133
438
|
const agentOpt = (options.agent || "all").toLowerCase();
|
|
439
|
+
// Determine mode: if --local flag, local; else if --global or no flag, global (backward compat)
|
|
440
|
+
let mode = 'global';
|
|
441
|
+
if (options.local)
|
|
442
|
+
mode = 'local';
|
|
443
|
+
else if (options.global)
|
|
444
|
+
mode = 'global';
|
|
445
|
+
// If neither, default global for backward compat, but hint about local
|
|
446
|
+
const cwd = process.cwd();
|
|
134
447
|
let agents;
|
|
135
448
|
if (agentOpt === "all") {
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
449
|
+
if (mode === 'local') {
|
|
450
|
+
// For local, install to all agents in project (like ui-ux-pro init --ai all)
|
|
451
|
+
agents = Object.keys(AGENT_CONFIG);
|
|
452
|
+
console.log(`Installing locally to project ${cwd} for all ${agents.length} agents (like ui-ux-pro)`);
|
|
453
|
+
}
|
|
454
|
+
else {
|
|
455
|
+
const byFolder = detectByHomeFolders();
|
|
456
|
+
const byLegacy = detectInstalledAgents();
|
|
457
|
+
agents = [...new Set([...byFolder, ...byLegacy])];
|
|
458
|
+
if (agents.length === 0) {
|
|
459
|
+
console.log("No supported AI agents detected globally. Checked:");
|
|
460
|
+
console.log(" ~/.claude, ~/.cursor, ~/.windsurf, ~/.agent, ~/.copilot, ~/.kiro, ~/.codex, ~/.qoder, ~/.roo, ~/.gemini, ~/.trae, ~/.agents, ~/.continue, ~/.codebuddy, ~/.factory, ~/.kilocode, ~/.warp, ~/.augment, ~/.codewhale, ~/.cline, ~/.aider");
|
|
461
|
+
console.log("\nTry: --agent claude-code --local (project-local, ensures '/' shows)");
|
|
462
|
+
console.log("Or: --agent all --local (like ui-ux-pro init --ai all)");
|
|
463
|
+
process.exit(1);
|
|
464
|
+
}
|
|
465
|
+
console.log(`Detected agents (${agents.length}): ${agents.join(", ")} [global mode]`);
|
|
140
466
|
}
|
|
141
467
|
}
|
|
142
468
|
else {
|
|
143
|
-
const valid =
|
|
469
|
+
const valid = Object.keys(AGENT_CONFIG);
|
|
144
470
|
if (!valid.includes(agentOpt)) {
|
|
145
|
-
console.error(`Unknown agent: "${agentOpt}". Valid: ${valid.join(", ")}`);
|
|
471
|
+
console.error(`Unknown agent: "${agentOpt}". Valid: ${valid.join(", ")}, all`);
|
|
146
472
|
process.exit(1);
|
|
147
473
|
}
|
|
148
474
|
agents = [agentOpt];
|
|
149
475
|
}
|
|
150
476
|
const label = dryRun ? " (dry run)" : "";
|
|
151
|
-
console.log(`\nāāā CyberAudit Installation${label} āāā\n`);
|
|
477
|
+
console.log(`\nāāā CyberAudit v${VERSION} Installation${label} ā ${agents.length} agents [${mode}] ā like ui-ux-pro āāā\n`);
|
|
478
|
+
console.log(`Main commands for "/" menu: ${MAIN_COMMANDS.map(f => f.replace('.md', '')).join(', ')}\n`);
|
|
479
|
+
if (mode === 'local')
|
|
480
|
+
console.log(`Project: ${cwd}\n`);
|
|
152
481
|
let ok = 0, fail = 0;
|
|
153
482
|
for (const agent of agents) {
|
|
154
|
-
const
|
|
155
|
-
|
|
483
|
+
const cfg = getPathsForMode(agent, mode, cwd);
|
|
484
|
+
console.log(`\nā ${cfg.displayName} (${agent}) [${mode}]:`);
|
|
485
|
+
if (installSkillForAgent(agent, dryRun, mode, cwd))
|
|
156
486
|
ok++;
|
|
157
487
|
else
|
|
158
488
|
fail++;
|
|
159
489
|
}
|
|
160
|
-
console.log(`\nDone. ${ok} configured, ${fail} skipped.\n`);
|
|
161
|
-
if (!dryRun)
|
|
162
|
-
console.log(
|
|
490
|
+
console.log(`\nDone. ${ok} configured, ${fail} skipped/failed [${mode}].\n`);
|
|
491
|
+
if (!dryRun && ok > 0) {
|
|
492
|
+
console.log(`Verify: npx -y cyberaudit-skill list${mode === 'local' ? ' --local' : ''}`);
|
|
493
|
+
console.log(`Tip: Type "/" in your agent ā you should see: ${MAIN_COMMANDS.slice(0, 4).map(f => '/' + f.replace('.md', '').replace('audit-', 'audit:')).join(', ')}... (${MAIN_COMMANDS.length} commands) [${mode}]\n`);
|
|
494
|
+
if (mode === 'global') {
|
|
495
|
+
console.log(`For 100% guarantee like ui-ux-pro, also run: npx -y cyberaudit-skill install --agent all --local\n`);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
163
498
|
});
|
|
164
499
|
program
|
|
165
500
|
.command("list")
|
|
166
|
-
.description("List audits and installed agents")
|
|
167
|
-
.
|
|
168
|
-
|
|
501
|
+
.description("List audits and installed agents (22 agents)")
|
|
502
|
+
.option("--local", "List project-local installation (./) instead of global (~/)", false)
|
|
503
|
+
.action((opts) => {
|
|
504
|
+
const mode = opts.local ? 'local' : 'global';
|
|
505
|
+
const cwd = process.cwd();
|
|
506
|
+
console.log(`\nāāā CyberAudit v${VERSION} ā Available Audits [${mode}] āāā\n`);
|
|
169
507
|
console.log(" cyberaudit-web OWASP web app security audit");
|
|
170
508
|
console.log(" cyberaudit-mobile OWASP mobile app security audit");
|
|
171
509
|
console.log(" cyberaudit-api API security audit");
|
|
172
510
|
console.log(" cyberaudit-cloud Cloud config audit");
|
|
173
|
-
console.log(" cyberaudit-full Full stack
|
|
174
|
-
console.log(" cyberaudit-quick Quick
|
|
175
|
-
console.log(
|
|
176
|
-
for (const [
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
511
|
+
console.log(" cyberaudit-full Full stack");
|
|
512
|
+
console.log(" cyberaudit-quick Quick scan (deterministic)\n");
|
|
513
|
+
console.log(`āāā Supported Agents (${Object.keys(AGENT_CONFIG).length}) [${mode}] āāā\n`);
|
|
514
|
+
for (const [agentKey, cfgGlobal] of Object.entries(AGENT_CONFIG)) {
|
|
515
|
+
const cfg = getPathsForMode(agentKey, mode, cwd);
|
|
516
|
+
let installed = false;
|
|
517
|
+
let loc = "";
|
|
518
|
+
for (const sp of cfg.skillPaths) {
|
|
519
|
+
if (existsSync(sp)) {
|
|
520
|
+
installed = true;
|
|
521
|
+
loc = sp;
|
|
522
|
+
break;
|
|
523
|
+
}
|
|
181
524
|
}
|
|
182
|
-
|
|
183
|
-
const
|
|
184
|
-
|
|
185
|
-
|
|
525
|
+
if (!installed) {
|
|
526
|
+
for (const cp of cfg.commandPaths) {
|
|
527
|
+
if (existsSync(cp)) {
|
|
528
|
+
try {
|
|
529
|
+
const files = readdirSync(cp).filter(f => f.startsWith('audit'));
|
|
530
|
+
if (files.length > 0) {
|
|
531
|
+
installed = true;
|
|
532
|
+
loc = `${cp} (${files.length} cmds)`;
|
|
533
|
+
break;
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
catch { }
|
|
537
|
+
}
|
|
538
|
+
}
|
|
186
539
|
}
|
|
540
|
+
if (!installed && cfg.mcpPath && existsSync(cfg.mcpPath)) {
|
|
541
|
+
try {
|
|
542
|
+
if (readFileSync(cfg.mcpPath, 'utf-8').includes('cyberaudit-skill')) {
|
|
543
|
+
installed = true;
|
|
544
|
+
loc = cfg.mcpPath;
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
catch { }
|
|
548
|
+
}
|
|
549
|
+
console.log(` ${installed ? "ā" : "ā"} ${cfg.displayName.padEnd(20)} (${agentKey})${installed ? ` ā ${loc}` : ""}`);
|
|
187
550
|
}
|
|
188
|
-
console.log();
|
|
551
|
+
console.log(`\nMain "/" commands: ${MAIN_COMMANDS.map(f => f.replace('.md', '')).join(', ')} [${mode}]`);
|
|
552
|
+
console.log(`Total commands available: ${readdirSync(COMMANDS_SRC).length} (8 main shown on "/")\n`);
|
|
189
553
|
});
|
|
190
554
|
program
|
|
191
|
-
.command("
|
|
192
|
-
.description("
|
|
193
|
-
.
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
tools: [
|
|
203
|
-
{
|
|
204
|
-
name: "cyberaudit-web",
|
|
205
|
-
description: "Web app security audit (OWASP Top 10)",
|
|
206
|
-
inputSchema: {
|
|
207
|
-
type: "object",
|
|
208
|
-
properties: {
|
|
209
|
-
target: { type: "string", description: "URL or project path" },
|
|
210
|
-
depth: { type: "string", enum: ["quick", "standard", "deep"], default: "standard" },
|
|
211
|
-
},
|
|
212
|
-
},
|
|
213
|
-
},
|
|
214
|
-
{
|
|
215
|
-
name: "cyberaudit-mobile",
|
|
216
|
-
description: "Mobile app security audit (OWASP MASVS)",
|
|
217
|
-
inputSchema: {
|
|
218
|
-
type: "object",
|
|
219
|
-
properties: {
|
|
220
|
-
target: { type: "string", description: "App package or project path" },
|
|
221
|
-
platform: { type: "string", enum: ["android", "ios", "both"], default: "both" },
|
|
222
|
-
},
|
|
223
|
-
},
|
|
224
|
-
},
|
|
225
|
-
{
|
|
226
|
-
name: "cyberaudit-api",
|
|
227
|
-
description: "API security audit",
|
|
228
|
-
inputSchema: {
|
|
229
|
-
type: "object",
|
|
230
|
-
properties: {
|
|
231
|
-
target: { type: "string", description: "API endpoint or spec file" },
|
|
232
|
-
},
|
|
233
|
-
},
|
|
234
|
-
},
|
|
235
|
-
{
|
|
236
|
-
name: "cyberaudit-list",
|
|
237
|
-
description: "List all available audit types",
|
|
238
|
-
inputSchema: { type: "object", properties: {} },
|
|
239
|
-
},
|
|
240
|
-
],
|
|
241
|
-
};
|
|
242
|
-
function write(id, result) {
|
|
243
|
-
process.stdout.write(JSON.stringify({ jsonrpc: "2.0", id, ...result }) + "\n");
|
|
244
|
-
}
|
|
245
|
-
rl.on("line", (line) => {
|
|
555
|
+
.command("scan")
|
|
556
|
+
.description("Deterministic security scan (secrets, web vulns)")
|
|
557
|
+
.argument("[target]", "Path to scan", ".")
|
|
558
|
+
.option("--json", "Output JSON", false)
|
|
559
|
+
.option("--type <type>", "Scan type: secrets, web, all", "all")
|
|
560
|
+
.option("--ignore <patterns>", "Comma-separated ignore", "")
|
|
561
|
+
.action((target, options) => {
|
|
562
|
+
const scanTarget = resolve(target || ".");
|
|
563
|
+
const ignore = options.ignore ? options.ignore.split(',').map((s) => s.trim()).filter(Boolean) : [];
|
|
564
|
+
const type = (options.type || 'all').toLowerCase();
|
|
565
|
+
console.error(`[CyberAudit] Scanning ${scanTarget} [type=${type}]... (${VERSION})`);
|
|
246
566
|
try {
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
567
|
+
let allFindings = [];
|
|
568
|
+
let secretFindings = [];
|
|
569
|
+
let webFindings = [];
|
|
570
|
+
if (type === 'secrets' || type === 'all') {
|
|
571
|
+
secretFindings = scanSecrets(scanTarget, { ignore });
|
|
572
|
+
allFindings.push(...secretFindings.map(f => ({ ...f, scanner: 'secrets' })));
|
|
251
573
|
}
|
|
252
|
-
|
|
253
|
-
|
|
574
|
+
if (type === 'web' || type === 'all') {
|
|
575
|
+
webFindings = scanWeb(scanTarget);
|
|
576
|
+
allFindings.push(...webFindings.map(f => ({ ...f, scanner: 'web' })));
|
|
254
577
|
}
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
content: [{ type: "text", text: `[CyberAudit] "${msg.params.name}" called with ${JSON.stringify(msg.params.arguments)}\n\nRun the audit by loading the CyberAudit Skill.` }],
|
|
258
|
-
});
|
|
578
|
+
if (options.json) {
|
|
579
|
+
console.log(JSON.stringify({ target: scanTarget, version: VERSION, type, findings: allFindings }, null, 2));
|
|
259
580
|
}
|
|
260
581
|
else {
|
|
261
|
-
|
|
582
|
+
if (type === 'secrets' || type === 'all')
|
|
583
|
+
console.log(formatFindingsText(secretFindings, scanTarget));
|
|
584
|
+
if (type === 'web' || type === 'all')
|
|
585
|
+
console.log(formatWebFindingsText(webFindings, scanTarget));
|
|
586
|
+
if (allFindings.length > 0)
|
|
587
|
+
console.log(`\nš” Next: /audit:secrets, /audit:web or /audit`);
|
|
588
|
+
else
|
|
589
|
+
console.log(`\nā
No findings for type=${type} in ${scanTarget}`);
|
|
262
590
|
}
|
|
591
|
+
if (allFindings.some(f => f.severity === 'CRITICAL'))
|
|
592
|
+
process.exitCode = 1;
|
|
593
|
+
}
|
|
594
|
+
catch (e) {
|
|
595
|
+
console.error(`[CyberAudit] Scan failed: ${e.message}`);
|
|
596
|
+
process.exit(1);
|
|
263
597
|
}
|
|
264
|
-
catch { /* skip malformed */ }
|
|
265
598
|
});
|
|
599
|
+
program
|
|
600
|
+
.command("report")
|
|
601
|
+
.description("Generate deterministic security report")
|
|
602
|
+
.argument("[target]", "Target path or app name", ".")
|
|
603
|
+
.option("--input <file>", "JSON file from scan --json")
|
|
604
|
+
.option("--type <type>", "Report type: web|api|mobile|cloud|full", "web")
|
|
605
|
+
.option("--framework <fw>", "Framework name")
|
|
606
|
+
.option("--output <file>", "Output markdown file")
|
|
607
|
+
.option("--json", " Also emit JSON summary", false)
|
|
608
|
+
.action(async (target, options) => {
|
|
609
|
+
const { generateReport } = await import("./report/generator.js");
|
|
610
|
+
let findings = [];
|
|
611
|
+
let scanTarget = target || ".";
|
|
612
|
+
const reportType = (options.type || 'web').toLowerCase();
|
|
613
|
+
if (options.input) {
|
|
614
|
+
const inputPath = resolve(options.input);
|
|
615
|
+
if (!existsSync(inputPath)) {
|
|
616
|
+
console.error(`Input file not found: ${inputPath}`);
|
|
617
|
+
process.exit(1);
|
|
618
|
+
}
|
|
619
|
+
const raw = readFileSync(inputPath, 'utf-8');
|
|
620
|
+
const parsed = JSON.parse(raw);
|
|
621
|
+
findings = parsed.findings || parsed;
|
|
622
|
+
scanTarget = parsed.target || scanTarget;
|
|
623
|
+
}
|
|
624
|
+
else {
|
|
625
|
+
console.error(`[CyberAudit] No input file, running scan on ${scanTarget}...`);
|
|
626
|
+
const secrets = scanSecrets(resolve(scanTarget));
|
|
627
|
+
const web = scanWeb(resolve(scanTarget));
|
|
628
|
+
findings = [...secrets.map(f => ({ ...f, scanner: 'secrets' })), ...web.map(f => ({ ...f, scanner: 'web' }))];
|
|
629
|
+
}
|
|
630
|
+
const report = generateReport({ target: scanTarget, version: VERSION, type: reportType, findings, framework: options.framework });
|
|
631
|
+
if (options.output) {
|
|
632
|
+
const outPath = resolve(options.output);
|
|
633
|
+
writeFileSync(outPath, report.markdown, 'utf-8');
|
|
634
|
+
console.error(`[CyberAudit] Report written to ${outPath} | Score ${report.score}/100 ${report.verdict}`);
|
|
635
|
+
}
|
|
636
|
+
else
|
|
637
|
+
console.log(report.markdown);
|
|
638
|
+
if (options.json)
|
|
639
|
+
console.error(JSON.stringify({ score: report.score, verdict: report.verdict, dashboard: report.dashboard, owasp: report.owaspCompliance }, null, 2));
|
|
640
|
+
if (report.verdict === 'CRITICAL' || report.verdict === 'HIGH')
|
|
641
|
+
process.exitCode = 1;
|
|
642
|
+
});
|
|
643
|
+
program
|
|
644
|
+
.command("serve")
|
|
645
|
+
.description("Start CyberAudit MCP server (stdio)")
|
|
646
|
+
.action(() => {
|
|
647
|
+
console.error(`[CyberAudit] MCP server starting v${VERSION}...`);
|
|
648
|
+
startMcpServerImpl();
|
|
649
|
+
});
|
|
650
|
+
program.parse(process.argv);
|
|
266
651
|
}
|
|
267
652
|
main().catch((err) => {
|
|
268
653
|
console.error("[CyberAudit] Fatal error:", err);
|