cyberaudit-skill 3.1.5 → 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.
Files changed (91) hide show
  1. package/README.md +84 -25
  2. package/dist/cli.d.ts +10 -0
  3. package/dist/cli.js +552 -172
  4. package/dist/cli.test.d.ts +1 -0
  5. package/dist/cli.test.js +100 -0
  6. package/dist/mcp-server.d.ts +135 -1
  7. package/dist/mcp-server.js +185 -96
  8. package/dist/mcp-server.test.d.ts +1 -0
  9. package/dist/mcp-server.test.js +77 -0
  10. package/dist/report/generator.d.ts +2 -0
  11. package/dist/report/generator.js +260 -0
  12. package/dist/report/generator.test.d.ts +1 -0
  13. package/dist/report/generator.test.js +73 -0
  14. package/dist/report/types.d.ts +31 -0
  15. package/dist/report/types.js +1 -0
  16. package/dist/scanners/secrets.d.ts +32 -0
  17. package/dist/scanners/secrets.js +437 -0
  18. package/dist/scanners/secrets.test.d.ts +1 -0
  19. package/dist/scanners/secrets.test.js +96 -0
  20. package/dist/scanners/types.d.ts +22 -0
  21. package/dist/scanners/types.js +1 -0
  22. package/dist/scanners/web.d.ts +37 -0
  23. package/dist/scanners/web.js +327 -0
  24. package/dist/scanners/web.test.d.ts +1 -0
  25. package/dist/scanners/web.test.js +67 -0
  26. package/package.json +15 -3
  27. package/skills/cyberaudit/AGENT-BOOT.md +1 -1
  28. package/skills/cyberaudit/COMMANDS.md +11 -1
  29. package/skills/cyberaudit/INSTALL.md +7 -6
  30. package/skills/cyberaudit/README.md +20 -5
  31. package/skills/cyberaudit/SKILL.md +20 -5
  32. package/skills/cyberaudit/cloud/CLOUD-CHECKLIST.md +85 -0
  33. package/skills/cyberaudit/cloud/CLOUD-PHILOSOPHY.md +91 -0
  34. package/skills/cyberaudit/cloud/CLOUD-REMEDIATION-LIBRARY.md +145 -0
  35. package/skills/cyberaudit/commands/audit-angular.md +11 -0
  36. package/skills/cyberaudit/commands/audit-api-bola.md +11 -0
  37. package/skills/cyberaudit/commands/audit-api-bopla.md +11 -0
  38. package/skills/cyberaudit/commands/audit-api-graphql.md +11 -0
  39. package/skills/cyberaudit/commands/audit-api-inventory.md +11 -0
  40. package/skills/cyberaudit/commands/audit-api-rate-limit.md +11 -0
  41. package/skills/cyberaudit/commands/audit-api-rest.md +11 -0
  42. package/skills/cyberaudit/commands/audit-api-third-party.md +11 -0
  43. package/skills/cyberaudit/commands/audit-api-ws.md +11 -0
  44. package/skills/cyberaudit/commands/audit-auth-api.md +11 -0
  45. package/skills/cyberaudit/commands/audit-auth-mobile.md +11 -0
  46. package/skills/cyberaudit/commands/audit-auth.md +12 -0
  47. package/skills/cyberaudit/commands/audit-binary.md +11 -0
  48. package/skills/cyberaudit/commands/audit-cis.md +11 -0
  49. package/skills/cyberaudit/commands/audit-cloud-iam.md +11 -0
  50. package/skills/cyberaudit/commands/audit-cloud-network.md +11 -0
  51. package/skills/cyberaudit/commands/audit-cloud-s3.md +11 -0
  52. package/skills/cyberaudit/commands/audit-cloud.md +10 -0
  53. package/skills/cyberaudit/commands/audit-cors.md +11 -0
  54. package/skills/cyberaudit/commands/audit-crypto-mobile.md +11 -0
  55. package/skills/cyberaudit/commands/audit-crypto.md +10 -0
  56. package/skills/cyberaudit/commands/audit-csrf.md +11 -0
  57. package/skills/cyberaudit/commands/audit-deeplinks.md +11 -0
  58. package/skills/cyberaudit/commands/audit-deps.md +11 -0
  59. package/skills/cyberaudit/commands/audit-deserial.md +11 -0
  60. package/skills/cyberaudit/commands/audit-exec.md +11 -0
  61. package/skills/cyberaudit/commands/audit-expo.md +11 -0
  62. package/skills/cyberaudit/commands/audit-express.md +11 -0
  63. package/skills/cyberaudit/commands/audit-flutter.md +11 -0
  64. package/skills/cyberaudit/commands/audit-headers.md +11 -0
  65. package/skills/cyberaudit/commands/audit-hipaa.md +11 -0
  66. package/skills/cyberaudit/commands/audit-idor.md +11 -0
  67. package/skills/cyberaudit/commands/audit-injection.md +12 -0
  68. package/skills/cyberaudit/commands/audit-ionic.md +11 -0
  69. package/skills/cyberaudit/commands/audit-laravel.md +11 -0
  70. package/skills/cyberaudit/commands/audit-logic.md +11 -0
  71. package/skills/cyberaudit/commands/audit-masvs.md +11 -0
  72. package/skills/cyberaudit/commands/audit-nestjs.md +11 -0
  73. package/skills/cyberaudit/commands/audit-network.md +11 -0
  74. package/skills/cyberaudit/commands/audit-nextjs.md +11 -0
  75. package/skills/cyberaudit/commands/audit-owasp.md +11 -0
  76. package/skills/cyberaudit/commands/audit-pci.md +11 -0
  77. package/skills/cyberaudit/commands/audit-permissions.md +11 -0
  78. package/skills/cyberaudit/commands/audit-react-native.md +11 -0
  79. package/skills/cyberaudit/commands/audit-react.md +11 -0
  80. package/skills/cyberaudit/commands/audit-rgpd.md +11 -0
  81. package/skills/cyberaudit/commands/audit-runtime.md +11 -0
  82. package/skills/cyberaudit/commands/audit-secrets.md +11 -0
  83. package/skills/cyberaudit/commands/audit-ssrf.md +11 -0
  84. package/skills/cyberaudit/commands/audit-storage.md +11 -0
  85. package/skills/cyberaudit/commands/audit-vue.md +11 -0
  86. package/skills/cyberaudit/commands/audit-xss.md +11 -0
  87. package/skills/cyberaudit/commands/audit-xxe.md +11 -0
  88. package/skills/cyberaudit/reports/EXECUTIVE-SUMMARY-TEMPLATE.md +1 -1
  89. package/skills/cyberaudit/reports/REPORT-TEMPLATE-CLOUD.md +103 -0
  90. package/skills/cyberaudit/reports/REPORT-TEMPLATE-MOBILE.md +2 -2
  91. package/skills/cyberaudit/reports/REPORT-TEMPLATE-WEB.md +2 -2
package/dist/cli.js CHANGED
@@ -1,273 +1,653 @@
1
1
  #!/usr/bin/env node
2
2
  import { Command } from "commander";
3
- import { existsSync, mkdirSync, cpSync, readdirSync, writeFileSync, readFileSync, rmSync, 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 AGENT_TARGETS = {
13
- "opencode": [join(homedir(), ".agents", "skills", "cyberaudit")],
14
- "claude-code": [join(homedir(), ".claude", "skills", "cyberaudit")],
15
- "kiro": [join(homedir(), ".kiro", "skills", "cyberaudit")],
16
- "cursor": [join(homedir(), ".cursor", "mcp.json")],
17
- "gemini": [join(homedir(), ".gemini", "skills", "cyberaudit")],
18
- "antigravity": [join(homedir(), ".gemini", "antigravity", "skills", "cyberaudit")],
19
- "antigravity-cli": [join(homedir(), ".gemini", "antigravity-cli", "skills", "cyberaudit")],
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 skipDetection = new Set(["cursor", "gemini", "antigravity", "antigravity-cli"]);
24
- for (const [agent, paths] of Object.entries(AGENT_TARGETS)) {
25
- if (skipDetection.has(agent))
26
- continue;
27
- if (paths.some((p) => existsSync(dirname(p)))) {
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(join(homedir(), ".cursor")))
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
- if (existsSync(join(homedir(), ".gemini")))
34
- found.push("gemini");
35
- if (existsSync(join(homedir(), ".gemini", "antigravity")))
36
- found.push("antigravity");
37
- if (existsSync(join(homedir(), ".gemini", "antigravity-cli")))
38
- found.push("antigravity-cli");
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 installDir(src, dst) {
42
- if (existsSync(dst))
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}`);
43
262
  rmSync(dst, { recursive: true, force: true });
263
+ }
44
264
  mkdirSync(dirname(dst), { recursive: true });
45
265
  cpSync(src, dst, { recursive: true });
46
266
  }
47
- function installSkill(targetDir, agent, dryRun) {
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;
271
+ }
272
+ if (dryRun) {
273
+ console.log(` → Would install ${commandFiles.length} commands to ${targetDir} (--dry-run) [${mode}]`);
274
+ return;
275
+ }
276
+ mkdirSync(targetDir, { recursive: true });
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) {
283
+ try {
284
+ unlinkSync(join(targetDir, f));
285
+ }
286
+ catch { }
287
+ }
288
+ }
289
+ }
290
+ }
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;
48
311
  if (!existsSync(SKILL_SRC)) {
49
312
  console.error(`āœ— Skill source not found at ${SKILL_SRC}.`);
50
313
  return false;
51
314
  }
52
315
  if (dryRun) {
53
- console.log(` → Would install to ${targetDir} (--dry-run)`);
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}]`);
54
325
  return true;
55
326
  }
56
- // Wipe + re-copy skill dir so stale files are cleaned up
57
- installDir(SKILL_SRC, targetDir);
58
- console.log(` āœ“ Installed at ${targetDir}`);
59
- // Install command + skill files for opencode
60
- if (agent === "opencode") {
61
- const opencodeConf = join(homedir(), ".config", "opencode");
62
- const cmdSrc = join(PKG_ROOT, "skills", "cyberaudit", "commands");
63
- if (existsSync(cmdSrc)) {
64
- // Remove stale audit command files, keep other commands (caveman etc.)
65
- if (existsSync(opencodeConf + "/commands")) {
66
- for (const f of readdirSync(opencodeConf + "/commands")) {
67
- if (typeof f === "string" && f.startsWith("audit") && f.endsWith(".md")) {
68
- try {
69
- unlinkSync(join(opencodeConf, "commands", f));
70
- }
71
- catch { }
72
- }
73
- }
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;
74
354
  }
75
- mkdirSync(join(opencodeConf, "commands"), { recursive: true });
76
- cpSync(cmdSrc, join(opencodeConf, "commands"), { recursive: true });
77
- console.log(` āœ“ Commands installed to opencode`);
355
+ catch { }
78
356
  }
79
- // Wipe + re-copy opencode skill dir
80
- installDir(SKILL_SRC, join(opencodeConf, "skills", "cyberaudit"));
81
- console.log(` āœ“ Skill installed to opencode`);
82
357
  }
83
- return true;
358
+ if (agent === "cursor") {
359
+ const cursorOk = installForCursor(dryRun, mode, cwd);
360
+ ok = ok || cursorOk;
361
+ }
362
+ return ok;
84
363
  }
85
- function installForCursor(dryRun) {
86
- const cursorDir = join(homedir(), ".cursor");
87
- if (!existsSync(cursorDir)) {
88
- console.log(" ~ Cursor not found (no ~/.cursor/)");
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");
89
370
  return false;
90
371
  }
91
372
  const mcpPath = join(cursorDir, "mcp.json");
92
373
  let mcpConfig = { mcpServers: {} };
93
374
  if (existsSync(mcpPath)) {
94
- mcpConfig = JSON.parse(readFileSync(mcpPath, "utf-8"));
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
+ }
95
392
  }
96
393
  if (mcpConfig.mcpServers?.["cyberaudit-skill"]) {
97
- console.log(` āœ“ Cursor MCP already configured`);
394
+ console.log(` āœ“ Cursor MCP already configured (${mcpPath}) [${mode}]`);
98
395
  return true;
99
396
  }
100
397
  if (dryRun) {
101
- console.log(` → Would add Cursor MCP entry to ${mcpPath}`);
398
+ console.log(` → Would add Cursor MCP entry to ${mcpPath} [${mode}]`);
102
399
  return true;
103
400
  }
401
+ if (existsSync(mcpPath)) {
402
+ try {
403
+ copyFileSync(mcpPath, `${mcpPath}.bak`);
404
+ }
405
+ catch { }
406
+ }
104
407
  mcpConfig.mcpServers = mcpConfig.mcpServers || {};
105
408
  mcpConfig.mcpServers["cyberaudit-skill"] = {
106
409
  command: "npx",
107
410
  args: ["-y", "cyberaudit-skill", "serve"],
108
411
  };
109
- writeFileSync(mcpPath, JSON.stringify(mcpConfig, null, 2));
110
- console.log(` āœ“ Added Cursor MCP entry to ${mcpPath}`);
111
- return true;
112
- }
113
- function installForAgent(agent, dryRun) {
114
- switch (agent) {
115
- case "cursor":
116
- return installForCursor(dryRun);
117
- default: {
118
- const paths = AGENT_TARGETS[agent];
119
- if (!paths?.length)
120
- return false;
121
- return installSkill(paths[0], agent, dryRun);
122
- }
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;
123
421
  }
124
422
  }
125
423
  async function main() {
126
424
  const program = new Command();
127
425
  program
128
426
  .name("cyberaudit-skill")
129
- .description("CyberAudit Skill — universal security audit skill for AI agents")
130
- .version(PKG.version);
427
+ .description("CyberAudit Skill — 22 agents like ui-ux-pro-max-skill, 8 main commands on '/'")
428
+ .version(VERSION);
131
429
  program
132
430
  .command("install")
133
- .description("Install CyberAudit Skill for AI agent(s)")
134
- .option("-a, --agent <agent>", "Target: opencode, claude-code, kiro, cursor, gemini, or all", "all")
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")
135
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)
136
436
  .action((options) => {
137
437
  const dryRun = !!options.dryRun;
138
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();
139
447
  let agents;
140
448
  if (agentOpt === "all") {
141
- agents = detectInstalledAgents();
142
- if (agents.length === 0) {
143
- console.log("No supported AI agents detected.\nTry: --agent opencode");
144
- process.exit(1);
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]`);
145
466
  }
146
467
  }
147
468
  else {
148
- const valid = ["opencode", "claude-code", "kiro", "cursor", "gemini", "antigravity", "antigravity-cli"];
469
+ const valid = Object.keys(AGENT_CONFIG);
149
470
  if (!valid.includes(agentOpt)) {
150
- console.error(`Unknown agent: "${agentOpt}". Valid: ${valid.join(", ")}`);
471
+ console.error(`Unknown agent: "${agentOpt}". Valid: ${valid.join(", ")}, all`);
151
472
  process.exit(1);
152
473
  }
153
474
  agents = [agentOpt];
154
475
  }
155
476
  const label = dryRun ? " (dry run)" : "";
156
- 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`);
157
481
  let ok = 0, fail = 0;
158
482
  for (const agent of agents) {
159
- const name = agent.charAt(0).toUpperCase() + agent.slice(1);
160
- if (installForAgent(agent, dryRun))
483
+ const cfg = getPathsForMode(agent, mode, cwd);
484
+ console.log(`\n→ ${cfg.displayName} (${agent}) [${mode}]:`);
485
+ if (installSkillForAgent(agent, dryRun, mode, cwd))
161
486
  ok++;
162
487
  else
163
488
  fail++;
164
489
  }
165
- console.log(`\nDone. ${ok} configured, ${fail} skipped.\n`);
166
- if (!dryRun)
167
- console.log("Verify: cyberaudit-skill list\n");
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
+ }
168
498
  });
169
499
  program
170
500
  .command("list")
171
- .description("List audits and installed agents")
172
- .action(() => {
173
- console.log("\n═══ CyberAudit — Available Audits ═══\n");
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`);
174
507
  console.log(" cyberaudit-web OWASP web app security audit");
175
508
  console.log(" cyberaudit-mobile OWASP mobile app security audit");
176
509
  console.log(" cyberaudit-api API security audit");
177
510
  console.log(" cyberaudit-cloud Cloud config audit");
178
- console.log(" cyberaudit-full Full stack (web + API + cloud)");
179
- console.log(" cyberaudit-quick Quick vulnerability scan\n");
180
- console.log("═══ Installed Agents ═══\n");
181
- for (const [agent] of Object.entries(AGENT_TARGETS)) {
182
- if (agent === "cursor") {
183
- const mcpPath = join(homedir(), ".cursor", "mcp.json");
184
- const ok = existsSync(mcpPath) && readFileSync(mcpPath, "utf-8").includes("cyberaudit-skill");
185
- console.log(` ${ok ? "āœ“" : "āœ—"} ${agent}`);
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
+ }
186
524
  }
187
- else {
188
- const paths = AGENT_TARGETS[agent];
189
- const ok = paths.some((p) => existsSync(p));
190
- console.log(` ${ok ? "āœ“" : "āœ—"} ${agent}`);
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
+ }
191
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}` : ""}`);
192
550
  }
193
- 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`);
194
553
  });
195
554
  program
196
- .command("serve")
197
- .description("Start CyberAudit MCP server (stdio)")
198
- .action(() => {
199
- console.error("[CyberAudit] MCP server starting...");
200
- startMcpServer();
201
- });
202
- program.parse(process.argv);
203
- }
204
- function startMcpServer() {
205
- const rl = createInterface({ input: process.stdin });
206
- const capabilities = {
207
- tools: [
208
- {
209
- name: "cyberaudit-web",
210
- description: "Web app security audit (OWASP Top 10)",
211
- inputSchema: {
212
- type: "object",
213
- properties: {
214
- target: { type: "string", description: "URL or project path" },
215
- depth: { type: "string", enum: ["quick", "standard", "deep"], default: "standard" },
216
- },
217
- },
218
- },
219
- {
220
- name: "cyberaudit-mobile",
221
- description: "Mobile app security audit (OWASP MASVS)",
222
- inputSchema: {
223
- type: "object",
224
- properties: {
225
- target: { type: "string", description: "App package or project path" },
226
- platform: { type: "string", enum: ["android", "ios", "both"], default: "both" },
227
- },
228
- },
229
- },
230
- {
231
- name: "cyberaudit-api",
232
- description: "API security audit",
233
- inputSchema: {
234
- type: "object",
235
- properties: {
236
- target: { type: "string", description: "API endpoint or spec file" },
237
- },
238
- },
239
- },
240
- {
241
- name: "cyberaudit-list",
242
- description: "List all available audit types",
243
- inputSchema: { type: "object", properties: {} },
244
- },
245
- ],
246
- };
247
- function write(id, result) {
248
- process.stdout.write(JSON.stringify({ jsonrpc: "2.0", id, ...result }) + "\n");
249
- }
250
- 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})`);
251
566
  try {
252
- const msg = JSON.parse(line.trim());
253
- const id = msg.id;
254
- if (msg.method === "initialize") {
255
- write(id, { protocolVersion: "2024-11-05", capabilities, serverInfo: { name: "cyberaudit-skill", version: "3.0.0" } });
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' })));
256
573
  }
257
- else if (msg.method === "tools/list") {
258
- write(id, capabilities);
574
+ if (type === 'web' || type === 'all') {
575
+ webFindings = scanWeb(scanTarget);
576
+ allFindings.push(...webFindings.map(f => ({ ...f, scanner: 'web' })));
259
577
  }
260
- else if (msg.method === "tools/call") {
261
- write(id, {
262
- content: [{ type: "text", text: `[CyberAudit] "${msg.params.name}" called with ${JSON.stringify(msg.params.arguments)}\n\nRun the audit by loading the CyberAudit Skill.` }],
263
- });
578
+ if (options.json) {
579
+ console.log(JSON.stringify({ target: scanTarget, version: VERSION, type, findings: allFindings }, null, 2));
264
580
  }
265
581
  else {
266
- write(id, { error: { code: -32601, message: `Unknown method: ${msg.method}` } });
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}`);
267
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);
268
597
  }
269
- catch { /* skip malformed */ }
270
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);
271
651
  }
272
652
  main().catch((err) => {
273
653
  console.error("[CyberAudit] Fatal error:", err);