hackmyagent 0.7.2 → 0.8.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 (333) hide show
  1. package/LICENSE +191 -0
  2. package/README.md +66 -28
  3. package/dist/arp/cli/index.d.ts +3 -0
  4. package/dist/arp/cli/index.d.ts.map +1 -0
  5. package/dist/arp/cli/index.js +219 -0
  6. package/dist/arp/cli/index.js.map +1 -0
  7. package/dist/arp/config/loader.d.ts +8 -0
  8. package/dist/arp/config/loader.d.ts.map +1 -0
  9. package/dist/arp/config/loader.js +102 -0
  10. package/dist/arp/config/loader.js.map +1 -0
  11. package/dist/arp/enforcement/kill-switch.d.ts +22 -0
  12. package/dist/arp/enforcement/kill-switch.d.ts.map +1 -0
  13. package/dist/arp/enforcement/kill-switch.js +122 -0
  14. package/dist/arp/enforcement/kill-switch.js.map +1 -0
  15. package/dist/arp/engine/event-engine.d.ts +29 -0
  16. package/dist/arp/engine/event-engine.d.ts.map +1 -0
  17. package/dist/arp/engine/event-engine.js +233 -0
  18. package/dist/arp/engine/event-engine.js.map +1 -0
  19. package/dist/arp/index.d.ts +81 -0
  20. package/dist/arp/index.d.ts.map +1 -0
  21. package/dist/arp/index.js +239 -0
  22. package/dist/arp/index.js.map +1 -0
  23. package/dist/arp/intelligence/adapters.d.ts +45 -0
  24. package/dist/arp/intelligence/adapters.d.ts.map +1 -0
  25. package/dist/arp/intelligence/adapters.js +222 -0
  26. package/dist/arp/intelligence/adapters.js.map +1 -0
  27. package/dist/arp/intelligence/anomaly.d.ts +32 -0
  28. package/dist/arp/intelligence/anomaly.d.ts.map +1 -0
  29. package/dist/arp/intelligence/anomaly.js +80 -0
  30. package/dist/arp/intelligence/anomaly.js.map +1 -0
  31. package/dist/arp/intelligence/budget.d.ts +33 -0
  32. package/dist/arp/intelligence/budget.d.ts.map +1 -0
  33. package/dist/arp/intelligence/budget.js +150 -0
  34. package/dist/arp/intelligence/budget.js.map +1 -0
  35. package/dist/arp/intelligence/coordinator.d.ts +43 -0
  36. package/dist/arp/intelligence/coordinator.d.ts.map +1 -0
  37. package/dist/arp/intelligence/coordinator.js +301 -0
  38. package/dist/arp/intelligence/coordinator.js.map +1 -0
  39. package/dist/arp/interceptors/a2a-protocol.d.ts +29 -0
  40. package/dist/arp/interceptors/a2a-protocol.d.ts.map +1 -0
  41. package/dist/arp/interceptors/a2a-protocol.js +111 -0
  42. package/dist/arp/interceptors/a2a-protocol.js.map +1 -0
  43. package/dist/arp/interceptors/filesystem.d.ts +33 -0
  44. package/dist/arp/interceptors/filesystem.d.ts.map +1 -0
  45. package/dist/arp/interceptors/filesystem.js +199 -0
  46. package/dist/arp/interceptors/filesystem.js.map +1 -0
  47. package/dist/arp/interceptors/mcp-protocol.d.ts +25 -0
  48. package/dist/arp/interceptors/mcp-protocol.d.ts.map +1 -0
  49. package/dist/arp/interceptors/mcp-protocol.js +126 -0
  50. package/dist/arp/interceptors/mcp-protocol.js.map +1 -0
  51. package/dist/arp/interceptors/network.d.ts +26 -0
  52. package/dist/arp/interceptors/network.d.ts.map +1 -0
  53. package/dist/arp/interceptors/network.js +146 -0
  54. package/dist/arp/interceptors/network.js.map +1 -0
  55. package/dist/arp/interceptors/process.d.ts +26 -0
  56. package/dist/arp/interceptors/process.d.ts.map +1 -0
  57. package/dist/arp/interceptors/process.js +157 -0
  58. package/dist/arp/interceptors/process.js.map +1 -0
  59. package/dist/arp/interceptors/prompt.d.ts +29 -0
  60. package/dist/arp/interceptors/prompt.d.ts.map +1 -0
  61. package/dist/arp/interceptors/prompt.js +82 -0
  62. package/dist/arp/interceptors/prompt.js.map +1 -0
  63. package/dist/arp/license/index.d.ts +59 -0
  64. package/dist/arp/license/index.d.ts.map +1 -0
  65. package/dist/arp/license/index.js +78 -0
  66. package/dist/arp/license/index.js.map +1 -0
  67. package/dist/arp/monitors/filesystem.d.ts +21 -0
  68. package/dist/arp/monitors/filesystem.d.ts.map +1 -0
  69. package/dist/arp/monitors/filesystem.js +141 -0
  70. package/dist/arp/monitors/filesystem.js.map +1 -0
  71. package/dist/arp/monitors/network.d.ts +32 -0
  72. package/dist/arp/monitors/network.d.ts.map +1 -0
  73. package/dist/arp/monitors/network.js +301 -0
  74. package/dist/arp/monitors/network.js.map +1 -0
  75. package/dist/arp/monitors/process.d.ts +24 -0
  76. package/dist/arp/monitors/process.d.ts.map +1 -0
  77. package/dist/arp/monitors/process.js +205 -0
  78. package/dist/arp/monitors/process.js.map +1 -0
  79. package/dist/arp/patterns/ai-threats.d.ts +48 -0
  80. package/dist/arp/patterns/ai-threats.d.ts.map +1 -0
  81. package/dist/arp/patterns/ai-threats.js +215 -0
  82. package/dist/arp/patterns/ai-threats.js.map +1 -0
  83. package/dist/arp/proxy/forward.d.ts +23 -0
  84. package/dist/arp/proxy/forward.d.ts.map +1 -0
  85. package/dist/arp/proxy/forward.js +152 -0
  86. package/dist/arp/proxy/forward.js.map +1 -0
  87. package/dist/arp/proxy/server.d.ts +45 -0
  88. package/dist/arp/proxy/server.d.ts.map +1 -0
  89. package/dist/arp/proxy/server.js +331 -0
  90. package/dist/arp/proxy/server.js.map +1 -0
  91. package/dist/arp/reporting/local-log.d.ts +22 -0
  92. package/dist/arp/reporting/local-log.d.ts.map +1 -0
  93. package/dist/arp/reporting/local-log.js +116 -0
  94. package/dist/arp/reporting/local-log.js.map +1 -0
  95. package/dist/arp/types.d.ts +230 -0
  96. package/dist/arp/types.d.ts.map +1 -0
  97. package/dist/arp/types.js +4 -0
  98. package/dist/arp/types.js.map +1 -0
  99. package/dist/attack/custom-payloads.d.ts +11 -0
  100. package/dist/attack/custom-payloads.d.ts.map +1 -0
  101. package/dist/attack/custom-payloads.js +108 -0
  102. package/dist/attack/custom-payloads.js.map +1 -0
  103. package/dist/attack/fail-policy.d.ts +16 -0
  104. package/dist/attack/fail-policy.d.ts.map +1 -0
  105. package/dist/attack/fail-policy.js +36 -0
  106. package/dist/attack/fail-policy.js.map +1 -0
  107. package/dist/attack/index.d.ts +12 -0
  108. package/dist/attack/index.d.ts.map +1 -0
  109. package/dist/attack/index.js +30 -0
  110. package/dist/attack/index.js.map +1 -0
  111. package/dist/attack/payloads/a2a-attacks.d.ts +12 -0
  112. package/dist/attack/payloads/a2a-attacks.d.ts.map +1 -0
  113. package/dist/attack/payloads/a2a-attacks.js +221 -0
  114. package/dist/attack/payloads/a2a-attacks.js.map +1 -0
  115. package/dist/attack/payloads/capability-abuse.d.ts +8 -0
  116. package/dist/attack/payloads/capability-abuse.d.ts.map +1 -0
  117. package/dist/attack/payloads/capability-abuse.js +222 -0
  118. package/dist/attack/payloads/capability-abuse.js.map +1 -0
  119. package/dist/attack/payloads/context-manipulation.d.ts +8 -0
  120. package/dist/attack/payloads/context-manipulation.d.ts.map +1 -0
  121. package/dist/attack/payloads/context-manipulation.js +217 -0
  122. package/dist/attack/payloads/context-manipulation.js.map +1 -0
  123. package/dist/attack/payloads/data-exfiltration.d.ts +8 -0
  124. package/dist/attack/payloads/data-exfiltration.d.ts.map +1 -0
  125. package/dist/attack/payloads/data-exfiltration.js +249 -0
  126. package/dist/attack/payloads/data-exfiltration.js.map +1 -0
  127. package/dist/attack/payloads/index.d.ts +29 -0
  128. package/dist/attack/payloads/index.d.ts.map +1 -0
  129. package/dist/attack/payloads/index.js +76 -0
  130. package/dist/attack/payloads/index.js.map +1 -0
  131. package/dist/attack/payloads/jailbreak.d.ts +8 -0
  132. package/dist/attack/payloads/jailbreak.d.ts.map +1 -0
  133. package/dist/attack/payloads/jailbreak.js +265 -0
  134. package/dist/attack/payloads/jailbreak.js.map +1 -0
  135. package/dist/attack/payloads/mcp-exploitation.d.ts +12 -0
  136. package/dist/attack/payloads/mcp-exploitation.d.ts.map +1 -0
  137. package/dist/attack/payloads/mcp-exploitation.js +221 -0
  138. package/dist/attack/payloads/mcp-exploitation.js.map +1 -0
  139. package/dist/attack/payloads/prompt-injection.d.ts +8 -0
  140. package/dist/attack/payloads/prompt-injection.d.ts.map +1 -0
  141. package/dist/attack/payloads/prompt-injection.js +262 -0
  142. package/dist/attack/payloads/prompt-injection.js.map +1 -0
  143. package/dist/attack/scanner.d.ts +84 -0
  144. package/dist/attack/scanner.d.ts.map +1 -0
  145. package/dist/attack/scanner.js +509 -0
  146. package/dist/attack/scanner.js.map +1 -0
  147. package/dist/attack/types.d.ts +153 -0
  148. package/dist/attack/types.d.ts.map +1 -0
  149. package/dist/attack/types.js +46 -0
  150. package/dist/attack/types.js.map +1 -0
  151. package/dist/benchmarks/index.d.ts +16 -0
  152. package/dist/benchmarks/index.d.ts.map +1 -0
  153. package/dist/benchmarks/index.js +27 -0
  154. package/dist/benchmarks/index.js.map +1 -0
  155. package/dist/benchmarks/oasb-1.d.ts +112 -0
  156. package/dist/benchmarks/oasb-1.d.ts.map +1 -0
  157. package/dist/benchmarks/oasb-1.js +1124 -0
  158. package/dist/benchmarks/oasb-1.js.map +1 -0
  159. package/dist/checker/check-skill.d.ts +48 -0
  160. package/dist/checker/check-skill.d.ts.map +1 -0
  161. package/dist/checker/check-skill.js +105 -0
  162. package/dist/checker/check-skill.js.map +1 -0
  163. package/dist/checker/index.d.ts +12 -0
  164. package/dist/checker/index.d.ts.map +1 -0
  165. package/dist/checker/index.js +16 -0
  166. package/dist/checker/index.js.map +1 -0
  167. package/dist/checker/permission-analyzer.d.ts +12 -0
  168. package/dist/checker/permission-analyzer.d.ts.map +1 -0
  169. package/dist/checker/permission-analyzer.js +84 -0
  170. package/dist/checker/permission-analyzer.js.map +1 -0
  171. package/dist/checker/publisher-verifier.d.ts +34 -0
  172. package/dist/checker/publisher-verifier.d.ts.map +1 -0
  173. package/dist/checker/publisher-verifier.js +121 -0
  174. package/dist/checker/publisher-verifier.js.map +1 -0
  175. package/dist/checker/skill-identifier.d.ts +14 -0
  176. package/dist/checker/skill-identifier.d.ts.map +1 -0
  177. package/dist/checker/skill-identifier.js +55 -0
  178. package/dist/checker/skill-identifier.js.map +1 -0
  179. package/dist/cli.d.ts +7 -0
  180. package/dist/cli.d.ts.map +1 -0
  181. package/dist/cli.js +3534 -0
  182. package/dist/cli.js.map +1 -0
  183. package/dist/hardening/index.d.ts +7 -0
  184. package/dist/hardening/index.d.ts.map +1 -0
  185. package/dist/hardening/index.js +9 -0
  186. package/dist/hardening/index.js.map +1 -0
  187. package/dist/hardening/scanner.d.ts +147 -0
  188. package/dist/hardening/scanner.d.ts.map +1 -0
  189. package/dist/hardening/scanner.js +5445 -0
  190. package/dist/hardening/scanner.js.map +1 -0
  191. package/dist/hardening/security-check.d.ts +85 -0
  192. package/dist/hardening/security-check.d.ts.map +1 -0
  193. package/dist/hardening/security-check.js +6 -0
  194. package/dist/hardening/security-check.js.map +1 -0
  195. package/dist/index.d.ts +38 -4
  196. package/dist/index.d.ts.map +1 -1
  197. package/dist/index.js +91 -3525
  198. package/dist/index.js.map +1 -1
  199. package/dist/mcp-server.js +10 -10
  200. package/dist/mcp-server.js.map +1 -1
  201. package/dist/oasb/config/dvaa-targets.d.ts +13 -0
  202. package/dist/oasb/config/dvaa-targets.d.ts.map +1 -0
  203. package/dist/oasb/config/dvaa-targets.js +89 -0
  204. package/dist/oasb/config/dvaa-targets.js.map +1 -0
  205. package/dist/oasb/harness/arp-wrapper.d.ts +29 -0
  206. package/dist/oasb/harness/arp-wrapper.d.ts.map +1 -0
  207. package/dist/oasb/harness/arp-wrapper.js +134 -0
  208. package/dist/oasb/harness/arp-wrapper.js.map +1 -0
  209. package/dist/oasb/harness/dvaa-client.d.ts +46 -0
  210. package/dist/oasb/harness/dvaa-client.d.ts.map +1 -0
  211. package/dist/oasb/harness/dvaa-client.js +98 -0
  212. package/dist/oasb/harness/dvaa-client.js.map +1 -0
  213. package/dist/oasb/harness/dvaa-manager.d.ts +17 -0
  214. package/dist/oasb/harness/dvaa-manager.d.ts.map +1 -0
  215. package/dist/oasb/harness/dvaa-manager.js +132 -0
  216. package/dist/oasb/harness/dvaa-manager.js.map +1 -0
  217. package/dist/oasb/harness/event-collector.d.ts +33 -0
  218. package/dist/oasb/harness/event-collector.d.ts.map +1 -0
  219. package/dist/oasb/harness/event-collector.js +86 -0
  220. package/dist/oasb/harness/event-collector.js.map +1 -0
  221. package/dist/oasb/harness/metrics.d.ts +14 -0
  222. package/dist/oasb/harness/metrics.d.ts.map +1 -0
  223. package/dist/oasb/harness/metrics.js +56 -0
  224. package/dist/oasb/harness/metrics.js.map +1 -0
  225. package/dist/oasb/harness/mock-llm-adapter.d.ts +34 -0
  226. package/dist/oasb/harness/mock-llm-adapter.d.ts.map +1 -0
  227. package/dist/oasb/harness/mock-llm-adapter.js +69 -0
  228. package/dist/oasb/harness/mock-llm-adapter.js.map +1 -0
  229. package/dist/oasb/harness/types.d.ts +74 -0
  230. package/dist/oasb/harness/types.d.ts.map +1 -0
  231. package/dist/oasb/harness/types.js +3 -0
  232. package/dist/oasb/harness/types.js.map +1 -0
  233. package/dist/plugins/core.d.ts +109 -0
  234. package/dist/plugins/core.d.ts.map +1 -0
  235. package/dist/plugins/core.js +30 -0
  236. package/dist/plugins/core.js.map +1 -0
  237. package/dist/plugins/credvault.d.ts +22 -0
  238. package/dist/plugins/credvault.d.ts.map +1 -0
  239. package/dist/plugins/credvault.js +374 -0
  240. package/dist/plugins/credvault.js.map +1 -0
  241. package/dist/plugins/signcrypt.d.ts +27 -0
  242. package/dist/plugins/signcrypt.d.ts.map +1 -0
  243. package/dist/plugins/signcrypt.js +317 -0
  244. package/dist/plugins/signcrypt.js.map +1 -0
  245. package/dist/plugins/skillguard.d.ts +25 -0
  246. package/dist/plugins/skillguard.d.ts.map +1 -0
  247. package/dist/plugins/skillguard.js +346 -0
  248. package/dist/plugins/skillguard.js.map +1 -0
  249. package/dist/registry/client.d.ts +125 -0
  250. package/dist/registry/client.d.ts.map +1 -0
  251. package/dist/registry/client.js +308 -0
  252. package/dist/registry/client.js.map +1 -0
  253. package/dist/registry/index.d.ts +3 -0
  254. package/dist/registry/index.d.ts.map +1 -0
  255. package/dist/registry/index.js +10 -0
  256. package/dist/registry/index.js.map +1 -0
  257. package/dist/scanner/external-scanner.d.ts +13 -0
  258. package/dist/scanner/external-scanner.d.ts.map +1 -0
  259. package/dist/scanner/external-scanner.js +299 -0
  260. package/dist/scanner/external-scanner.js.map +1 -0
  261. package/dist/scanner/index.d.ts +6 -0
  262. package/dist/scanner/index.d.ts.map +1 -0
  263. package/dist/scanner/index.js +9 -0
  264. package/dist/scanner/index.js.map +1 -0
  265. package/dist/scanner/types.d.ts +32 -0
  266. package/dist/scanner/types.d.ts.map +1 -0
  267. package/dist/scanner/types.js +6 -0
  268. package/dist/scanner/types.js.map +1 -0
  269. package/dist/semantic/deep-scan.d.ts +13 -0
  270. package/dist/semantic/deep-scan.d.ts.map +1 -0
  271. package/dist/semantic/deep-scan.js +63 -0
  272. package/dist/semantic/deep-scan.js.map +1 -0
  273. package/dist/semantic/index.d.ts +17 -0
  274. package/dist/semantic/index.d.ts.map +1 -0
  275. package/dist/semantic/index.js +39 -0
  276. package/dist/semantic/index.js.map +1 -0
  277. package/dist/semantic/integration/cost-estimator.d.ts +17 -0
  278. package/dist/semantic/integration/cost-estimator.d.ts.map +1 -0
  279. package/dist/semantic/integration/cost-estimator.js +54 -0
  280. package/dist/semantic/integration/cost-estimator.js.map +1 -0
  281. package/dist/semantic/integration/finding-adapter.d.ts +34 -0
  282. package/dist/semantic/integration/finding-adapter.d.ts.map +1 -0
  283. package/dist/semantic/integration/finding-adapter.js +41 -0
  284. package/dist/semantic/integration/finding-adapter.js.map +1 -0
  285. package/dist/semantic/integration/oasb-upgrader.d.ts +20 -0
  286. package/dist/semantic/integration/oasb-upgrader.d.ts.map +1 -0
  287. package/dist/semantic/integration/oasb-upgrader.js +47 -0
  288. package/dist/semantic/integration/oasb-upgrader.js.map +1 -0
  289. package/dist/semantic/llm/budget.d.ts +50 -0
  290. package/dist/semantic/llm/budget.d.ts.map +1 -0
  291. package/dist/semantic/llm/budget.js +139 -0
  292. package/dist/semantic/llm/budget.js.map +1 -0
  293. package/dist/semantic/llm/cache.d.ts +36 -0
  294. package/dist/semantic/llm/cache.d.ts.map +1 -0
  295. package/dist/semantic/llm/cache.js +103 -0
  296. package/dist/semantic/llm/cache.js.map +1 -0
  297. package/dist/semantic/llm/client.d.ts +49 -0
  298. package/dist/semantic/llm/client.d.ts.map +1 -0
  299. package/dist/semantic/llm/client.js +64 -0
  300. package/dist/semantic/llm/client.js.map +1 -0
  301. package/dist/semantic/llm/index.d.ts +33 -0
  302. package/dist/semantic/llm/index.d.ts.map +1 -0
  303. package/dist/semantic/llm/index.js +129 -0
  304. package/dist/semantic/llm/index.js.map +1 -0
  305. package/dist/semantic/llm/prompts.d.ts +30 -0
  306. package/dist/semantic/llm/prompts.d.ts.map +1 -0
  307. package/dist/semantic/llm/prompts.js +120 -0
  308. package/dist/semantic/llm/prompts.js.map +1 -0
  309. package/dist/semantic/structural/credential-context.d.ts +14 -0
  310. package/dist/semantic/structural/credential-context.d.ts.map +1 -0
  311. package/dist/semantic/structural/credential-context.js +295 -0
  312. package/dist/semantic/structural/credential-context.js.map +1 -0
  313. package/dist/semantic/structural/index.d.ts +28 -0
  314. package/dist/semantic/structural/index.d.ts.map +1 -0
  315. package/dist/semantic/structural/index.js +138 -0
  316. package/dist/semantic/structural/index.js.map +1 -0
  317. package/dist/semantic/structural/instruction.d.ts +19 -0
  318. package/dist/semantic/structural/instruction.d.ts.map +1 -0
  319. package/dist/semantic/structural/instruction.js +167 -0
  320. package/dist/semantic/structural/instruction.js.map +1 -0
  321. package/dist/semantic/structural/mcp-config.d.ts +22 -0
  322. package/dist/semantic/structural/mcp-config.d.ts.map +1 -0
  323. package/dist/semantic/structural/mcp-config.js +294 -0
  324. package/dist/semantic/structural/mcp-config.js.map +1 -0
  325. package/dist/semantic/structural/permission-model.d.ts +16 -0
  326. package/dist/semantic/structural/permission-model.d.ts.map +1 -0
  327. package/dist/semantic/structural/permission-model.js +121 -0
  328. package/dist/semantic/structural/permission-model.js.map +1 -0
  329. package/dist/semantic/types.d.ts +122 -0
  330. package/dist/semantic/types.d.ts.map +1 -0
  331. package/dist/semantic/types.js +10 -0
  332. package/dist/semantic/types.js.map +1 -0
  333. package/package.json +25 -14
package/dist/cli.js ADDED
@@ -0,0 +1,3534 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ /**
4
+ * HackMyAgent CLI
5
+ * Find it. Break it. Fix it.
6
+ */
7
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
8
+ if (k2 === undefined) k2 = k;
9
+ var desc = Object.getOwnPropertyDescriptor(m, k);
10
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
11
+ desc = { enumerable: true, get: function() { return m[k]; } };
12
+ }
13
+ Object.defineProperty(o, k2, desc);
14
+ }) : (function(o, m, k, k2) {
15
+ if (k2 === undefined) k2 = k;
16
+ o[k2] = m[k];
17
+ }));
18
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
19
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
20
+ }) : function(o, v) {
21
+ o["default"] = v;
22
+ });
23
+ var __importStar = (this && this.__importStar) || (function () {
24
+ var ownKeys = function(o) {
25
+ ownKeys = Object.getOwnPropertyNames || function (o) {
26
+ var ar = [];
27
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
28
+ return ar;
29
+ };
30
+ return ownKeys(o);
31
+ };
32
+ return function (mod) {
33
+ if (mod && mod.__esModule) return mod;
34
+ var result = {};
35
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
36
+ __setModuleDefault(result, mod);
37
+ return result;
38
+ };
39
+ })();
40
+ Object.defineProperty(exports, "__esModule", { value: true });
41
+ const commander_1 = require("commander");
42
+ const index_1 = require("./index");
43
+ const program = new commander_1.Command();
44
+ // Check for NO_COLOR env or non-TTY to disable colors by default
45
+ const noColorEnv = process.env.NO_COLOR !== undefined || process.stdout.isTTY === false;
46
+ // Color codes - will be cleared if --no-color is passed
47
+ let colors = {
48
+ green: '\x1b[32m',
49
+ yellow: '\x1b[33m',
50
+ red: '\x1b[31m',
51
+ brightRed: '\x1b[91m',
52
+ cyan: '\x1b[36m',
53
+ reset: '\x1b[0m',
54
+ };
55
+ if (noColorEnv) {
56
+ colors = { green: '', yellow: '', red: '', brightRed: '', cyan: '', reset: '' };
57
+ }
58
+ // Deprecation warning for removed HMAC auth
59
+ if (process.env.HMA_COMMUNITY_SECRET) {
60
+ console.error('Warning: HMA_COMMUNITY_SECRET is deprecated and no longer used. Scan tokens are now issued automatically.');
61
+ }
62
+ program
63
+ .name('hackmyagent')
64
+ .description(`Find it. Break it. Fix it.
65
+
66
+ The hacker's toolkit for AI agents. 147+ security checks, 75 attack
67
+ payloads, auto-fix with rollback, and OASB benchmark compliance.
68
+
69
+ Documentation: https://hackmyagent.com/docs
70
+
71
+ Updates (v${index_1.VERSION}):
72
+ - MCP JSON-RPC and A2A protocol attack modes
73
+ - SARIF and HTML output for all scan modes
74
+ - Semantic engine (structural + LLM analysis)
75
+ - OpenA2A Registry integration for trust scoring
76
+
77
+ Examples:
78
+ $ hackmyagent secure Find vulnerabilities (147+ checks)
79
+ $ hackmyagent attack --local Break it with 75 attack payloads
80
+ $ hackmyagent secure --fix Fix issues automatically
81
+ $ hackmyagent fix-all Run all security plugins
82
+ $ hackmyagent scan example.com Scan external infrastructure`)
83
+ .version(index_1.VERSION, '-V, --version', 'Output the version number')
84
+ .option('--no-color', 'Disable colored output (also respects NO_COLOR env)')
85
+ .hook('preAction', (thisCommand) => {
86
+ const opts = thisCommand.opts();
87
+ if (opts.color === false) {
88
+ colors = { green: '', yellow: '', red: '', brightRed: '', cyan: '', reset: '' };
89
+ }
90
+ });
91
+ // Risk level colors and symbols
92
+ const RISK_DISPLAY = {
93
+ low: { symbol: '✅', color: () => colors.green },
94
+ medium: { symbol: '⚠️', color: () => colors.yellow },
95
+ high: { symbol: '🔴', color: () => colors.red },
96
+ critical: { symbol: '🚨', color: () => colors.brightRed },
97
+ };
98
+ const RESET = () => colors.reset;
99
+ program
100
+ .command('check')
101
+ .description(`Verify a skill before installing
102
+
103
+ Analyzes skill safety by checking:
104
+ • Publisher identity via DNS TXT records
105
+ • Permissions requested (filesystem, network, shell)
106
+ • Revocation status against global blocklist
107
+
108
+ Risk levels: low, medium, high, critical
109
+ Exit code 1 if high/critical risk detected.
110
+
111
+ Examples:
112
+ $ hackmyagent check @anthropic/claude-mcp
113
+ $ hackmyagent check @publisher/skill --verbose
114
+ $ hackmyagent check @publisher/skill --json`)
115
+ .argument('<skill>', 'Skill identifier (e.g., @publisher/skill)')
116
+ .option('-v, --verbose', 'Show detailed verification info')
117
+ .option('--json', 'Output as JSON (for scripting/CI)')
118
+ .option('--offline', 'Skip DNS verification (offline mode)')
119
+ .action(async (skill, options) => {
120
+ try {
121
+ const result = await (0, index_1.checkSkill)(skill, {
122
+ skipDnsVerification: options.offline,
123
+ });
124
+ if (options.json) {
125
+ console.log(JSON.stringify(result, null, 2));
126
+ return;
127
+ }
128
+ const risk = RISK_DISPLAY[result.risk];
129
+ console.log(`\n${risk.color()}${risk.symbol} ${result.risk.toUpperCase()} RISK${RESET()}\n`);
130
+ // Publisher info
131
+ console.log(`Publisher: @${result.publisher.name}`);
132
+ if (result.publisher.verified) {
133
+ console.log(`├─ ✅ Verified via DNS`);
134
+ if (result.publisher.domain) {
135
+ console.log(`├─ 🌐 Domain: ${result.publisher.domain}`);
136
+ }
137
+ if (result.publisher.verifiedAt && options.verbose) {
138
+ console.log(`└─ 📅 Verified at: ${result.publisher.verifiedAt.toISOString()}`);
139
+ }
140
+ else {
141
+ console.log(`└─ Method: DNS TXT record`);
142
+ }
143
+ }
144
+ else {
145
+ console.log(`├─ ❌ Not verified`);
146
+ if (result.publisher.failureReason && options.verbose) {
147
+ console.log(`└─ Reason: ${result.publisher.failureReason}`);
148
+ }
149
+ else if (options.offline) {
150
+ console.log(`└─ (DNS verification skipped - offline mode)`);
151
+ }
152
+ else {
153
+ console.log(`└─ No valid DNS TXT record found`);
154
+ }
155
+ }
156
+ console.log();
157
+ // Permissions
158
+ console.log('Permissions:');
159
+ if (result.permissions.requested.length === 0) {
160
+ console.log('└─ None declared');
161
+ }
162
+ else {
163
+ for (const perm of result.permissions.safe) {
164
+ console.log(`├─ ✅ ${perm}`);
165
+ }
166
+ for (const perm of result.permissions.reviewNeeded) {
167
+ console.log(`├─ ⚠️ ${perm} (review needed)`);
168
+ }
169
+ for (const perm of result.permissions.dangerous) {
170
+ console.log(`├─ ❌ ${perm} (DANGEROUS)`);
171
+ }
172
+ console.log(`└─ Risk score: ${result.permissions.riskScore}/100`);
173
+ }
174
+ console.log();
175
+ // Revocation
176
+ console.log('Revocation:');
177
+ if (result.revocation.revoked) {
178
+ console.log(`└─ 🚨 REVOKED: ${result.revocation.reason}`);
179
+ }
180
+ else {
181
+ console.log(`└─ ✅ Not on blocklist`);
182
+ }
183
+ console.log();
184
+ // Verbose details
185
+ if (options.verbose) {
186
+ console.log('Details:');
187
+ console.log(`└─ Checked at: ${result.revocation.checkedAt.toISOString()}`);
188
+ }
189
+ // Exit with non-zero for high/critical risk
190
+ if (result.risk === 'critical' || result.risk === 'high') {
191
+ process.exit(1);
192
+ }
193
+ }
194
+ catch (error) {
195
+ console.error(`Error: ${error instanceof Error ? error.message : 'Unknown error'}`);
196
+ process.exit(1);
197
+ }
198
+ });
199
+ // Severity colors and symbols for secure command
200
+ const SEVERITY_DISPLAY = {
201
+ critical: { symbol: '🔴', color: () => colors.brightRed },
202
+ high: { symbol: '🟠', color: () => colors.red },
203
+ medium: { symbol: '🟡', color: () => colors.yellow },
204
+ low: { symbol: '🟢', color: () => colors.green },
205
+ };
206
+ function groupFindingsBySeverity(findings) {
207
+ const grouped = {
208
+ critical: [],
209
+ high: [],
210
+ medium: [],
211
+ low: [],
212
+ };
213
+ for (const finding of findings) {
214
+ grouped[finding.severity].push(finding);
215
+ }
216
+ return grouped;
217
+ }
218
+ function generateBenchmarkReport(findings, level, categoryFilter) {
219
+ // Get controls for the specified level
220
+ let controls = (0, index_1.getControlsForLevel)(level);
221
+ // Filter by category if specified
222
+ if (categoryFilter) {
223
+ const categoryControls = (0, index_1.getControlsForCategory)(categoryFilter);
224
+ if (categoryControls.length === 0) {
225
+ console.error(`Error: Unknown category '${categoryFilter}'.`);
226
+ console.error(`Available categories: ${index_1.OASB_1_CATEGORIES.map((c) => c.name).join(', ')}`);
227
+ process.exit(1);
228
+ }
229
+ controls = controls.filter((c) => c.category.toLowerCase() === categoryFilter.toLowerCase());
230
+ }
231
+ // Build a map of checkId -> finding for quick lookup
232
+ const findingsByCheckId = new Map();
233
+ for (const finding of findings) {
234
+ findingsByCheckId.set(finding.checkId, finding);
235
+ }
236
+ // Evaluate each control
237
+ const controlResults = [];
238
+ let l1Passed = 0, l1Total = 0;
239
+ let l2Passed = 0, l2Total = 0;
240
+ let l3Passed = 0, l3Total = 0;
241
+ let passedCount = 0, failedCount = 0, unverifiedCount = 0;
242
+ for (const control of controls) {
243
+ let status;
244
+ const relatedFindings = [];
245
+ let remediation;
246
+ if (control.verification === 'manual' || control.verification === 'forward') {
247
+ // Manual/forward controls are unverified (human must check)
248
+ status = 'unverified';
249
+ unverifiedCount++;
250
+ // Use control's remediation for manual/forward controls
251
+ remediation = control.remediation;
252
+ }
253
+ else if (control.checkIds.length === 0) {
254
+ // No automated checks defined
255
+ status = 'unverified';
256
+ unverifiedCount++;
257
+ remediation = control.remediation;
258
+ }
259
+ else {
260
+ // Check all mapped check IDs
261
+ let hasAnyFinding = false;
262
+ let hasFailure = false;
263
+ for (const checkId of control.checkIds) {
264
+ const finding = findingsByCheckId.get(checkId);
265
+ if (finding) {
266
+ hasAnyFinding = true;
267
+ if (!finding.passed) {
268
+ hasFailure = true;
269
+ relatedFindings.push(`${checkId}: ${finding.description}`);
270
+ if (finding.fix) {
271
+ remediation = remediation || finding.fix;
272
+ }
273
+ }
274
+ }
275
+ }
276
+ // Only mark as passed if we actually verified something
277
+ // Missing findings = unverified (not passed)
278
+ if (!hasAnyFinding) {
279
+ status = 'unverified';
280
+ unverifiedCount++;
281
+ remediation = control.remediation;
282
+ }
283
+ else if (hasFailure) {
284
+ status = 'failed';
285
+ failedCount++;
286
+ remediation = remediation || control.remediation;
287
+ }
288
+ else {
289
+ status = 'passed';
290
+ passedCount++;
291
+ }
292
+ }
293
+ // Count by level for compliance calculation
294
+ if (control.scored && status !== 'unverified') {
295
+ if (control.level === 'L1') {
296
+ l1Total++;
297
+ if (status === 'passed')
298
+ l1Passed++;
299
+ }
300
+ else if (control.level === 'L2') {
301
+ l2Total++;
302
+ if (status === 'passed')
303
+ l2Passed++;
304
+ }
305
+ else if (control.level === 'L3') {
306
+ l3Total++;
307
+ if (status === 'passed')
308
+ l3Passed++;
309
+ }
310
+ }
311
+ controlResults.push({ control, status, findings: relatedFindings, remediation });
312
+ }
313
+ // Calculate compliance percentages
314
+ const l1Compliance = l1Total > 0 ? Math.round((l1Passed / l1Total) * 100) : 100;
315
+ const l2Compliance = l2Total > 0 ? Math.round((l2Passed / l2Total) * 100) : 100;
316
+ const l3Compliance = l3Total > 0 ? Math.round((l3Passed / l3Total) * 100) : 100;
317
+ const totalScored = l1Total + l2Total + l3Total;
318
+ const totalPassed = l1Passed + l2Passed + l3Passed;
319
+ const overallCompliance = totalScored > 0 ? Math.round((totalPassed / totalScored) * 100) : 100;
320
+ // Group results by category
321
+ const categoryResults = [];
322
+ for (const category of index_1.OASB_1_CATEGORIES) {
323
+ if (categoryFilter && category.name.toLowerCase() !== categoryFilter.toLowerCase())
324
+ continue;
325
+ const catControls = controlResults.filter((r) => r.control.category === category.name);
326
+ if (catControls.length === 0)
327
+ continue;
328
+ const passed = catControls.filter((r) => r.status === 'passed').length;
329
+ const failed = catControls.filter((r) => r.status === 'failed').length;
330
+ const unverified = catControls.filter((r) => r.status === 'unverified').length;
331
+ const compliance = (passed + failed) > 0 ? Math.round((passed / (passed + failed)) * 100) : 100;
332
+ categoryResults.push({
333
+ category: category.name,
334
+ compliance,
335
+ passed,
336
+ failed,
337
+ unverified,
338
+ controls: catControls.map((r) => ({
339
+ controlId: r.control.id,
340
+ name: r.control.name,
341
+ level: r.control.level,
342
+ status: r.status,
343
+ findings: r.findings,
344
+ remediation: r.remediation,
345
+ })),
346
+ });
347
+ }
348
+ const rating = (0, index_1.calculateRating)(l1Compliance, l2Compliance, l3Compliance, level);
349
+ return {
350
+ benchmark: index_1.OASB_1_NAME,
351
+ version: index_1.OASB_1_VERSION,
352
+ level,
353
+ timestamp: new Date(),
354
+ compliance: overallCompliance,
355
+ l1Compliance,
356
+ l2Compliance,
357
+ l3Compliance,
358
+ rating,
359
+ categories: categoryResults,
360
+ totalControls: controls.length,
361
+ passedControls: passedCount,
362
+ failedControls: failedCount,
363
+ unverifiedControls: unverifiedCount,
364
+ };
365
+ }
366
+ // SARIF 2.1.0 output for GitHub Security tab and IDE integration
367
+ function generateSarifOutput(benchmarkResult, findings, targetDir) {
368
+ const rules = [];
369
+ const results = [];
370
+ // Build rules and results from benchmark controls
371
+ for (const cat of benchmarkResult.categories) {
372
+ for (const ctrl of cat.controls) {
373
+ if (ctrl.status === 'failed') {
374
+ const ruleId = `OASB-1/${ctrl.controlId}`;
375
+ const severityScore = ctrl.level === 'L1' ? '8.0' : ctrl.level === 'L2' ? '6.0' : '4.0';
376
+ const sarifLevel = ctrl.level === 'L1' ? 'error' : ctrl.level === 'L2' ? 'warning' : 'note';
377
+ rules.push({
378
+ id: ruleId,
379
+ name: ctrl.name.replace(/\s+/g, ''),
380
+ shortDescription: { text: ctrl.name },
381
+ fullDescription: { text: `OASB-1 ${ctrl.level} Control: ${ctrl.name}` },
382
+ help: {
383
+ text: ctrl.remediation || `Fix the ${ctrl.name} control to achieve compliance.`,
384
+ markdown: ctrl.remediation ? `**Remediation:** ${ctrl.remediation}` : undefined,
385
+ },
386
+ helpUri: `https://oasb.ai/controls/${ctrl.controlId}`,
387
+ defaultConfiguration: { level: sarifLevel },
388
+ properties: {
389
+ 'security-severity': severityScore,
390
+ tags: ['security', 'oasb-1', ctrl.level.toLowerCase()],
391
+ },
392
+ });
393
+ // Find related findings for locations
394
+ const relatedFindings = findings.filter(f => ctrl.findings.some(cf => cf.includes(f.checkId)));
395
+ if (relatedFindings.length > 0) {
396
+ for (const finding of relatedFindings) {
397
+ results.push({
398
+ ruleId,
399
+ level: sarifLevel,
400
+ message: { text: finding.description },
401
+ locations: finding.file ? [{
402
+ physicalLocation: {
403
+ artifactLocation: { uri: finding.file.replace(targetDir + '/', '') },
404
+ region: finding.line ? { startLine: finding.line } : undefined,
405
+ },
406
+ }] : undefined,
407
+ });
408
+ }
409
+ }
410
+ else {
411
+ // No specific location, just report the control failure
412
+ results.push({
413
+ ruleId,
414
+ level: sarifLevel,
415
+ message: { text: ctrl.findings.join('; ') || `Control ${ctrl.controlId} failed` },
416
+ });
417
+ }
418
+ }
419
+ }
420
+ }
421
+ const sarif = {
422
+ $schema: 'https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json',
423
+ version: '2.1.0',
424
+ runs: [{
425
+ tool: {
426
+ driver: {
427
+ name: 'HackMyAgent',
428
+ version: index_1.VERSION,
429
+ informationUri: 'https://hackmyagent.com',
430
+ rules,
431
+ },
432
+ },
433
+ results,
434
+ }],
435
+ };
436
+ return JSON.stringify(sarif, null, 2);
437
+ }
438
+ // HTML report for shareable compliance documentation
439
+ function generateHtmlReport(result) {
440
+ const ratingColor = {
441
+ 'Certified': '#22c55e',
442
+ 'Compliant': '#22c55e',
443
+ 'Passing': '#eab308',
444
+ 'Needs Improvement': '#f97316',
445
+ 'Failing': '#ef4444',
446
+ }[result.rating] || '#94a3b8';
447
+ const ratingBg = {
448
+ 'Certified': 'rgba(34, 197, 94, 0.15)',
449
+ 'Compliant': 'rgba(34, 197, 94, 0.15)',
450
+ 'Passing': 'rgba(234, 179, 8, 0.15)',
451
+ 'Needs Improvement': 'rgba(249, 115, 22, 0.15)',
452
+ 'Failing': 'rgba(239, 68, 68, 0.15)',
453
+ }[result.rating] || 'rgba(148, 163, 184, 0.15)';
454
+ // Generate donut chart SVG
455
+ const donutRadius = 70;
456
+ const donutStroke = 14;
457
+ const donutCircumference = 2 * Math.PI * donutRadius;
458
+ const donutOffset = donutCircumference * (1 - result.compliance / 100);
459
+ const complianceColor = result.compliance >= 90 ? '#22c55e' : result.compliance >= 70 ? '#eab308' : '#ef4444';
460
+ // Generate radar chart data points
461
+ const radarCategories = result.categories.slice(0, 10); // Max 10 for radar
462
+ const radarPoints = [];
463
+ const radarLabels = [];
464
+ const radarCenter = 120;
465
+ const radarRadius = 90;
466
+ // Category name abbreviations for radar chart labels
467
+ const categoryAbbreviations = {
468
+ 'Identity & Provenance': 'Identity',
469
+ 'Capability & Authorization': 'Capability',
470
+ 'Input Security': 'Input',
471
+ 'Output Security': 'Output',
472
+ 'Credential Protection': 'Credentials',
473
+ 'Supply Chain Integrity': 'Supply Chain',
474
+ 'Agent-to-Agent Security': 'A2A Security',
475
+ 'Memory & Context Integrity': 'Memory',
476
+ 'Operational Security': 'Operations',
477
+ 'Monitoring & Response': 'Monitoring',
478
+ };
479
+ radarCategories.forEach((cat, i) => {
480
+ const angle = (Math.PI * 2 * i) / radarCategories.length - Math.PI / 2;
481
+ // Use minimum 5% so 0% categories still show on the chart edge (not at center)
482
+ const value = Math.max(0.05, cat.compliance / 100);
483
+ const x = radarCenter + Math.cos(angle) * radarRadius * value;
484
+ const y = radarCenter + Math.sin(angle) * radarRadius * value;
485
+ radarPoints.push(`${x},${y}`);
486
+ // Label position (slightly outside)
487
+ const labelX = radarCenter + Math.cos(angle) * (radarRadius + 20);
488
+ const labelY = radarCenter + Math.sin(angle) * (radarRadius + 20);
489
+ const shortName = categoryAbbreviations[cat.category] || cat.category.split(' ')[0];
490
+ radarLabels.push(`<text x="${labelX}" y="${labelY}" text-anchor="middle" dominant-baseline="middle" fill="#94a3b8" font-size="10" font-weight="500">${escapeHtml(shortName)}</text>`);
491
+ });
492
+ // Generate radar grid lines
493
+ const radarGrid = [0.25, 0.5, 0.75, 1].map(scale => {
494
+ const points = radarCategories.map((_, i) => {
495
+ const angle = (Math.PI * 2 * i) / radarCategories.length - Math.PI / 2;
496
+ const x = radarCenter + Math.cos(angle) * radarRadius * scale;
497
+ const y = radarCenter + Math.sin(angle) * radarRadius * scale;
498
+ return `${x},${y}`;
499
+ }).join(' ');
500
+ return `<polygon points="${points}" fill="none" stroke="#334155" stroke-width="1"/>`;
501
+ }).join('');
502
+ // Radar axis lines
503
+ const radarAxes = radarCategories.map((_, i) => {
504
+ const angle = (Math.PI * 2 * i) / radarCategories.length - Math.PI / 2;
505
+ const x = radarCenter + Math.cos(angle) * radarRadius;
506
+ const y = radarCenter + Math.sin(angle) * radarRadius;
507
+ return `<line x1="${radarCenter}" y1="${radarCenter}" x2="${x}" y2="${y}" stroke="#334155" stroke-width="1"/>`;
508
+ }).join('');
509
+ // Collect all controls for statistics
510
+ const allControls = result.categories.flatMap(cat => cat.controls);
511
+ const failedControls = allControls.filter(ctrl => ctrl.status === 'failed');
512
+ const passedControls = allControls.filter(ctrl => ctrl.status === 'passed');
513
+ const unverifiedControls = allControls.filter(ctrl => ctrl.status === 'unverified');
514
+ // Level breakdown stats
515
+ const levelStats = {
516
+ L1: { passed: 0, failed: 0, total: 0 },
517
+ L2: { passed: 0, failed: 0, total: 0 },
518
+ L3: { passed: 0, failed: 0, total: 0 },
519
+ };
520
+ allControls.forEach(ctrl => {
521
+ const lvl = ctrl.level;
522
+ if (levelStats[lvl]) {
523
+ levelStats[lvl].total++;
524
+ if (ctrl.status === 'passed')
525
+ levelStats[lvl].passed++;
526
+ if (ctrl.status === 'failed')
527
+ levelStats[lvl].failed++;
528
+ }
529
+ });
530
+ // Find worst category
531
+ const worstCategory = result.categories
532
+ .filter(cat => cat.passed + cat.failed > 0)
533
+ .sort((a, b) => a.compliance - b.compliance)[0];
534
+ // Security grade based on compliance
535
+ const getGrade = (pct) => {
536
+ if (pct >= 95)
537
+ return { letter: 'A+', color: '#22c55e' };
538
+ if (pct >= 90)
539
+ return { letter: 'A', color: '#22c55e' };
540
+ if (pct >= 85)
541
+ return { letter: 'B+', color: '#84cc16' };
542
+ if (pct >= 80)
543
+ return { letter: 'B', color: '#84cc16' };
544
+ if (pct >= 75)
545
+ return { letter: 'C+', color: '#eab308' };
546
+ if (pct >= 70)
547
+ return { letter: 'C', color: '#eab308' };
548
+ if (pct >= 60)
549
+ return { letter: 'D', color: '#f97316' };
550
+ return { letter: 'F', color: '#ef4444' };
551
+ };
552
+ const grade = getGrade(result.compliance);
553
+ // Generate executive summary items
554
+ const executiveSummary = failedControls.length === 0
555
+ ? '<div class="exec-item success"><span class="exec-icon">✓</span><span>All controls passing at this level</span></div>'
556
+ : failedControls.slice(0, 5).map(ctrl => `<div class="exec-item critical"><span class="exec-icon">!</span><span><strong>${ctrl.controlId}</strong>: ${escapeHtml(ctrl.name)}</span></div>`).join('') + (failedControls.length > 5 ? `<div class="exec-item warning"><span class="exec-icon">+</span><span>${failedControls.length - 5} more issues not shown</span></div>` : '');
557
+ // SVG icons for professional look (no emojis)
558
+ const icons = {
559
+ check: '<svg class="icon icon-check" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd"/></svg>',
560
+ x: '<svg class="icon icon-x" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"/></svg>',
561
+ warning: '<svg class="icon icon-warning" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd"/></svg>',
562
+ circle: '<svg class="icon icon-circle" viewBox="0 0 20 20" fill="currentColor"><circle cx="10" cy="10" r="4"/></svg>',
563
+ shield: '<svg class="icon icon-shield" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M10 1.944A11.954 11.954 0 012.166 5C2.056 5.649 2 6.319 2 7c0 5.225 3.34 9.67 8 11.317C14.66 16.67 18 12.225 18 7c0-.682-.057-1.35-.166-2A11.954 11.954 0 0110 1.944zM11 14a1 1 0 11-2 0 1 1 0 012 0zm0-7a1 1 0 10-2 0v3a1 1 0 102 0V7z" clip-rule="evenodd"/></svg>',
564
+ print: '<svg class="icon icon-print" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M5 4v3H4a2 2 0 00-2 2v3a2 2 0 002 2h1v2a2 2 0 002 2h6a2 2 0 002-2v-2h1a2 2 0 002-2V9a2 2 0 00-2-2h-1V4a2 2 0 00-2-2H7a2 2 0 00-2 2zm8 0H7v3h6V4zm0 8H7v4h6v-4z" clip-rule="evenodd"/></svg>',
565
+ };
566
+ // Category rows with collapsible sections
567
+ const categoryRows = result.categories.map((cat, catIndex) => {
568
+ const statusIcon = cat.failed === 0 ? icons.check : cat.passed > 0 ? icons.warning : icons.x;
569
+ const statusClass = cat.failed === 0 ? 'status-pass' : cat.passed > 0 ? 'status-warn' : 'status-fail';
570
+ const barColor = cat.compliance >= 90 ? '#22c55e' : cat.compliance >= 70 ? '#eab308' : '#ef4444';
571
+ const controlRows = cat.controls.map(ctrl => {
572
+ const statusSvg = ctrl.status === 'passed' ? icons.check : ctrl.status === 'failed' ? icons.x : icons.circle;
573
+ const ctrlStatusClass = ctrl.status === 'passed' ? 'status-pass' : ctrl.status === 'failed' ? 'status-fail' : 'status-unverified';
574
+ const findingsList = ctrl.findings.length > 0
575
+ ? `<ul class="findings">${ctrl.findings.map(f => `<li>${escapeHtml(f)}</li>`).join('')}</ul>`
576
+ : '';
577
+ const remediation = ctrl.remediation
578
+ ? `<div class="remediation"><strong>Remediation:</strong> ${escapeHtml(ctrl.remediation)}</div>`
579
+ : '';
580
+ return `
581
+ <tr class="control-row ${ctrl.status}">
582
+ <td class="status-cell"><span class="${ctrlStatusClass}">${statusSvg}</span></td>
583
+ <td class="id-cell"><code>${ctrl.controlId}</code></td>
584
+ <td class="name-cell">${escapeHtml(ctrl.name)}</td>
585
+ <td class="level-cell"><span class="level-badge level-${ctrl.level.toLowerCase()}">${ctrl.level}</span></td>
586
+ <td class="details-cell">${findingsList}${remediation}</td>
587
+ </tr>`;
588
+ }).join('');
589
+ return `
590
+ <div class="category" id="cat-${catIndex}">
591
+ <div class="category-header" onclick="toggleCategory(${catIndex})">
592
+ <span class="category-icon ${statusClass}">${statusIcon}</span>
593
+ <span class="category-name">${escapeHtml(cat.category)}</span>
594
+ <div class="category-meta">
595
+ <span class="category-score">${cat.passed}/${cat.passed + cat.failed}</span>
596
+ <div class="mini-bar"><div class="mini-fill" style="width: ${cat.compliance}%; background: ${barColor};"></div></div>
597
+ <span class="category-percent">${cat.compliance}%</span>
598
+ <span class="chevron">▼</span>
599
+ </div>
600
+ </div>
601
+ <div class="category-content">
602
+ <table class="controls-table">
603
+ <thead><tr><th></th><th>Control ID</th><th>Control Name</th><th>Level</th><th>Details</th></tr></thead>
604
+ <tbody>${controlRows}</tbody>
605
+ </table>
606
+ </div>
607
+ </div>`;
608
+ }).join('');
609
+ // Level description
610
+ const levelDesc = {
611
+ 'L1': 'Essential baseline security every agent should implement',
612
+ 'L2': 'Defense-in-depth for production systems',
613
+ 'L3': 'Maximum security for high-risk or regulated environments'
614
+ }[result.level] || '';
615
+ return `<!DOCTYPE html>
616
+ <html lang="en">
617
+ <head>
618
+ <meta charset="UTF-8">
619
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
620
+ <title>OASB-1 Compliance Report | ${result.rating}</title>
621
+ <style>
622
+ :root {
623
+ --bg-primary: #0a0f1a;
624
+ --bg-secondary: #111827;
625
+ --bg-tertiary: #1f2937;
626
+ --text-primary: #f1f5f9;
627
+ --text-secondary: #94a3b8;
628
+ --text-muted: #64748b;
629
+ --border: #334155;
630
+ --accent: #3b82f6;
631
+ --success: #22c55e;
632
+ --warning: #eab308;
633
+ --danger: #ef4444;
634
+ }
635
+ * { box-sizing: border-box; margin: 0; padding: 0; }
636
+ body {
637
+ font-family: 'SF Pro Display', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
638
+ background: var(--bg-primary);
639
+ color: var(--text-primary);
640
+ line-height: 1.6;
641
+ padding: 2rem;
642
+ font-size: 14px;
643
+ }
644
+ .container { max-width: 1400px; margin: 0 auto; }
645
+
646
+ /* Header */
647
+ .header {
648
+ display: flex;
649
+ justify-content: space-between;
650
+ align-items: center;
651
+ margin-bottom: 2rem;
652
+ padding: 1.5rem 2rem;
653
+ background: var(--bg-secondary);
654
+ border-radius: 12px;
655
+ border: 1px solid var(--border);
656
+ }
657
+ .header-left h1 {
658
+ font-size: 1.5rem;
659
+ font-weight: 700;
660
+ display: flex;
661
+ align-items: center;
662
+ gap: 0.75rem;
663
+ }
664
+ .header-left .meta { color: var(--text-muted); font-size: 0.8rem; margin-top: 0.25rem; }
665
+ .header-icon { display: inline-flex; margin-right: 0.5rem; }
666
+ .header-icon .icon { width: 24px; height: 24px; color: var(--accent); }
667
+ .header-right { display: flex; align-items: center; gap: 1rem; }
668
+ .rating-badge {
669
+ display: inline-block;
670
+ padding: 0.375rem 1rem;
671
+ border-radius: 6px;
672
+ font-weight: 600;
673
+ font-size: 0.875rem;
674
+ background: ${ratingBg};
675
+ color: ${ratingColor};
676
+ border: 1px solid ${ratingColor}40;
677
+ }
678
+ .level-tag {
679
+ display: inline-block;
680
+ padding: 0.375rem 1rem;
681
+ background: var(--accent);
682
+ color: white;
683
+ border-radius: 6px;
684
+ font-size: 0.875rem;
685
+ font-weight: 600;
686
+ }
687
+
688
+ /* SVG Icons */
689
+ .icon { width: 16px; height: 16px; display: inline-block; vertical-align: middle; }
690
+ .status-pass { color: var(--success); }
691
+ .status-fail { color: var(--danger); }
692
+ .status-warn { color: var(--warning); }
693
+ .status-unverified { color: var(--text-muted); }
694
+ .category-icon { display: flex; align-items: center; }
695
+ .category-icon .icon { width: 18px; height: 18px; }
696
+ .footer-btn .icon { width: 14px; height: 14px; margin-right: 0.375rem; }
697
+
698
+ /* Dashboard grid */
699
+ .dashboard {
700
+ display: grid;
701
+ grid-template-columns: 280px 1fr 300px;
702
+ gap: 1.5rem;
703
+ margin-bottom: 2rem;
704
+ }
705
+ @media (max-width: 1200px) {
706
+ .dashboard { grid-template-columns: 1fr 1fr; }
707
+ .radar-section { grid-column: span 2; }
708
+ }
709
+ @media (max-width: 768px) {
710
+ .dashboard { grid-template-columns: 1fr; }
711
+ .radar-section { grid-column: span 1; }
712
+ }
713
+
714
+ /* Score card - Prowler style */
715
+ .score-card {
716
+ background: var(--bg-secondary);
717
+ border-radius: 12px;
718
+ padding: 1.25rem;
719
+ border: 1px solid var(--border);
720
+ }
721
+ .score-header {
722
+ display: flex;
723
+ align-items: center;
724
+ gap: 1rem;
725
+ margin-bottom: 1.25rem;
726
+ padding-bottom: 1rem;
727
+ border-bottom: 1px solid var(--border);
728
+ }
729
+ .score-grade {
730
+ width: 56px;
731
+ height: 56px;
732
+ border-radius: 12px;
733
+ border: 2px solid;
734
+ display: flex;
735
+ align-items: center;
736
+ justify-content: center;
737
+ }
738
+ .grade-letter { font-size: 1.75rem; font-weight: 800; }
739
+ .score-main { flex: 1; }
740
+ .score-pct { font-size: 2rem; font-weight: 700; color: var(--text-primary); line-height: 1; }
741
+ .score-label { font-size: 0.75rem; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.05em; margin-top: 0.25rem; }
742
+
743
+ .score-bars { margin-bottom: 1rem; }
744
+ .score-bar-row {
745
+ display: flex;
746
+ align-items: center;
747
+ gap: 0.75rem;
748
+ margin-bottom: 0.5rem;
749
+ }
750
+ .bar-label { width: 50px; font-size: 0.75rem; color: var(--text-secondary); }
751
+ .bar-track { flex: 1; height: 8px; background: var(--bg-tertiary); border-radius: 4px; overflow: hidden; }
752
+ .bar-fill { height: 100%; border-radius: 4px; transition: width 0.3s; }
753
+ .bar-pass { background: var(--success); }
754
+ .bar-fail { background: var(--danger); }
755
+ .bar-manual { background: var(--text-muted); }
756
+ .bar-count { width: 24px; font-size: 0.8rem; font-weight: 600; text-align: right; color: var(--text-primary); }
757
+
758
+ .level-breakdown {
759
+ display: flex;
760
+ gap: 0.75rem;
761
+ padding: 0.75rem;
762
+ background: var(--bg-tertiary);
763
+ border-radius: 8px;
764
+ margin-bottom: 1rem;
765
+ }
766
+ .level-row { display: flex; align-items: center; gap: 0.375rem; }
767
+ .level-stat { font-size: 0.8rem; color: var(--text-secondary); }
768
+
769
+ .worst-category {
770
+ display: flex;
771
+ align-items: center;
772
+ gap: 0.5rem;
773
+ padding: 0.625rem 0.75rem;
774
+ background: rgba(239, 68, 68, 0.1);
775
+ border-radius: 6px;
776
+ border-left: 3px solid var(--danger);
777
+ }
778
+ .worst-label { font-size: 0.7rem; color: var(--danger); text-transform: uppercase; font-weight: 600; }
779
+ .worst-name { flex: 1; font-size: 0.8rem; color: var(--text-primary); }
780
+ .worst-pct { font-size: 0.85rem; font-weight: 700; }
781
+
782
+ /* Radar chart */
783
+ .radar-section {
784
+ background: var(--bg-secondary);
785
+ border-radius: 12px;
786
+ padding: 1.5rem;
787
+ border: 1px solid var(--border);
788
+ }
789
+ .radar-section h3 {
790
+ font-size: 0.85rem;
791
+ color: var(--text-secondary);
792
+ text-transform: uppercase;
793
+ letter-spacing: 0.05em;
794
+ margin-bottom: 1rem;
795
+ }
796
+ .radar-container { display: flex; justify-content: center; }
797
+
798
+ /* Executive summary */
799
+ .exec-section {
800
+ background: var(--bg-secondary);
801
+ border-radius: 12px;
802
+ padding: 1.5rem;
803
+ border: 1px solid var(--border);
804
+ }
805
+ .exec-section h3 {
806
+ font-size: 0.85rem;
807
+ color: var(--text-secondary);
808
+ text-transform: uppercase;
809
+ letter-spacing: 0.05em;
810
+ margin-bottom: 1rem;
811
+ }
812
+ .exec-item {
813
+ display: flex;
814
+ align-items: flex-start;
815
+ gap: 0.75rem;
816
+ padding: 0.75rem;
817
+ margin-bottom: 0.5rem;
818
+ border-radius: 6px;
819
+ font-size: 0.85rem;
820
+ }
821
+ .exec-item.critical { background: rgba(239, 68, 68, 0.1); border-left: 3px solid var(--danger); }
822
+ .exec-item.warning { background: rgba(234, 179, 8, 0.1); border-left: 3px solid var(--warning); }
823
+ .exec-item.success { background: rgba(34, 197, 94, 0.1); border-left: 3px solid var(--success); }
824
+ .exec-icon {
825
+ width: 20px;
826
+ height: 20px;
827
+ border-radius: 50%;
828
+ display: flex;
829
+ align-items: center;
830
+ justify-content: center;
831
+ font-weight: 700;
832
+ font-size: 0.75rem;
833
+ flex-shrink: 0;
834
+ }
835
+ .exec-item.critical .exec-icon { background: var(--danger); color: white; }
836
+ .exec-item.warning .exec-icon { background: var(--warning); color: black; }
837
+ .exec-item.success .exec-icon { background: var(--success); color: white; }
838
+
839
+ /* Categories */
840
+ .categories-header {
841
+ display: flex;
842
+ justify-content: space-between;
843
+ align-items: center;
844
+ margin-bottom: 1rem;
845
+ }
846
+ .categories-header h2 { font-size: 1.1rem; }
847
+ .expand-all {
848
+ background: var(--bg-tertiary);
849
+ border: 1px solid var(--border);
850
+ color: var(--text-secondary);
851
+ padding: 0.5rem 1rem;
852
+ border-radius: 6px;
853
+ cursor: pointer;
854
+ font-size: 0.8rem;
855
+ }
856
+ .expand-all:hover { background: var(--border); }
857
+
858
+ .category {
859
+ background: var(--bg-secondary);
860
+ border-radius: 8px;
861
+ margin-bottom: 0.75rem;
862
+ border: 1px solid var(--border);
863
+ overflow: hidden;
864
+ }
865
+ .category-header {
866
+ display: flex;
867
+ align-items: center;
868
+ gap: 0.75rem;
869
+ padding: 1rem 1.25rem;
870
+ cursor: pointer;
871
+ transition: background 0.15s;
872
+ }
873
+ .category-header:hover { background: var(--bg-tertiary); }
874
+ .category-icon { font-size: 1.1rem; }
875
+ .category-name { flex: 1; font-weight: 500; }
876
+ .category-meta { display: flex; align-items: center; gap: 0.75rem; }
877
+ .category-score { color: var(--text-secondary); font-size: 0.85rem; font-weight: 500; }
878
+ .mini-bar { width: 60px; height: 6px; background: var(--border); border-radius: 3px; overflow: hidden; }
879
+ .mini-fill { height: 100%; border-radius: 3px; }
880
+ .category-percent { color: var(--text-muted); font-size: 0.85rem; width: 40px; text-align: right; }
881
+ .chevron {
882
+ color: var(--text-muted);
883
+ font-size: 0.7rem;
884
+ transition: transform 0.2s;
885
+ margin-left: 0.5rem;
886
+ }
887
+ .category.collapsed .chevron { transform: rotate(-90deg); }
888
+ .category.collapsed .category-content { display: none; }
889
+
890
+ .category-content { border-top: 1px solid var(--border); }
891
+ .controls-table { width: 100%; border-collapse: collapse; }
892
+ .controls-table th {
893
+ padding: 0.75rem 1rem;
894
+ text-align: left;
895
+ background: var(--bg-primary);
896
+ color: var(--text-muted);
897
+ font-weight: 500;
898
+ font-size: 0.75rem;
899
+ text-transform: uppercase;
900
+ letter-spacing: 0.03em;
901
+ }
902
+ .controls-table td {
903
+ padding: 0.875rem 1rem;
904
+ border-top: 1px solid var(--border);
905
+ vertical-align: top;
906
+ }
907
+ .status-cell { width: 40px; text-align: center; }
908
+ .id-cell { width: 100px; }
909
+ .id-cell code {
910
+ background: var(--bg-tertiary);
911
+ padding: 0.2rem 0.5rem;
912
+ border-radius: 4px;
913
+ font-size: 0.8rem;
914
+ color: var(--accent);
915
+ }
916
+ .name-cell { width: 30%; }
917
+ .level-cell { width: 60px; }
918
+ .details-cell { color: var(--text-secondary); font-size: 0.85rem; }
919
+ .control-row.failed { background: rgba(239, 68, 68, 0.05); }
920
+ .control-row.unverified { opacity: 0.5; }
921
+
922
+ .level-badge {
923
+ padding: 0.2rem 0.6rem;
924
+ border-radius: 4px;
925
+ font-size: 0.7rem;
926
+ font-weight: 600;
927
+ text-transform: uppercase;
928
+ }
929
+ .level-l1 { background: #7c3aed; color: white; }
930
+ .level-l2 { background: #2563eb; color: white; }
931
+ .level-l3 { background: #059669; color: white; }
932
+
933
+ .findings {
934
+ margin: 0.25rem 0 0.5rem;
935
+ padding-left: 1.25rem;
936
+ color: #f87171;
937
+ list-style-type: disc;
938
+ }
939
+ .findings li { margin-bottom: 0.25rem; }
940
+ .remediation {
941
+ margin-top: 0.5rem;
942
+ padding: 0.625rem 0.875rem;
943
+ background: var(--bg-tertiary);
944
+ border-radius: 6px;
945
+ font-size: 0.8rem;
946
+ border-left: 3px solid var(--accent);
947
+ }
948
+
949
+ /* Footer */
950
+ .footer {
951
+ display: flex;
952
+ justify-content: space-between;
953
+ align-items: center;
954
+ margin-top: 2rem;
955
+ padding: 1.5rem;
956
+ background: var(--bg-secondary);
957
+ border-radius: 12px;
958
+ border: 1px solid var(--border);
959
+ color: var(--text-muted);
960
+ font-size: 0.85rem;
961
+ }
962
+ .footer a { color: var(--accent); text-decoration: none; }
963
+ .footer a:hover { text-decoration: underline; }
964
+ .footer-actions { display: flex; gap: 1rem; }
965
+ .footer-btn {
966
+ padding: 0.5rem 1rem;
967
+ background: var(--bg-tertiary);
968
+ border: 1px solid var(--border);
969
+ border-radius: 6px;
970
+ color: var(--text-primary);
971
+ cursor: pointer;
972
+ font-size: 0.8rem;
973
+ }
974
+ .footer-btn:hover { background: var(--border); }
975
+
976
+ /* Print styles */
977
+ @media print {
978
+ body { background: white; color: black; padding: 1rem; }
979
+ .container { max-width: 100%; }
980
+ .header, .donut-card, .radar-section, .exec-section, .category, .footer {
981
+ background: white;
982
+ border: 1px solid #ddd;
983
+ break-inside: avoid;
984
+ }
985
+ .category.collapsed .category-content { display: block !important; }
986
+ .chevron, .expand-all, .footer-actions { display: none; }
987
+ .category-header { cursor: default; }
988
+ .control-row.failed { background: #fff0f0; }
989
+ :root {
990
+ --bg-primary: white;
991
+ --bg-secondary: white;
992
+ --bg-tertiary: #f5f5f5;
993
+ --text-primary: black;
994
+ --text-secondary: #555;
995
+ --text-muted: #888;
996
+ --border: #ddd;
997
+ }
998
+ }
999
+ </style>
1000
+ </head>
1001
+ <body>
1002
+ <div class="container">
1003
+ <header class="header">
1004
+ <div class="header-left">
1005
+ <h1><span class="header-icon">${icons.shield}</span>${escapeHtml(result.benchmark)}</h1>
1006
+ <div class="meta">Version ${result.version} • Generated ${new Date(result.timestamp).toLocaleString()}</div>
1007
+ </div>
1008
+ <div class="header-right">
1009
+ <div class="rating-badge">${result.rating}</div>
1010
+ <div class="level-tag">${result.level} — ${result.level === 'L1' ? 'Essential' : result.level === 'L2' ? 'Standard' : 'Hardened'}</div>
1011
+ </div>
1012
+ </header>
1013
+
1014
+ <div class="dashboard">
1015
+ <div class="score-card">
1016
+ <div class="score-header">
1017
+ <div class="score-grade" style="background: ${grade.color}20; border-color: ${grade.color};">
1018
+ <span class="grade-letter" style="color: ${grade.color};">${grade.letter}</span>
1019
+ </div>
1020
+ <div class="score-main">
1021
+ <div class="score-pct">${result.compliance}%</div>
1022
+ <div class="score-label">Security Score</div>
1023
+ </div>
1024
+ </div>
1025
+
1026
+ <div class="score-bars">
1027
+ <div class="score-bar-row">
1028
+ <span class="bar-label">Passed</span>
1029
+ <div class="bar-track">
1030
+ <div class="bar-fill bar-pass" style="width: ${allControls.length ? (passedControls.length / allControls.length * 100) : 0}%;"></div>
1031
+ </div>
1032
+ <span class="bar-count">${passedControls.length}</span>
1033
+ </div>
1034
+ <div class="score-bar-row">
1035
+ <span class="bar-label">Failed</span>
1036
+ <div class="bar-track">
1037
+ <div class="bar-fill bar-fail" style="width: ${allControls.length ? (failedControls.length / allControls.length * 100) : 0}%;"></div>
1038
+ </div>
1039
+ <span class="bar-count">${failedControls.length}</span>
1040
+ </div>
1041
+ <div class="score-bar-row">
1042
+ <span class="bar-label">Manual</span>
1043
+ <div class="bar-track">
1044
+ <div class="bar-fill bar-manual" style="width: ${allControls.length ? (unverifiedControls.length / allControls.length * 100) : 0}%;"></div>
1045
+ </div>
1046
+ <span class="bar-count">${unverifiedControls.length}</span>
1047
+ </div>
1048
+ </div>
1049
+
1050
+ <div class="level-breakdown">
1051
+ <div class="level-row">
1052
+ <span class="level-badge level-l1">L1</span>
1053
+ <span class="level-stat">${levelStats.L1.passed}/${levelStats.L1.total}</span>
1054
+ </div>
1055
+ <div class="level-row">
1056
+ <span class="level-badge level-l2">L2</span>
1057
+ <span class="level-stat">${levelStats.L2.passed}/${levelStats.L2.total}</span>
1058
+ </div>
1059
+ <div class="level-row">
1060
+ <span class="level-badge level-l3">L3</span>
1061
+ <span class="level-stat">${levelStats.L3.passed}/${levelStats.L3.total}</span>
1062
+ </div>
1063
+ </div>
1064
+
1065
+ ${worstCategory && worstCategory.compliance < 100 ? `
1066
+ <div class="worst-category">
1067
+ <span class="worst-label">Needs Attention</span>
1068
+ <span class="worst-name">${escapeHtml(worstCategory.category)}</span>
1069
+ <span class="worst-pct" style="color: ${worstCategory.compliance < 50 ? '#ef4444' : '#eab308'};">${worstCategory.compliance}%</span>
1070
+ </div>` : ''}
1071
+ </div>
1072
+
1073
+ <div class="radar-section">
1074
+ <h3>Category Coverage</h3>
1075
+ <div class="radar-container">
1076
+ <svg width="240" height="240" viewBox="0 0 240 240">
1077
+ ${radarGrid}
1078
+ ${radarAxes}
1079
+ <polygon points="${radarPoints.join(' ')}" fill="${complianceColor}20" stroke="${complianceColor}" stroke-width="2"/>
1080
+ ${radarLabels.join('')}
1081
+ </svg>
1082
+ </div>
1083
+ </div>
1084
+
1085
+ <div class="exec-section">
1086
+ <h3>Priority Issues</h3>
1087
+ ${executiveSummary}
1088
+ ${failedControls.length > 0 ? `<div style="margin-top: 1rem; padding-top: 1rem; border-top: 1px solid var(--border); font-size: 0.8rem; color: var(--text-muted);">
1089
+ ${levelDesc}
1090
+ </div>` : ''}
1091
+ </div>
1092
+ </div>
1093
+
1094
+ <div class="categories-header">
1095
+ <h2>Control Details by Category</h2>
1096
+ <button class="expand-all" onclick="toggleAll()">Expand All</button>
1097
+ </div>
1098
+
1099
+ ${categoryRows}
1100
+
1101
+ <footer class="footer">
1102
+ <div>
1103
+ Generated by <a href="https://hackmyagent.com">HackMyAgent</a> •
1104
+ <a href="https://oasb.ai">OASB-1 Specification</a>
1105
+ </div>
1106
+ <div class="footer-actions">
1107
+ <button class="footer-btn" onclick="window.print()">${icons.print} Print / PDF</button>
1108
+ </div>
1109
+ </footer>
1110
+ </div>
1111
+
1112
+ <script>
1113
+ function toggleCategory(index) {
1114
+ const cat = document.getElementById('cat-' + index);
1115
+ cat.classList.toggle('collapsed');
1116
+ }
1117
+
1118
+ function toggleAll() {
1119
+ const categories = document.querySelectorAll('.category');
1120
+ const btn = document.querySelector('.expand-all');
1121
+ const allCollapsed = Array.from(categories).every(c => c.classList.contains('collapsed'));
1122
+
1123
+ categories.forEach(cat => {
1124
+ if (allCollapsed) {
1125
+ cat.classList.remove('collapsed');
1126
+ } else {
1127
+ cat.classList.add('collapsed');
1128
+ }
1129
+ });
1130
+
1131
+ btn.textContent = allCollapsed ? 'Collapse All' : 'Expand All';
1132
+ }
1133
+
1134
+ // Start with categories collapsed
1135
+ document.querySelectorAll('.category').forEach(cat => cat.classList.add('collapsed'));
1136
+ </script>
1137
+ </body>
1138
+ </html>`;
1139
+ }
1140
+ function escapeHtml(str) {
1141
+ return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
1142
+ }
1143
+ // SARIF output for non-benchmark secure scans
1144
+ function generateScanSarif(findings, targetDir) {
1145
+ const issues = findings.filter(f => !f.passed && !f.fixed);
1146
+ const rules = issues.map(f => ({
1147
+ id: f.checkId,
1148
+ name: f.name.replace(/\s+/g, ''),
1149
+ shortDescription: { text: f.name },
1150
+ fullDescription: { text: f.description },
1151
+ help: { text: f.fix || `Fix the ${f.name} issue.` },
1152
+ defaultConfiguration: {
1153
+ level: (f.severity === 'critical' || f.severity === 'high' ? 'error' :
1154
+ f.severity === 'medium' ? 'warning' : 'note'),
1155
+ },
1156
+ properties: {
1157
+ 'security-severity': f.severity === 'critical' ? '9.0' :
1158
+ f.severity === 'high' ? '7.0' :
1159
+ f.severity === 'medium' ? '5.0' : '3.0',
1160
+ tags: ['security', 'ai-agent', f.category],
1161
+ },
1162
+ }));
1163
+ const results = issues.map(f => ({
1164
+ ruleId: f.checkId,
1165
+ level: (f.severity === 'critical' || f.severity === 'high' ? 'error' :
1166
+ f.severity === 'medium' ? 'warning' : 'note'),
1167
+ message: { text: f.description },
1168
+ locations: f.file ? [{
1169
+ physicalLocation: {
1170
+ artifactLocation: { uri: f.file.replace(targetDir + '/', '') },
1171
+ ...(f.line ? { region: { startLine: f.line } } : {}),
1172
+ },
1173
+ }] : undefined,
1174
+ }));
1175
+ return JSON.stringify({
1176
+ $schema: 'https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json',
1177
+ version: '2.1.0',
1178
+ runs: [{
1179
+ tool: {
1180
+ driver: {
1181
+ name: 'HackMyAgent',
1182
+ version: index_1.VERSION,
1183
+ informationUri: 'https://hackmyagent.com',
1184
+ rules,
1185
+ },
1186
+ },
1187
+ results,
1188
+ }],
1189
+ }, null, 2);
1190
+ }
1191
+ // HTML report for non-benchmark secure scans
1192
+ function generateScanHtmlReport(scanResult, targetDir) {
1193
+ const issues = scanResult.findings.filter(f => !f.passed && !f.fixed);
1194
+ const fixedFindings = scanResult.findings.filter(f => f.fixed);
1195
+ const score = scanResult.score;
1196
+ const scoreColor = score >= 90 ? '#22c55e' : score >= 70 ? '#eab308' : score >= 50 ? '#f97316' : '#ef4444';
1197
+ const gradeLetters = score >= 90 ? 'A' : score >= 80 ? 'B' : score >= 70 ? 'C' : score >= 60 ? 'D' : 'F';
1198
+ const severityOrder = ['critical', 'high', 'medium', 'low'];
1199
+ const severityColors = {
1200
+ critical: '#ef4444', high: '#f97316', medium: '#eab308', low: '#22c55e',
1201
+ };
1202
+ const issueRows = issues
1203
+ .sort((a, b) => severityOrder.indexOf(a.severity) - severityOrder.indexOf(b.severity))
1204
+ .map(f => `
1205
+ <tr>
1206
+ <td><span class="severity-badge" style="background: ${severityColors[f.severity]}20; color: ${severityColors[f.severity]}; border: 1px solid ${severityColors[f.severity]}40;">${escapeHtml(f.severity.toUpperCase())}</span></td>
1207
+ <td><code>${escapeHtml(f.checkId)}</code></td>
1208
+ <td>${escapeHtml(f.description)}</td>
1209
+ <td>${f.file ? escapeHtml(f.file) + (f.line ? ':' + f.line : '') : ''}</td>
1210
+ <td>${f.fix ? escapeHtml(f.fix) : ''}</td>
1211
+ </tr>`).join('');
1212
+ const fixedRows = fixedFindings.map(f => `
1213
+ <tr>
1214
+ <td><span class="severity-badge" style="background: #22c55e20; color: #22c55e; border: 1px solid #22c55e40;">FIXED</span></td>
1215
+ <td><code>${escapeHtml(f.checkId)}</code></td>
1216
+ <td>${escapeHtml(f.description)}</td>
1217
+ <td>${f.file ? escapeHtml(f.file) : ''}</td>
1218
+ <td>${f.fixMessage ? escapeHtml(f.fixMessage) : ''}</td>
1219
+ </tr>`).join('');
1220
+ const projectTypeLabel = {
1221
+ cli: 'CLI Tool', library: 'Library', webapp: 'Web App', api: 'API Server',
1222
+ mcp: 'MCP Server', openclaw: 'OpenClaw Agent', all: 'Project',
1223
+ };
1224
+ return `<!DOCTYPE html>
1225
+ <html lang="en">
1226
+ <head>
1227
+ <meta charset="UTF-8">
1228
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
1229
+ <title>HackMyAgent Security Report | ${escapeHtml(require('path').basename(targetDir))}</title>
1230
+ <style>
1231
+ :root { --bg-primary: #0a0f1a; --bg-secondary: #111827; --bg-tertiary: #1f2937; --text-primary: #f1f5f9; --text-secondary: #94a3b8; --border: #334155; }
1232
+ * { box-sizing: border-box; margin: 0; padding: 0; }
1233
+ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: var(--bg-primary); color: var(--text-primary); line-height: 1.6; padding: 2rem; font-size: 14px; }
1234
+ .container { max-width: 1200px; margin: 0 auto; }
1235
+ h1 { font-size: 1.5rem; margin-bottom: 0.5rem; }
1236
+ .meta { color: var(--text-secondary); margin-bottom: 2rem; }
1237
+ .score-card { display: flex; align-items: center; gap: 2rem; background: var(--bg-secondary); border: 1px solid var(--border); border-radius: 12px; padding: 1.5rem; margin-bottom: 2rem; }
1238
+ .grade { font-size: 3rem; font-weight: 700; width: 80px; height: 80px; display: flex; align-items: center; justify-content: center; border-radius: 50%; border: 3px solid ${scoreColor}; }
1239
+ .score-details { flex: 1; }
1240
+ .score-num { font-size: 2rem; font-weight: 700; }
1241
+ .stats { display: flex; gap: 2rem; margin-top: 0.5rem; }
1242
+ .stat { color: var(--text-secondary); }
1243
+ .stat strong { color: var(--text-primary); }
1244
+ table { width: 100%; border-collapse: collapse; margin-top: 1rem; }
1245
+ th { text-align: left; padding: 0.75rem; background: var(--bg-secondary); border-bottom: 1px solid var(--border); color: var(--text-secondary); font-size: 0.8rem; text-transform: uppercase; }
1246
+ td { padding: 0.75rem; border-bottom: 1px solid var(--border); vertical-align: top; }
1247
+ code { background: var(--bg-tertiary); padding: 2px 6px; border-radius: 4px; font-size: 0.85em; }
1248
+ .severity-badge { padding: 2px 8px; border-radius: 4px; font-size: 0.75rem; font-weight: 600; }
1249
+ .section { margin-top: 2rem; }
1250
+ .section h2 { font-size: 1.2rem; margin-bottom: 0.5rem; }
1251
+ footer { margin-top: 3rem; padding-top: 1rem; border-top: 1px solid var(--border); color: var(--text-secondary); font-size: 0.85rem; }
1252
+ footer a { color: #3b82f6; }
1253
+ @media print { body { background: #fff; color: #000; } .score-card { border-color: #ccc; } th { background: #f3f4f6; } td { border-color: #e5e7eb; } }
1254
+ </style>
1255
+ </head>
1256
+ <body>
1257
+ <div class="container">
1258
+ <h1>HackMyAgent Security Report</h1>
1259
+ <div class="meta">${escapeHtml(projectTypeLabel[scanResult.projectType] || 'Project')} - ${escapeHtml(require('path').basename(targetDir))} - ${new Date().toISOString().split('T')[0]}</div>
1260
+
1261
+ <div class="score-card">
1262
+ <div class="grade" style="color: ${scoreColor};">${gradeLetters}</div>
1263
+ <div class="score-details">
1264
+ <div class="score-num" style="color: ${scoreColor};">${score}/${scanResult.maxScore}</div>
1265
+ <div class="stats">
1266
+ <span class="stat"><strong>${issues.length}</strong> issues</span>
1267
+ <span class="stat"><strong>${fixedFindings.length}</strong> fixed</span>
1268
+ <span class="stat"><strong>${scanResult.findings.filter(f => f.passed).length}</strong> passed</span>
1269
+ </div>
1270
+ </div>
1271
+ </div>
1272
+
1273
+ ${issues.length > 0 ? `
1274
+ <div class="section">
1275
+ <h2>Issues (${issues.length})</h2>
1276
+ <table>
1277
+ <thead><tr><th>Severity</th><th>Check</th><th>Description</th><th>Location</th><th>Remediation</th></tr></thead>
1278
+ <tbody>${issueRows}</tbody>
1279
+ </table>
1280
+ </div>` : '<div class="section"><h2>No issues found</h2></div>'}
1281
+
1282
+ ${fixedFindings.length > 0 ? `
1283
+ <div class="section">
1284
+ <h2>Auto-Fixed (${fixedFindings.length})</h2>
1285
+ <table>
1286
+ <thead><tr><th>Status</th><th>Check</th><th>Description</th><th>Location</th><th>Details</th></tr></thead>
1287
+ <tbody>${fixedRows}</tbody>
1288
+ </table>
1289
+ </div>` : ''}
1290
+
1291
+ <footer>Generated by <a href="https://hackmyagent.com">HackMyAgent</a> v${index_1.VERSION}</footer>
1292
+ </div>
1293
+ </body>
1294
+ </html>`;
1295
+ }
1296
+ // Agent Security Profile (ASP) - our differentiator format
1297
+ function generateAspOutput(benchmarkResult, scanResult, targetDir) {
1298
+ const fs = require('fs');
1299
+ const path = require('path');
1300
+ // Try to get agent name from package.json or directory name
1301
+ let agentName = path.basename(targetDir);
1302
+ let agentVersion = '0.0.0';
1303
+ try {
1304
+ const pkgPath = path.join(targetDir, 'package.json');
1305
+ if (fs.existsSync(pkgPath)) {
1306
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
1307
+ agentName = pkg.name || agentName;
1308
+ agentVersion = pkg.version || agentVersion;
1309
+ }
1310
+ }
1311
+ catch { /* ignore */ }
1312
+ // Analyze capabilities from findings
1313
+ const capabilities = {};
1314
+ const hasFilesystemAccess = scanResult.findings.some(f => f.checkId.includes('FS-') || f.description.toLowerCase().includes('filesystem'));
1315
+ const hasNetworkAccess = scanResult.findings.some(f => f.checkId.includes('NET-') || f.description.toLowerCase().includes('network'));
1316
+ const hasShellAccess = scanResult.findings.some(f => f.checkId.includes('SHELL-') || f.description.toLowerCase().includes('shell') || f.description.toLowerCase().includes('exec'));
1317
+ capabilities['filesystem'] = hasFilesystemAccess ? 'detected' : 'none';
1318
+ capabilities['network'] = hasNetworkAccess ? 'detected' : 'none';
1319
+ capabilities['shell'] = hasShellAccess ? 'detected' : 'none';
1320
+ // Credential hygiene
1321
+ const credentialFindings = scanResult.findings.filter(f => f.checkId.startsWith('CRED-'));
1322
+ const hardcodedCreds = credentialFindings.filter(f => !f.passed).length;
1323
+ // Supply chain status
1324
+ const supplyChainFindings = scanResult.findings.filter(f => f.checkId.startsWith('SKILL-') || f.checkId.startsWith('HEARTBEAT-') || f.checkId.startsWith('DEP-'));
1325
+ const signedSkills = !supplyChainFindings.some(f => f.checkId === 'SKILL-001' && !f.passed);
1326
+ const pinnedDeps = !supplyChainFindings.some(f => f.checkId === 'DEP-001' && !f.passed);
1327
+ const asp = {
1328
+ specVersion: '1.0.0',
1329
+ generatedAt: new Date().toISOString(),
1330
+ generator: {
1331
+ name: 'HackMyAgent',
1332
+ version: index_1.VERSION,
1333
+ url: 'https://hackmyagent.com',
1334
+ },
1335
+ agent: {
1336
+ name: agentName,
1337
+ version: agentVersion,
1338
+ type: scanResult.projectType,
1339
+ path: targetDir,
1340
+ },
1341
+ securityPosture: {
1342
+ benchmark: 'OASB-1',
1343
+ benchmarkVersion: benchmarkResult.version,
1344
+ level: benchmarkResult.level,
1345
+ compliance: benchmarkResult.compliance,
1346
+ rating: benchmarkResult.rating,
1347
+ l1Compliance: benchmarkResult.l1Compliance,
1348
+ l2Compliance: benchmarkResult.l2Compliance,
1349
+ l3Compliance: benchmarkResult.l3Compliance,
1350
+ },
1351
+ capabilities,
1352
+ credentials: {
1353
+ hardcodedSecrets: hardcodedCreds,
1354
+ recommendation: hardcodedCreds > 0 ? 'Move secrets to environment variables or secrets manager' : 'No hardcoded credentials detected',
1355
+ },
1356
+ supplyChain: {
1357
+ signedComponents: signedSkills,
1358
+ pinnedDependencies: pinnedDeps,
1359
+ issues: supplyChainFindings.filter(f => !f.passed).map(f => ({
1360
+ id: f.checkId,
1361
+ description: f.description,
1362
+ remediation: f.fix,
1363
+ })),
1364
+ },
1365
+ categories: benchmarkResult.categories.map(cat => ({
1366
+ name: cat.category,
1367
+ compliance: cat.compliance,
1368
+ passed: cat.passed,
1369
+ failed: cat.failed,
1370
+ unverified: cat.unverified,
1371
+ })),
1372
+ failedControls: benchmarkResult.categories.flatMap(cat => cat.controls.filter(c => c.status === 'failed').map(c => ({
1373
+ id: c.controlId,
1374
+ name: c.name,
1375
+ level: c.level,
1376
+ findings: c.findings,
1377
+ remediation: c.remediation,
1378
+ }))),
1379
+ // Attestation placeholder - could be signed in future
1380
+ attestation: {
1381
+ timestamp: new Date().toISOString(),
1382
+ // signature: null, // Future: GPG or Sigstore signature
1383
+ },
1384
+ };
1385
+ return JSON.stringify(asp, null, 2);
1386
+ }
1387
+ function printBenchmarkReport(result, verbose) {
1388
+ const ratingColors = {
1389
+ 'Certified': colors.green,
1390
+ 'Compliant': colors.green,
1391
+ 'Passing': colors.yellow,
1392
+ 'Needs Improvement': colors.yellow,
1393
+ 'Failing': colors.red,
1394
+ };
1395
+ // Header
1396
+ console.log(`\n📋 ${result.benchmark} v${result.version}`);
1397
+ console.log(`━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n`);
1398
+ // Level and rating
1399
+ const levelNames = {
1400
+ 'L1': 'Level 1 - Essential',
1401
+ 'L2': 'Level 2 - Standard',
1402
+ 'L3': 'Level 3 - Hardened',
1403
+ };
1404
+ console.log(`Level: ${levelNames[result.level]}`);
1405
+ console.log(`Rating: ${ratingColors[result.rating]}${result.rating}${RESET()}`);
1406
+ console.log(`Compliance: ${result.compliance}% (${result.passedControls}/${result.passedControls + result.failedControls} verified controls)`);
1407
+ if (result.unverifiedControls > 0) {
1408
+ console.log(`Unverified: ${result.unverifiedControls} controls require manual/forward verification`);
1409
+ }
1410
+ console.log();
1411
+ // Category breakdown
1412
+ console.log(`Categories:`);
1413
+ for (const catResult of result.categories) {
1414
+ const statusIcon = catResult.failed === 0 ? '✅' : (catResult.passed > 0 ? '🟡' : '❌');
1415
+ console.log(` ${statusIcon} ${catResult.category}: ${catResult.passed}/${catResult.passed + catResult.failed} (${catResult.compliance}%)`);
1416
+ // Show failed controls
1417
+ if (verbose || catResult.failed > 0) {
1418
+ for (const ctrl of catResult.controls) {
1419
+ if (ctrl.status === 'failed') {
1420
+ console.log(` ❌ ${ctrl.controlId}: ${ctrl.name}`);
1421
+ if (verbose) {
1422
+ for (const finding of ctrl.findings) {
1423
+ console.log(` └─ ${finding}`);
1424
+ }
1425
+ }
1426
+ }
1427
+ else if (verbose && ctrl.status === 'passed') {
1428
+ console.log(` ✅ ${ctrl.controlId}: ${ctrl.name}`);
1429
+ }
1430
+ else if (verbose && ctrl.status === 'unverified') {
1431
+ // Look up the original control to determine why it's unverified
1432
+ const originalControl = index_1.OASB_1_CATEGORIES
1433
+ .flatMap((c) => c.controls)
1434
+ .find((c) => c.id === ctrl.controlId);
1435
+ const reason = originalControl && (originalControl.verification === 'manual' || originalControl.verification === 'forward')
1436
+ ? 'manual/forward'
1437
+ : 'no scanner data';
1438
+ console.log(` ⚪ ${ctrl.controlId}: ${ctrl.name} (${reason})`);
1439
+ }
1440
+ }
1441
+ }
1442
+ }
1443
+ console.log();
1444
+ console.log(`━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`);
1445
+ // Compliance breakdown by level
1446
+ if (verbose) {
1447
+ console.log(`\nCompliance by level: L1=${result.l1Compliance}% L2=${result.l2Compliance}% L3=${result.l3Compliance}%`);
1448
+ console.log(`Legend: ⚪ = Manual/Forward verification required`);
1449
+ }
1450
+ // Show appropriate next step based on current level
1451
+ if (result.level === 'L1') {
1452
+ console.log(`\nRun 'hackmyagent secure -b oasb-1 -l L2' for stricter checks.`);
1453
+ }
1454
+ else if (result.level === 'L2') {
1455
+ console.log(`\nRun 'hackmyagent secure -b oasb-1 -l L3' for hardened requirements.`);
1456
+ }
1457
+ else {
1458
+ console.log(`\nThis is the highest maturity level (L3 - Hardened).`);
1459
+ }
1460
+ console.log(`Spec: https://oasb.ai/oasb-1\n`);
1461
+ }
1462
+ // Package name resolution for community registry reporting
1463
+ function resolvePackageName(targetDir) {
1464
+ try {
1465
+ const fs = require('fs');
1466
+ const path = require('path');
1467
+ const pkgJsonPath = path.join(targetDir, 'package.json');
1468
+ if (fs.existsSync(pkgJsonPath)) {
1469
+ const pkg = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf-8'));
1470
+ if (pkg.name)
1471
+ return pkg.name;
1472
+ }
1473
+ }
1474
+ catch { /* ignore */ }
1475
+ // Fallback: use directory name
1476
+ const path = require('path');
1477
+ return path.basename(targetDir);
1478
+ }
1479
+ function resolvePackageVersion(targetDir) {
1480
+ try {
1481
+ const fs = require('fs');
1482
+ const path = require('path');
1483
+ const pkgJsonPath = path.join(targetDir, 'package.json');
1484
+ if (fs.existsSync(pkgJsonPath)) {
1485
+ const pkg = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf-8'));
1486
+ if (pkg.version)
1487
+ return pkg.version;
1488
+ }
1489
+ }
1490
+ catch { /* ignore */ }
1491
+ return null;
1492
+ }
1493
+ program
1494
+ .command('secure')
1495
+ .description(`Scan and harden your agent setup
1496
+
1497
+ Performs 147 security checks across 30 categories:
1498
+ • Credentials: API key exposure, secrets in configs
1499
+ • MCP: Server configs, tool permissions, secrets
1500
+ • Network: TLS, interface bindings, CORS
1501
+ • Prompt: Injection defenses, role protection
1502
+ • Encryption: At-rest encryption, secure hashing
1503
+ • And 25 more categories...
1504
+
1505
+ Benchmark mode (--benchmark oasb-1):
1506
+ Run OASB-1 compliance checks with L1/L2/L3 levels.
1507
+ L1 = Essential (baseline), L2 = Standard, L3 = Hardened
1508
+
1509
+ Output formats (--format):
1510
+ text Human-readable terminal output (default)
1511
+ json Machine-readable JSON
1512
+ sarif GitHub Security tab / IDE integration
1513
+ html Shareable compliance report
1514
+ asp Agent Security Profile (our format)
1515
+
1516
+ Severities: critical, high, medium, low
1517
+ Exit code 1 if critical/high issues found (or non-compliant in benchmark mode).
1518
+
1519
+ Examples:
1520
+ $ hackmyagent secure Scan current directory
1521
+ $ hackmyagent secure ./my-project Scan specific directory
1522
+ $ hackmyagent secure --fix Auto-fix issues
1523
+ $ hackmyagent secure -b oasb-1 OASB-1 L1 compliance
1524
+ $ hackmyagent secure -b oasb-1 -l L2 OASB-1 L2 compliance
1525
+ $ hackmyagent secure -b oasb-1 -f sarif SARIF for GitHub
1526
+ $ hackmyagent secure -b oasb-1 -f html -o report.html
1527
+ $ hackmyagent secure -b oasb-1 --fail-below 80 CI threshold`)
1528
+ .argument('[directory]', 'Directory to scan (defaults to current directory)', '.')
1529
+ .option('--fix', 'Automatically fix issues where possible')
1530
+ .option('--dry-run', 'Preview fixes without applying them (use with --fix)')
1531
+ .option('--ignore <checks>', 'Comma-separated check IDs to skip (e.g., CRED-001,GIT-002)')
1532
+ .option('--json', 'Output as JSON (deprecated: use --format json)')
1533
+ .option('-f, --format <format>', 'Output format: text, json, sarif, html, asp (default: text)', 'text')
1534
+ .option('-o, --output <file>', 'Write output to file instead of stdout')
1535
+ .option('--fail-below <percent>', 'Exit 1 if compliance below threshold (0-100)')
1536
+ .option('-v, --verbose', 'Show all checks including passed ones')
1537
+ .option('-b, --benchmark <name>', 'Run benchmark compliance check (e.g., oasb-1)')
1538
+ .option('-l, --level <level>', 'Benchmark level: L1 (Essential), L2 (Standard), L3 (Hardened)', 'L1')
1539
+ .option('-c, --category <name>', 'Filter to specific benchmark category')
1540
+ .option('--deep', 'Enable LLM-powered semantic analysis (requires ANTHROPIC_API_KEY)')
1541
+ .option('--registry-report', 'Post results to OpenA2A Registry')
1542
+ .option('--no-registry', 'Skip auto-publishing results to OpenA2A Registry')
1543
+ .option('--version-id <id>', 'Registry version ID to report against')
1544
+ .option('--registry-url <url>', 'Registry URL (default: REGISTRY_URL env)')
1545
+ .option('--registry-key <key>', 'Registry API key (default: REGISTRY_API_KEY env)')
1546
+ .action(async (directory, options) => {
1547
+ try {
1548
+ const targetDir = directory.startsWith('/') ? directory : process.cwd() + '/' + directory;
1549
+ // Check if directory exists
1550
+ if (!require('fs').existsSync(targetDir)) {
1551
+ console.error(`Error: Directory '${targetDir}' does not exist.`);
1552
+ process.exit(1);
1553
+ }
1554
+ // Parse ignore list
1555
+ const ignoreList = options.ignore
1556
+ ? options.ignore.split(',').map((s) => s.trim()).filter(Boolean)
1557
+ : [];
1558
+ // Validate benchmark flag if provided
1559
+ if (options.benchmark && !(0, index_1.isValidBenchmark)(options.benchmark)) {
1560
+ console.error(`Error: Unknown benchmark '${options.benchmark}'. Available: ${index_1.AVAILABLE_BENCHMARKS.join(', ')}`);
1561
+ process.exit(1);
1562
+ }
1563
+ // Validate level if benchmark mode
1564
+ const validLevels = ['L1', 'L2', 'L3'];
1565
+ const level = (options.level?.toUpperCase() || 'L1');
1566
+ if (options.benchmark && !validLevels.includes(level)) {
1567
+ console.error(`Error: Invalid level '${options.level}'. Use: L1, L2, or L3`);
1568
+ process.exit(1);
1569
+ }
1570
+ // Determine output format (--json is deprecated alias for --format json)
1571
+ const validFormats = ['text', 'json', 'sarif', 'html', 'asp'];
1572
+ const format = options.json ? 'json' : (options.format || 'text');
1573
+ if (!validFormats.includes(format)) {
1574
+ console.error(`Error: Invalid format '${format}'. Use: ${validFormats.join(', ')}`);
1575
+ process.exit(1);
1576
+ }
1577
+ // Parse fail threshold
1578
+ const failBelow = options.failBelow ? parseInt(options.failBelow, 10) : undefined;
1579
+ if (failBelow !== undefined && (isNaN(failBelow) || failBelow < 0 || failBelow > 100)) {
1580
+ console.error(`Error: --fail-below must be a number between 0 and 100`);
1581
+ process.exit(1);
1582
+ }
1583
+ // Only show progress for text output
1584
+ if (format === 'text') {
1585
+ if (options.dryRun) {
1586
+ console.log(`\n🔍 Scanning ${targetDir} (dry-run)...\n`);
1587
+ }
1588
+ else {
1589
+ console.log(`\n🔍 Scanning ${targetDir}...\n`);
1590
+ }
1591
+ }
1592
+ // Deep mode progress display
1593
+ const isDeep = options.deep ?? false;
1594
+ const onProgress = isDeep && format === 'text'
1595
+ ? (msg) => process.stdout.write(msg)
1596
+ : undefined;
1597
+ if (isDeep && format === 'text') {
1598
+ if (!process.env.ANTHROPIC_API_KEY) {
1599
+ console.log(`Layer 3: Semantic analysis — skipped (no ANTHROPIC_API_KEY)`);
1600
+ console.log(` Tip: Add HackMyAgent as an MCP server for free LLM analysis:`);
1601
+ console.log(` npx hackmyagent init-mcp\n`);
1602
+ }
1603
+ }
1604
+ const scanner = new index_1.HardeningScanner();
1605
+ const result = await scanner.scan({
1606
+ targetDir,
1607
+ autoFix: options.fix ?? false,
1608
+ dryRun: options.dryRun ?? false,
1609
+ ignore: ignoreList,
1610
+ deep: isDeep,
1611
+ onProgress,
1612
+ });
1613
+ // Benchmark mode - output compliance report
1614
+ if (options.benchmark) {
1615
+ // Use allFindings (unfiltered) for accurate benchmark evaluation
1616
+ const benchmarkResult = generateBenchmarkReport(result.allFindings || result.findings, level, options.category);
1617
+ // Output based on format
1618
+ let output;
1619
+ switch (format) {
1620
+ case 'json':
1621
+ output = JSON.stringify(benchmarkResult, null, 2);
1622
+ break;
1623
+ case 'sarif':
1624
+ output = generateSarifOutput(benchmarkResult, result.findings, targetDir);
1625
+ break;
1626
+ case 'html':
1627
+ output = generateHtmlReport(benchmarkResult);
1628
+ break;
1629
+ case 'asp':
1630
+ output = generateAspOutput(benchmarkResult, result, targetDir);
1631
+ break;
1632
+ default: // text
1633
+ printBenchmarkReport(benchmarkResult, options.verbose ?? false);
1634
+ output = '';
1635
+ }
1636
+ // Write output
1637
+ if (output) {
1638
+ if (options.output) {
1639
+ require('fs').writeFileSync(options.output, output);
1640
+ console.error(`Report written to ${options.output}`);
1641
+ }
1642
+ else {
1643
+ console.log(output);
1644
+ }
1645
+ }
1646
+ // Check fail threshold
1647
+ if (failBelow !== undefined && benchmarkResult.compliance < failBelow) {
1648
+ console.error(`Compliance ${benchmarkResult.compliance}% is below threshold ${failBelow}%`);
1649
+ process.exit(1);
1650
+ }
1651
+ // Exit with non-zero if failing or needs improvement (default behavior)
1652
+ if (failBelow === undefined && (benchmarkResult.rating === 'Failing' || benchmarkResult.rating === 'Needs Improvement')) {
1653
+ process.exit(1);
1654
+ }
1655
+ return;
1656
+ }
1657
+ if (format === 'json') {
1658
+ console.log(JSON.stringify(result, null, 2));
1659
+ return;
1660
+ }
1661
+ // Handle SARIF/HTML/ASP for non-benchmark mode
1662
+ if (format === 'sarif') {
1663
+ const output = generateScanSarif(result.findings, targetDir);
1664
+ if (options.output) {
1665
+ require('fs').writeFileSync(options.output, output);
1666
+ console.error(`Report written to ${options.output}`);
1667
+ }
1668
+ else {
1669
+ console.log(output);
1670
+ }
1671
+ const critHigh = result.findings.filter((f) => !f.passed && !f.fixed && (f.severity === 'critical' || f.severity === 'high'));
1672
+ if (critHigh.length > 0)
1673
+ process.exit(1);
1674
+ return;
1675
+ }
1676
+ if (format === 'html') {
1677
+ const output = generateScanHtmlReport(result, targetDir);
1678
+ if (options.output) {
1679
+ require('fs').writeFileSync(options.output, output);
1680
+ console.error(`Report written to ${options.output}`);
1681
+ }
1682
+ else {
1683
+ console.log(output);
1684
+ }
1685
+ const critHigh = result.findings.filter((f) => !f.passed && !f.fixed && (f.severity === 'critical' || f.severity === 'high'));
1686
+ if (critHigh.length > 0)
1687
+ process.exit(1);
1688
+ return;
1689
+ }
1690
+ // Filter to only show failed findings (issues)
1691
+ const issues = result.findings.filter((f) => !f.passed && !f.fixed);
1692
+ const fixedFindings = result.findings.filter((f) => f.fixed);
1693
+ // Print header - clean and simple
1694
+ const projectTypeLabel = {
1695
+ cli: 'CLI Tool',
1696
+ library: 'Library',
1697
+ webapp: 'Web App',
1698
+ api: 'API Server',
1699
+ mcp: 'MCP Server',
1700
+ openclaw: 'OpenClaw Agent',
1701
+ all: 'Project',
1702
+ }[result.projectType] || 'Project';
1703
+ let scoreExtra = '';
1704
+ if (result.semanticAnalysis) {
1705
+ const sa = result.semanticAnalysis;
1706
+ scoreExtra = ` | Semantic: ${sa.layer2Findings} structural`;
1707
+ if (sa.layer3Findings > 0) {
1708
+ scoreExtra += `, ${sa.layer3Findings} LLM`;
1709
+ if (sa.llmCost !== undefined)
1710
+ scoreExtra += ` ($${sa.llmCost.toFixed(3)})`;
1711
+ if (sa.cachedResults)
1712
+ scoreExtra += ` (${sa.cachedResults} cached)`;
1713
+ }
1714
+ }
1715
+ console.log(`${projectTypeLabel} | Score: ${result.score}/${result.maxScore}${scoreExtra}\n`);
1716
+ // No issues? Say so and exit
1717
+ if (issues.length === 0 && fixedFindings.length === 0) {
1718
+ console.log(`${colors.green}No issues found.${RESET()}\n`);
1719
+ }
1720
+ else if (issues.length > 0) {
1721
+ // Print issues - clean format
1722
+ console.log(`${issues.length} issue${issues.length === 1 ? '' : 's'} found:\n`);
1723
+ for (const finding of issues) {
1724
+ const display = SEVERITY_DISPLAY[finding.severity];
1725
+ const location = finding.file
1726
+ ? finding.line
1727
+ ? `${finding.file}:${finding.line}`
1728
+ : finding.file
1729
+ : '';
1730
+ // Format: SEVERITY file:line
1731
+ // Description
1732
+ // Fix: command
1733
+ console.log(`${display.color()}${display.symbol} ${finding.severity.toUpperCase()}${RESET()} ${location}`);
1734
+ console.log(` ${finding.description}`);
1735
+ if (finding.fix) {
1736
+ console.log(` ${colors.cyan}Fix:${RESET()} ${finding.fix}`);
1737
+ }
1738
+ console.log();
1739
+ }
1740
+ }
1741
+ // Print fixed findings
1742
+ if (fixedFindings.length > 0) {
1743
+ console.log(`${colors.green}Fixed ${fixedFindings.length} issue${fixedFindings.length === 1 ? '' : 's'}:${RESET()}`);
1744
+ for (const finding of fixedFindings) {
1745
+ const location = finding.file || '';
1746
+ console.log(` ${colors.green}✓${RESET()} ${location} - ${finding.name}`);
1747
+ }
1748
+ console.log();
1749
+ if (result.backupPath) {
1750
+ console.log(`Backup: ${result.backupPath}`);
1751
+ console.log(`Undo: hackmyagent rollback ${directory}\n`);
1752
+ }
1753
+ }
1754
+ // Registry reporting: auto-publish to community endpoint by default
1755
+ const shouldReport = options.registryReport || (options.registry !== false);
1756
+ if (shouldReport) {
1757
+ try {
1758
+ const core = await Promise.resolve().then(() => __importStar(require('./index')));
1759
+ const registryUrl = options.registryUrl || process.env.REGISTRY_URL || 'https://registry.opena2a.org';
1760
+ if (options.versionId) {
1761
+ // Authenticated path: existing behavior (version-id + API key)
1762
+ const registryKey = options.registryKey || process.env.REGISTRY_API_KEY;
1763
+ if (!registryKey) {
1764
+ console.error('Error: --registry-key or REGISTRY_API_KEY env is required when using --version-id');
1765
+ process.exit(1);
1766
+ }
1767
+ const client = new core.RegistryClient({ registryUrl, apiKey: registryKey });
1768
+ const payload = core.buildScanReport(options.versionId, result.findings);
1769
+ await client.reportScanResult(payload);
1770
+ console.log(`Registry: scan results reported for version ${options.versionId}`);
1771
+ }
1772
+ else if (typeof core.buildCommunityReport === 'function') {
1773
+ // Community path: request scan token, then submit results
1774
+ const client = new core.RegistryClient({ registryUrl, apiKey: '' });
1775
+ const packageName = resolvePackageName(targetDir);
1776
+ if (packageName) {
1777
+ const packageVersion = resolvePackageVersion(targetDir);
1778
+ const tokenResp = typeof client.requestScanToken === 'function'
1779
+ ? await client.requestScanToken(packageName, { version: packageVersion ?? undefined })
1780
+ : null;
1781
+ const payload = core.buildCommunityReport(packageName, result.findings, {
1782
+ version: packageVersion ?? undefined,
1783
+ });
1784
+ const resp = typeof client.reportCommunityResult === 'function'
1785
+ ? await client.reportCommunityResult(payload, tokenResp?.scanToken)
1786
+ : { status: 'skipped' };
1787
+ if (resp.status === 'accepted') {
1788
+ console.log('Registry: scan shared with OpenA2A community');
1789
+ }
1790
+ }
1791
+ }
1792
+ }
1793
+ catch (reportErr) {
1794
+ console.error(`Registry: failed to report scan results: ${reportErr.message || reportErr}`);
1795
+ }
1796
+ }
1797
+ // Star prompt (interactive TTY only, text format only)
1798
+ if (process.stdout.isTTY) {
1799
+ console.log(`${colors.cyan}Helpful?${RESET()} Star the project: https://github.com/opena2a-org/opena2a\n`);
1800
+ }
1801
+ // Exit with non-zero if critical/high issues remain
1802
+ const criticalOrHigh = issues.filter((f) => f.severity === 'critical' || f.severity === 'high');
1803
+ if (criticalOrHigh.length > 0) {
1804
+ process.exit(1);
1805
+ }
1806
+ }
1807
+ catch (error) {
1808
+ console.error(`Error: ${error instanceof Error ? error.message : 'Unknown error'}`);
1809
+ process.exit(1);
1810
+ }
1811
+ });
1812
+ // Severity display for external scan findings
1813
+ const FINDING_SEVERITY_DISPLAY = {
1814
+ critical: { symbol: '🔴', color: () => colors.brightRed },
1815
+ high: { symbol: '🟠', color: () => colors.red },
1816
+ medium: { symbol: '🟡', color: () => colors.yellow },
1817
+ low: { symbol: '🟢', color: () => colors.green },
1818
+ };
1819
+ function groupExternalFindingsBySeverity(findings) {
1820
+ const grouped = {
1821
+ critical: [],
1822
+ high: [],
1823
+ medium: [],
1824
+ low: [],
1825
+ };
1826
+ for (const finding of findings) {
1827
+ grouped[finding.severity].push(finding);
1828
+ }
1829
+ return grouped;
1830
+ }
1831
+ // OpenClaw-specific check categories
1832
+ const OPENCLAW_CATEGORIES = ['skill', 'heartbeat', 'gateway', 'config', 'supply'];
1833
+ function detectOpenClawDirectory(providedDir) {
1834
+ const os = require('os');
1835
+ const fs = require('fs');
1836
+ const path = require('path');
1837
+ // If user provided a directory, use it
1838
+ if (providedDir && providedDir !== '') {
1839
+ return providedDir.startsWith('/') ? providedDir : path.join(process.cwd(), providedDir);
1840
+ }
1841
+ // Auto-detect common OpenClaw/Moltbot installation directories
1842
+ const homeDir = os.homedir();
1843
+ const candidates = [
1844
+ path.join(homeDir, '.openclaw'),
1845
+ path.join(homeDir, '.moltbot'),
1846
+ path.join(homeDir, '.clawdbot'),
1847
+ ];
1848
+ for (const candidate of candidates) {
1849
+ if (fs.existsSync(candidate)) {
1850
+ return candidate;
1851
+ }
1852
+ }
1853
+ // Fall back to current working directory
1854
+ return process.cwd();
1855
+ }
1856
+ function filterOpenClawFindings(findings) {
1857
+ return findings.filter((f) => {
1858
+ const checkId = f.checkId.toLowerCase();
1859
+ return OPENCLAW_CATEGORIES.some((cat) => checkId.includes(cat));
1860
+ });
1861
+ }
1862
+ function assessRiskLevel(findings) {
1863
+ const criticalCount = findings.filter((f) => f.severity === 'critical').length;
1864
+ const highCount = findings.filter((f) => f.severity === 'high').length;
1865
+ const mediumCount = findings.filter((f) => f.severity === 'medium').length;
1866
+ if (criticalCount > 0) {
1867
+ return {
1868
+ level: 'CRITICAL',
1869
+ color: colors.brightRed,
1870
+ description: 'Immediate action required. Your OpenClaw installation has critical vulnerabilities.',
1871
+ };
1872
+ }
1873
+ if (highCount > 0) {
1874
+ return {
1875
+ level: 'HIGH',
1876
+ color: colors.red,
1877
+ description: 'Significant risks detected. Address high-severity issues promptly.',
1878
+ };
1879
+ }
1880
+ if (mediumCount > 0) {
1881
+ return {
1882
+ level: 'MODERATE',
1883
+ color: colors.yellow,
1884
+ description: 'Some issues found. Review and address when possible.',
1885
+ };
1886
+ }
1887
+ return {
1888
+ level: 'LOW',
1889
+ color: colors.green,
1890
+ description: 'Your OpenClaw installation appears well-secured.',
1891
+ };
1892
+ }
1893
+ program
1894
+ .command('secure-openclaw')
1895
+ .description(`Security scan specifically for OpenClaw/Moltbot installations
1896
+
1897
+ Performs focused security checks for OpenClaw agent deployments:
1898
+ • Skill validation: Permission scopes, signature verification
1899
+ • Heartbeat security: Endpoint exposure, authentication
1900
+ • Gateway configs: Routing rules, rate limiting
1901
+ • Config files: Secret exposure, insecure defaults
1902
+ • Supply chain: Dependency vulnerabilities, integrity
1903
+
1904
+ Auto-detects ~/.openclaw, ~/.moltbot, or ~/.clawdbot directories.
1905
+ Exit code 1 if critical/high issues found.
1906
+
1907
+ Examples:
1908
+ $ hackmyagent secure-openclaw Scan auto-detected directory
1909
+ $ hackmyagent secure-openclaw ~/.openclaw Scan specific directory
1910
+ $ hackmyagent secure-openclaw --fix Auto-fix issues
1911
+ $ hackmyagent secure-openclaw --json JSON output for CI`)
1912
+ .argument('[directory]', 'Directory to scan (default: ~/.openclaw or ~/.moltbot)', '')
1913
+ .option('--fix', 'Automatically fix issues where possible')
1914
+ .option('--dry-run', 'Preview fixes without applying them (use with --fix)')
1915
+ .option('--json', 'Output as JSON (for scripting/CI)')
1916
+ .option('-v, --verbose', 'Show all checks including passed ones')
1917
+ .action(async (directory, options) => {
1918
+ try {
1919
+ const targetDir = detectOpenClawDirectory(directory);
1920
+ if (!options.json) {
1921
+ console.log(`\n🦞 OpenClaw Security Report`);
1922
+ console.log(`━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n`);
1923
+ if (options.dryRun) {
1924
+ console.log(`🔍 Scanning ${targetDir} (dry-run - previewing fixes)...\n`);
1925
+ }
1926
+ else if (options.fix) {
1927
+ console.log(`🔧 Scanning and fixing ${targetDir}...\n`);
1928
+ console.log(`${colors.yellow}Auto-fix will:${RESET()}`);
1929
+ console.log(` • Bind gateway to 127.0.0.1 (local-only)`);
1930
+ console.log(` • Replace plaintext tokens with env var references`);
1931
+ console.log(` • Enable approval confirmations`);
1932
+ console.log(` • Enable sandbox mode`);
1933
+ console.log(`\n${colors.cyan}A backup will be created for rollback if needed.${RESET()}\n`);
1934
+ }
1935
+ else {
1936
+ console.log(`🔍 Scanning ${targetDir}...\n`);
1937
+ }
1938
+ }
1939
+ const scanner = new index_1.HardeningScanner();
1940
+ const result = await scanner.scan({
1941
+ targetDir,
1942
+ autoFix: options.fix ?? false,
1943
+ dryRun: options.dryRun ?? false,
1944
+ ignore: [],
1945
+ });
1946
+ // Filter to OpenClaw-specific findings
1947
+ const allOpenClawFindings = filterOpenClawFindings(result.findings);
1948
+ const issues = allOpenClawFindings.filter((f) => !f.passed && !f.fixed);
1949
+ const fixedFindings = allOpenClawFindings.filter((f) => f.fixed);
1950
+ const passedFindings = allOpenClawFindings.filter((f) => f.passed);
1951
+ if (options.json) {
1952
+ const jsonOutput = {
1953
+ target: targetDir,
1954
+ riskLevel: assessRiskLevel(issues).level,
1955
+ totalChecks: allOpenClawFindings.length,
1956
+ issues: issues.length,
1957
+ fixed: fixedFindings.length,
1958
+ passed: passedFindings.length,
1959
+ findings: allOpenClawFindings,
1960
+ };
1961
+ console.log(JSON.stringify(jsonOutput, null, 2));
1962
+ return;
1963
+ }
1964
+ // Risk assessment
1965
+ const risk = assessRiskLevel(issues);
1966
+ console.log(`Risk Level: ${risk.color}${risk.level}${RESET()}`);
1967
+ console.log(`${risk.description}\n`);
1968
+ // Summary stats
1969
+ console.log(`Checks: ${allOpenClawFindings.length} total | ${issues.length} issues | ${fixedFindings.length} fixed | ${passedFindings.length} passed\n`);
1970
+ // Show issues
1971
+ if (issues.length > 0) {
1972
+ console.log(`${colors.red}Issues Found:${RESET()}\n`);
1973
+ for (const finding of issues) {
1974
+ const display = SEVERITY_DISPLAY[finding.severity];
1975
+ const location = finding.file
1976
+ ? finding.line
1977
+ ? `${finding.file}:${finding.line}`
1978
+ : finding.file
1979
+ : '';
1980
+ console.log(`${display.color()}${display.symbol} [${finding.checkId}] ${finding.severity.toUpperCase()}${RESET()}`);
1981
+ console.log(` ${finding.description}`);
1982
+ if (location) {
1983
+ console.log(` File: ${location}`);
1984
+ }
1985
+ if (finding.fix) {
1986
+ console.log(` ${colors.cyan}Fix:${RESET()} ${finding.fix}`);
1987
+ }
1988
+ console.log();
1989
+ }
1990
+ }
1991
+ else {
1992
+ console.log(`${colors.green}No OpenClaw-specific issues found.${RESET()}\n`);
1993
+ }
1994
+ // Show fixed findings
1995
+ if (fixedFindings.length > 0) {
1996
+ console.log(`${colors.green}✅ Auto-Remediation Applied:${RESET()}\n`);
1997
+ for (const finding of fixedFindings) {
1998
+ console.log(` ${colors.green}✓${RESET()} [${finding.checkId}] ${finding.name}`);
1999
+ if (finding.fixMessage) {
2000
+ console.log(` ${colors.cyan}→${RESET()} ${finding.fixMessage}`);
2001
+ }
2002
+ }
2003
+ console.log();
2004
+ if (result.backupPath) {
2005
+ console.log(`${colors.yellow}📁 Backup created:${RESET()} ${result.backupPath}`);
2006
+ console.log(`${colors.yellow}↩️ To rollback:${RESET()} hackmyagent rollback ${targetDir}`);
2007
+ console.log();
2008
+ console.log(`${colors.cyan}Note:${RESET()} If you replaced tokens with env vars, set OPENCLAW_AUTH_TOKEN`);
2009
+ console.log(` in your environment before starting OpenClaw.\n`);
2010
+ }
2011
+ }
2012
+ // Show passed checks in verbose mode
2013
+ if (options.verbose && passedFindings.length > 0) {
2014
+ console.log(`${colors.green}Passed Checks:${RESET()}`);
2015
+ for (const finding of passedFindings) {
2016
+ console.log(` ${colors.green}✓${RESET()} [${finding.checkId}] ${finding.name}`);
2017
+ }
2018
+ console.log();
2019
+ }
2020
+ console.log(`━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`);
2021
+ console.log(`Run 'hackmyagent secure' for a full security scan.\n`);
2022
+ // Exit with non-zero if critical/high issues remain
2023
+ const criticalOrHigh = issues.filter((f) => f.severity === 'critical' || f.severity === 'high');
2024
+ if (criticalOrHigh.length > 0) {
2025
+ process.exit(1);
2026
+ }
2027
+ }
2028
+ catch (error) {
2029
+ console.error(`Error: ${error instanceof Error ? error.message : 'Unknown error'}`);
2030
+ process.exit(1);
2031
+ }
2032
+ });
2033
+ program
2034
+ .command('scan')
2035
+ .description(`Scan external target for exposed MCP endpoints
2036
+
2037
+ Detects externally exposed:
2038
+ • MCP SSE/tools endpoints
2039
+ • Configuration files (mcp.json, settings)
2040
+ • API keys in responses
2041
+ • Debug/admin interfaces
2042
+
2043
+ Scoring: A (90-100), B (80-89), C (70-79), D (60-69), F (<60)
2044
+ Exit code 1 if critical/high issues found.
2045
+
2046
+ Examples:
2047
+ $ hackmyagent scan example.com
2048
+ $ hackmyagent scan 192.168.1.100 -p 3000,8080
2049
+ $ hackmyagent scan example.com --verbose
2050
+ $ hackmyagent scan example.com --json`)
2051
+ .argument('<target>', 'Target hostname or IP address')
2052
+ .option('--json', 'Output as JSON (for scripting/CI)')
2053
+ .option('-p, --ports <ports>', 'Comma-separated ports to scan (default: common MCP ports)')
2054
+ .option('-t, --timeout <ms>', 'Connection timeout in milliseconds', '5000')
2055
+ .option('-v, --verbose', 'Show detailed finding information')
2056
+ .action(async (target, options) => {
2057
+ try {
2058
+ console.log(`\n🔍 Scanning ${target}...\n`);
2059
+ const scanner = new index_1.ExternalScanner();
2060
+ const customPorts = options.ports
2061
+ ? options.ports.split(',').map((p) => parseInt(p.trim(), 10))
2062
+ : undefined;
2063
+ const result = await scanner.scan(target, {
2064
+ ports: customPorts,
2065
+ timeout: parseInt(options.timeout ?? '5000', 10),
2066
+ });
2067
+ if (options.json) {
2068
+ console.log(JSON.stringify(result, null, 2));
2069
+ return;
2070
+ }
2071
+ // Print header
2072
+ const gradeColor = result.grade === 'A'
2073
+ ? colors.green
2074
+ : result.grade === 'B'
2075
+ ? colors.green
2076
+ : result.grade === 'C'
2077
+ ? colors.yellow
2078
+ : colors.red;
2079
+ console.log(`Target: ${result.target}`);
2080
+ console.log(`Score: ${gradeColor}${result.score}/100 (${result.grade})${RESET()}`);
2081
+ console.log(`Open Ports: ${result.openPorts.length > 0 ? result.openPorts.join(', ') : 'None detected'}`);
2082
+ console.log(`Duration: ${result.duration}ms\n`);
2083
+ if (result.findings.length === 0) {
2084
+ console.log(`${colors.green}✅ No security issues found!${RESET()}\n`);
2085
+ return;
2086
+ }
2087
+ // Group findings by severity
2088
+ const grouped = groupExternalFindingsBySeverity(result.findings);
2089
+ // Print findings by severity
2090
+ for (const severity of ['critical', 'high', 'medium', 'low']) {
2091
+ const findings = grouped[severity];
2092
+ if (findings.length === 0)
2093
+ continue;
2094
+ const display = FINDING_SEVERITY_DISPLAY[severity];
2095
+ console.log(`${display.color()}${display.symbol} ${severity.toUpperCase()} (${findings.length})${RESET()}`);
2096
+ for (const finding of findings) {
2097
+ console.log(` • [${finding.checkId}] ${finding.title}`);
2098
+ if (finding.port) {
2099
+ console.log(` Port: ${finding.port}${finding.path ? `, Path: ${finding.path}` : ''}`);
2100
+ }
2101
+ if (options.verbose) {
2102
+ console.log(` ${finding.description}`);
2103
+ console.log(` Evidence: ${finding.evidence}`);
2104
+ console.log(` Impact: ${finding.impact}`);
2105
+ console.log(` Fix: ${finding.fix}`);
2106
+ }
2107
+ }
2108
+ console.log();
2109
+ }
2110
+ // Exit with non-zero if critical/high issues found
2111
+ const criticalOrHigh = result.findings.filter((f) => f.severity === 'critical' || f.severity === 'high');
2112
+ if (criticalOrHigh.length > 0) {
2113
+ process.exit(1);
2114
+ }
2115
+ }
2116
+ catch (error) {
2117
+ console.error(`Error: ${error instanceof Error ? error.message : 'Unknown error'}`);
2118
+ process.exit(1);
2119
+ }
2120
+ });
2121
+ program
2122
+ .command('rollback')
2123
+ .description(`Rollback auto-fix changes to the most recent backup
2124
+
2125
+ Restores files to their state before the last --fix operation.
2126
+ Backups are stored in .hackmyagent-backup/ with timestamps.
2127
+
2128
+ Examples:
2129
+ $ hackmyagent rollback Rollback current directory
2130
+ $ hackmyagent rollback ./my-project Rollback specific directory`)
2131
+ .argument('[directory]', 'Directory to rollback (defaults to current directory)', '.')
2132
+ .action(async (directory) => {
2133
+ try {
2134
+ const targetDir = directory.startsWith('/') ? directory : process.cwd() + '/' + directory;
2135
+ console.log(`\n🔄 Rolling back changes in ${targetDir}...\n`);
2136
+ const scanner = new index_1.HardeningScanner();
2137
+ await scanner.rollback(targetDir);
2138
+ console.log(`${colors.green}✅ Rollback successful!${RESET()}`);
2139
+ console.log(' All auto-fix changes have been reverted.\n');
2140
+ }
2141
+ catch (error) {
2142
+ console.error(`Error: ${error instanceof Error ? error.message : 'Unknown error'}`);
2143
+ process.exit(1);
2144
+ }
2145
+ });
2146
+ // Attack command - adversarial security testing
2147
+ const ATTACK_CATEGORY_NAMES = Object.keys(index_1.ATTACK_CATEGORIES);
2148
+ program
2149
+ .command('attack')
2150
+ .description(`Adversarial security testing for AI agents
2151
+
2152
+ Red team your AI agent with ${index_1.PAYLOAD_STATS.total} attack payloads across 7 categories:
2153
+ • Prompt Injection: ${index_1.PAYLOAD_STATS.byCategory['prompt-injection']} payloads
2154
+ • Jailbreaking: ${index_1.PAYLOAD_STATS.byCategory['jailbreak']} payloads
2155
+ • Data Exfiltration: ${index_1.PAYLOAD_STATS.byCategory['data-exfiltration']} payloads
2156
+ • Capability Abuse: ${index_1.PAYLOAD_STATS.byCategory['capability-abuse']} payloads
2157
+ • Context Manipulation: ${index_1.PAYLOAD_STATS.byCategory['context-manipulation']} payloads
2158
+ • MCP Exploitation: ${index_1.PAYLOAD_STATS.byCategory['mcp-exploitation']} payloads
2159
+ • A2A Attacks: ${index_1.PAYLOAD_STATS.byCategory['a2a-attack']} payloads
2160
+
2161
+ Intensity levels (controls how many payloads run):
2162
+ passive Observation only (${index_1.PAYLOAD_STATS.byIntensity.passive} payloads)
2163
+ active Standard payloads (${index_1.PAYLOAD_STATS.byIntensity.passive + index_1.PAYLOAD_STATS.byIntensity.active} payloads, default)
2164
+ aggressive All payloads including creative/risky (${index_1.PAYLOAD_STATS.total} payloads)
2165
+
2166
+ Target types:
2167
+ api OpenAI/Anthropic chat completions (default)
2168
+ mcp MCP JSON-RPC server (tools/call, tools/list)
2169
+ a2a A2A agent messaging endpoint (/a2a/message)
2170
+ local Local simulation (no API calls)
2171
+
2172
+ Target types:
2173
+ api OpenAI/Anthropic chat completions (default)
2174
+ mcp MCP JSON-RPC server (tools/call, tools/list)
2175
+ a2a A2A agent messaging endpoint (/a2a/message)
2176
+ local Local simulation (no API calls)
2177
+
2178
+ Examples:
2179
+ $ hackmyagent attack https://api.example.com/v1/chat
2180
+ $ hackmyagent attack https://api.example.com --intensity aggressive
2181
+ $ hackmyagent attack https://api.example.com --category prompt-injection
2182
+ $ hackmyagent attack --local --system-prompt "You are a helpful assistant"
2183
+ $ hackmyagent attack https://api.example.com -f sarif -o results.sarif
2184
+ $ hackmyagent attack https://api.example.com --payload-file custom.json
2185
+ $ hackmyagent attack https://api.example.com --fail-on-vulnerable medium
2186
+ $ hackmyagent attack http://localhost:3010 --target-type mcp --category mcp-exploitation
2187
+ $ hackmyagent attack http://localhost:3020 --target-type a2a --category a2a-attack`)
2188
+ .argument('[target]', 'API endpoint to test (or use --local for simulation)')
2189
+ .option('-i, --intensity <level>', 'Attack intensity: passive, active, aggressive', 'active')
2190
+ .option('-c, --category <categories>', 'Comma-separated categories to test')
2191
+ .option('--local', 'Run in local simulation mode (no actual API calls)')
2192
+ .option('-t, --target-type <type>', 'Target type: api, mcp, a2a, local', 'api')
2193
+ .option('--api-format <format>', 'API format: openai, anthropic, mcp-jsonrpc, a2a, custom', 'openai')
2194
+ .option('--model <model>', 'Model to test (for API targets)')
2195
+ .option('--system-prompt <prompt>', 'System prompt (for local testing)')
2196
+ .option('--mcp-tool <tool>', 'Default MCP tool name (for mcp targets)')
2197
+ .option('--a2a-sender <name>', 'A2A sender identity (for a2a targets)', 'attacker-agent')
2198
+ .option('--a2a-recipient <name>', 'A2A recipient identity (for a2a targets)', 'target-agent')
2199
+ .option('-H, --header <headers>', 'Headers in format "Key: Value" (can be used multiple times)')
2200
+ .option('--timeout <ms>', 'Request timeout in milliseconds', '30000')
2201
+ .option('--delay <ms>', 'Delay between requests in milliseconds', '1000')
2202
+ .option('--stop-on-success', 'Stop after first successful attack')
2203
+ .option('--payload-file <path>', 'JSON file with custom attack payloads')
2204
+ .option('--fail-on-vulnerable [severity]', 'Exit code 1 if vulnerabilities found (optional: critical/high/medium/low)')
2205
+ .option('-f, --format <format>', 'Output format: text, json, sarif, html', 'text')
2206
+ .option('-o, --output <file>', 'Write output to file')
2207
+ .option('-v, --verbose', 'Show detailed output for each payload')
2208
+ .option('--registry-report', 'Post results to OpenA2A Registry')
2209
+ .option('--no-registry', 'Skip auto-publishing results to OpenA2A Registry')
2210
+ .option('--version-id <id>', 'Registry version ID to report against')
2211
+ .option('--registry-url <url>', 'Registry URL (default: REGISTRY_URL env)')
2212
+ .option('--registry-key <key>', 'Registry API key (default: REGISTRY_API_KEY env)')
2213
+ .action(async (targetUrl, options) => {
2214
+ try {
2215
+ // Validate target
2216
+ if (!targetUrl && !options.local) {
2217
+ console.error('Error: Target URL required (or use --local for simulation)');
2218
+ process.exit(1);
2219
+ }
2220
+ // Validate intensity
2221
+ const validIntensities = ['passive', 'active', 'aggressive'];
2222
+ const intensity = (options.intensity || 'active');
2223
+ if (!validIntensities.includes(intensity)) {
2224
+ console.error(`Error: Invalid intensity '${options.intensity}'. Use: ${validIntensities.join(', ')}`);
2225
+ process.exit(1);
2226
+ }
2227
+ // Parse categories
2228
+ let categories;
2229
+ if (options.category) {
2230
+ categories = options.category.split(',').map(c => c.trim());
2231
+ for (const cat of categories) {
2232
+ if (!ATTACK_CATEGORY_NAMES.includes(cat)) {
2233
+ console.error(`Error: Invalid category '${cat}'. Use: ${ATTACK_CATEGORY_NAMES.join(', ')}`);
2234
+ process.exit(1);
2235
+ }
2236
+ }
2237
+ }
2238
+ // Parse headers
2239
+ const headers = {};
2240
+ if (options.header) {
2241
+ const headerList = Array.isArray(options.header) ? options.header : [options.header];
2242
+ for (const h of headerList) {
2243
+ const [key, ...valueParts] = h.split(':');
2244
+ if (key && valueParts.length > 0) {
2245
+ headers[key.trim()] = valueParts.join(':').trim();
2246
+ }
2247
+ }
2248
+ }
2249
+ // Determine target type
2250
+ let targetType = 'api';
2251
+ if (options.local) {
2252
+ targetType = 'local';
2253
+ }
2254
+ else if (options.targetType) {
2255
+ const validTypes = ['api', 'mcp', 'a2a', 'local'];
2256
+ if (!validTypes.includes(options.targetType)) {
2257
+ console.error(`Error: Invalid target type '${options.targetType}'. Use: ${validTypes.join(', ')}`);
2258
+ process.exit(1);
2259
+ }
2260
+ targetType = options.targetType;
2261
+ }
2262
+ // Auto-detect api format from target type if not explicitly set
2263
+ let apiFormat = options.apiFormat || 'openai';
2264
+ if (targetType === 'mcp' && apiFormat === 'openai') {
2265
+ apiFormat = 'mcp-jsonrpc';
2266
+ }
2267
+ else if (targetType === 'a2a' && apiFormat === 'openai') {
2268
+ apiFormat = 'a2a';
2269
+ }
2270
+ // Build target
2271
+ const target = {
2272
+ url: targetUrl || '',
2273
+ type: targetType,
2274
+ headers: Object.keys(headers).length > 0 ? headers : undefined,
2275
+ apiFormat: apiFormat,
2276
+ model: options.model,
2277
+ systemPrompt: options.systemPrompt,
2278
+ mcpTool: options.mcpTool,
2279
+ a2aSender: options.a2aSender,
2280
+ a2aRecipient: options.a2aRecipient,
2281
+ };
2282
+ // Validate format
2283
+ const validFormats = ['text', 'json', 'sarif', 'html'];
2284
+ const format = options.format || 'text';
2285
+ if (!validFormats.includes(format)) {
2286
+ console.error(`Error: Invalid format '${format}'. Use: ${validFormats.join(', ')}`);
2287
+ process.exit(1);
2288
+ }
2289
+ // Load custom payloads from file
2290
+ let customPayloads;
2291
+ if (options.payloadFile) {
2292
+ const filePath = require('path').resolve(options.payloadFile);
2293
+ if (!require('fs').existsSync(filePath)) {
2294
+ console.error(`Error: Payload file not found: ${filePath}`);
2295
+ process.exit(1);
2296
+ }
2297
+ const fileContent = require('fs').readFileSync(filePath, 'utf-8');
2298
+ customPayloads = (0, index_1.parseCustomPayloads)(fileContent);
2299
+ }
2300
+ // Show header for text output
2301
+ if (format === 'text') {
2302
+ console.log(`\n⚔️ HackMyAgent Attack Mode`);
2303
+ console.log(`━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n`);
2304
+ console.log(`Target: ${target.type === 'local' ? 'Local Simulation' : targetUrl}`);
2305
+ console.log(`Intensity: ${intensity}`);
2306
+ if (customPayloads) {
2307
+ console.log(`Payloads: ${customPayloads.length} custom (from file)`);
2308
+ }
2309
+ else {
2310
+ console.log(`Categories: ${categories ? categories.join(', ') : 'all'}`);
2311
+ }
2312
+ console.log();
2313
+ }
2314
+ // Run attack
2315
+ const scanner = new index_1.AttackScanner();
2316
+ const report = await scanner.scan(target, {
2317
+ intensity,
2318
+ categories,
2319
+ customPayloads,
2320
+ timeout: parseInt(options.timeout || '30000', 10),
2321
+ delay: parseInt(options.delay || '1000', 10),
2322
+ stopOnSuccess: options.stopOnSuccess,
2323
+ verbose: options.verbose,
2324
+ });
2325
+ // Output results
2326
+ let output;
2327
+ switch (format) {
2328
+ case 'json':
2329
+ output = JSON.stringify(report, null, 2);
2330
+ break;
2331
+ case 'sarif':
2332
+ output = generateAttackSarif(report);
2333
+ break;
2334
+ case 'html':
2335
+ output = generateAttackHtmlReport(report);
2336
+ break;
2337
+ default: // text
2338
+ printAttackReport(report, options.verbose ?? false);
2339
+ output = '';
2340
+ }
2341
+ // Write output
2342
+ if (output) {
2343
+ if (options.output) {
2344
+ require('fs').writeFileSync(options.output, output);
2345
+ console.error(`Report written to ${options.output}`);
2346
+ }
2347
+ else {
2348
+ console.log(output);
2349
+ }
2350
+ }
2351
+ // Registry reporting: auto-publish to community endpoint by default
2352
+ const shouldReport = options.registryReport || (options.registry !== false);
2353
+ if (shouldReport) {
2354
+ try {
2355
+ const core = await Promise.resolve().then(() => __importStar(require('./index')));
2356
+ const registryUrl = options.registryUrl || process.env.REGISTRY_URL || 'https://registry.opena2a.org';
2357
+ if (options.versionId) {
2358
+ // Authenticated path: existing behavior (version-id + API key)
2359
+ const registryKey = options.registryKey || process.env.REGISTRY_API_KEY;
2360
+ if (!registryKey) {
2361
+ console.error('Error: --registry-key or REGISTRY_API_KEY env is required when using --version-id');
2362
+ process.exit(1);
2363
+ }
2364
+ const client = new core.RegistryClient({ registryUrl, apiKey: registryKey });
2365
+ const payload = core.buildAttackReport(options.versionId, report);
2366
+ await client.reportScanResult(payload);
2367
+ console.log(`Registry: attack results reported for version ${options.versionId}`);
2368
+ }
2369
+ else if (typeof core.buildCommunityAttackReport === 'function') {
2370
+ // Community path: request scan token, then submit results
2371
+ const client = new core.RegistryClient({ registryUrl, apiKey: '' });
2372
+ const packageName = target.url || targetUrl || 'unknown';
2373
+ const tokenResp = typeof client.requestScanToken === 'function'
2374
+ ? await client.requestScanToken(packageName)
2375
+ : null;
2376
+ const payload = core.buildCommunityAttackReport(packageName, report);
2377
+ const resp = typeof client.reportCommunityResult === 'function'
2378
+ ? await client.reportCommunityResult(payload, tokenResp?.scanToken)
2379
+ : { status: 'skipped' };
2380
+ if (resp.status === 'accepted') {
2381
+ console.log('Registry: attack results shared with OpenA2A community');
2382
+ }
2383
+ }
2384
+ }
2385
+ catch (reportErr) {
2386
+ console.error(`Registry: failed to report scan results: ${reportErr.message || reportErr}`);
2387
+ }
2388
+ }
2389
+ // Exit with non-zero based on fail policy
2390
+ if ((0, index_1.shouldFail)(report, options.failOnVulnerable)) {
2391
+ process.exit(1);
2392
+ }
2393
+ }
2394
+ catch (error) {
2395
+ console.error(`Error: ${error instanceof Error ? error.message : 'Unknown error'}`);
2396
+ process.exit(1);
2397
+ }
2398
+ });
2399
+ // Attack report formatting
2400
+ function printAttackReport(report, verbose) {
2401
+ const riskColors = {
2402
+ 'critical': colors.brightRed,
2403
+ 'high': colors.red,
2404
+ 'medium': colors.yellow,
2405
+ 'low': colors.green,
2406
+ 'secure': colors.green,
2407
+ };
2408
+ // Summary
2409
+ console.log(`Risk Score: ${riskColors[report.riskRating]}${report.riskScore}/100 (${report.riskRating.toUpperCase()})${RESET()}`);
2410
+ console.log(`Duration: ${report.duration}ms`);
2411
+ console.log();
2412
+ // Attack summary
2413
+ console.log(`Attacks: ${report.summary.total} total | ${colors.red}${report.summary.successful} successful${RESET()} | ${colors.green}${report.summary.blocked} blocked${RESET()} | ${report.summary.inconclusive} inconclusive`);
2414
+ console.log();
2415
+ // Category breakdown
2416
+ console.log(`Categories:`);
2417
+ for (const [cat, stats] of Object.entries(report.summary.byCategory)) {
2418
+ if (stats.total === 0)
2419
+ continue;
2420
+ const catInfo = index_1.ATTACK_CATEGORIES[cat];
2421
+ const icon = stats.successful > 0 ? '❌' : '✅';
2422
+ console.log(` ${icon} ${catInfo.name}: ${stats.successful}/${stats.total} successful`);
2423
+ }
2424
+ console.log();
2425
+ // Successful attacks
2426
+ const successful = report.results.filter(r => r.success);
2427
+ if (successful.length > 0) {
2428
+ console.log(`${colors.red}Successful Attacks:${RESET()}`);
2429
+ for (const r of successful) {
2430
+ const sevColor = r.payload.severity === 'critical' ? colors.brightRed :
2431
+ r.payload.severity === 'high' ? colors.red :
2432
+ r.payload.severity === 'medium' ? colors.yellow : colors.green;
2433
+ console.log(` ${sevColor}[${r.payload.severity.toUpperCase()}]${RESET()} ${r.payload.id}: ${r.payload.name}`);
2434
+ if (verbose) {
2435
+ console.log(` Evidence: ${r.evidence}`);
2436
+ console.log(` Remediation: ${r.payload.remediation}`);
2437
+ }
2438
+ }
2439
+ console.log();
2440
+ }
2441
+ // Blocked attacks (only in verbose)
2442
+ if (verbose) {
2443
+ const blocked = report.results.filter(r => r.blocked);
2444
+ if (blocked.length > 0) {
2445
+ console.log(`${colors.green}Blocked Attacks (${blocked.length}):${RESET()}`);
2446
+ for (const r of blocked) {
2447
+ console.log(` ✅ ${r.payload.id}: ${r.payload.name}`);
2448
+ }
2449
+ console.log();
2450
+ }
2451
+ }
2452
+ console.log(`━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`);
2453
+ console.log(`\nUse --verbose for detailed attack results.`);
2454
+ console.log(`Use --intensity aggressive for advanced attacks.\n`);
2455
+ }
2456
+ // Generate SARIF output for attack results
2457
+ function generateAttackSarif(report) {
2458
+ const rules = report.results
2459
+ .filter(r => r.success)
2460
+ .map(r => ({
2461
+ id: r.payload.id,
2462
+ name: r.payload.name.replace(/\s+/g, ''),
2463
+ shortDescription: { text: r.payload.name },
2464
+ fullDescription: { text: r.payload.description },
2465
+ help: { text: r.payload.remediation },
2466
+ helpUri: `https://oasb.ai/attacks/${r.payload.id}`,
2467
+ defaultConfiguration: {
2468
+ level: r.payload.severity === 'critical' || r.payload.severity === 'high' ? 'error' :
2469
+ r.payload.severity === 'medium' ? 'warning' : 'note',
2470
+ },
2471
+ properties: {
2472
+ 'security-severity': r.payload.severity === 'critical' ? '9.0' :
2473
+ r.payload.severity === 'high' ? '7.0' :
2474
+ r.payload.severity === 'medium' ? '5.0' : '3.0',
2475
+ tags: ['security', 'ai-agent', r.payload.category],
2476
+ },
2477
+ }));
2478
+ const results = report.results
2479
+ .filter(r => r.success)
2480
+ .map(r => ({
2481
+ ruleId: r.payload.id,
2482
+ level: r.payload.severity === 'critical' || r.payload.severity === 'high' ? 'error' :
2483
+ r.payload.severity === 'medium' ? 'warning' : 'note',
2484
+ message: { text: `${r.payload.name}: ${r.evidence}` },
2485
+ }));
2486
+ return JSON.stringify({
2487
+ $schema: 'https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json',
2488
+ version: '2.1.0',
2489
+ runs: [{
2490
+ tool: {
2491
+ driver: {
2492
+ name: 'HackMyAgent',
2493
+ version: index_1.VERSION,
2494
+ informationUri: 'https://hackmyagent.com',
2495
+ rules,
2496
+ },
2497
+ },
2498
+ results,
2499
+ }],
2500
+ }, null, 2);
2501
+ }
2502
+ // Generate HTML report for attack results
2503
+ function generateAttackHtmlReport(report) {
2504
+ // Risk grade based on score
2505
+ const getGrade = (score) => {
2506
+ if (score <= 10)
2507
+ return { letter: 'A', color: '#22c55e' };
2508
+ if (score <= 25)
2509
+ return { letter: 'B', color: '#84cc16' };
2510
+ if (score <= 50)
2511
+ return { letter: 'C', color: '#eab308' };
2512
+ if (score <= 70)
2513
+ return { letter: 'D', color: '#f97316' };
2514
+ return { letter: 'F', color: '#ef4444' };
2515
+ };
2516
+ const grade = getGrade(report.riskScore);
2517
+ const ratingColor = {
2518
+ 'critical': '#ef4444',
2519
+ 'high': '#f97316',
2520
+ 'medium': '#eab308',
2521
+ 'low': '#22c55e',
2522
+ 'secure': '#22c55e',
2523
+ };
2524
+ const ratingBg = {
2525
+ 'critical': 'rgba(239, 68, 68, 0.15)',
2526
+ 'high': 'rgba(249, 115, 22, 0.15)',
2527
+ 'medium': 'rgba(234, 179, 8, 0.15)',
2528
+ 'low': 'rgba(34, 197, 94, 0.15)',
2529
+ 'secure': 'rgba(34, 197, 94, 0.15)',
2530
+ };
2531
+ // SVG icons
2532
+ const icons = {
2533
+ sword: '<svg class="icon icon-sword" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14.5 17.5L3 6V3h3l11.5 11.5"/><path d="M13 19l6-6"/><path d="M16 16l4 4"/><path d="M19 21l2-2"/></svg>',
2534
+ shield: '<svg class="icon icon-shield" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M10 1.944A11.954 11.954 0 012.166 5C2.056 5.649 2 6.319 2 7c0 5.225 3.34 9.67 8 11.317C14.66 16.67 18 12.225 18 7c0-.682-.057-1.35-.166-2A11.954 11.954 0 0110 1.944zM11 14a1 1 0 11-2 0 1 1 0 012 0zm0-7a1 1 0 10-2 0v3a1 1 0 102 0V7z" clip-rule="evenodd"/></svg>',
2535
+ check: '<svg class="icon icon-check" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd"/></svg>',
2536
+ x: '<svg class="icon icon-x" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"/></svg>',
2537
+ warning: '<svg class="icon icon-warning" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd"/></svg>',
2538
+ print: '<svg class="icon icon-print" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M5 4v3H4a2 2 0 00-2 2v3a2 2 0 002 2h1v2a2 2 0 002 2h6a2 2 0 002-2v-2h1a2 2 0 002-2V9a2 2 0 00-2-2h-1V4a2 2 0 00-2-2H7a2 2 0 00-2 2zm8 0H7v3h6V4zm0 8H7v4h6v-4z" clip-rule="evenodd"/></svg>',
2539
+ };
2540
+ // Category abbreviations
2541
+ const categoryAbbrev = {
2542
+ 'prompt-injection': 'PI',
2543
+ 'jailbreak': 'JB',
2544
+ 'data-exfiltration': 'DE',
2545
+ 'capability-abuse': 'CA',
2546
+ 'context-manipulation': 'CM',
2547
+ 'mcp-exploitation': 'MCP',
2548
+ 'a2a-attack': 'A2A',
2549
+ };
2550
+ // Donut chart for attack results
2551
+ const donutRadius = 60;
2552
+ const donutStroke = 12;
2553
+ const donutCircumference = 2 * Math.PI * donutRadius;
2554
+ const total = report.summary.total || 1;
2555
+ const successPct = report.summary.successful / total;
2556
+ const blockedPct = report.summary.blocked / total;
2557
+ const inconclusivePct = report.summary.inconclusive / total;
2558
+ const successDash = donutCircumference * successPct;
2559
+ const blockedDash = donutCircumference * blockedPct;
2560
+ const inconclusiveDash = donutCircumference * inconclusivePct;
2561
+ // Calculate offsets for each segment
2562
+ const successOffset = 0;
2563
+ const blockedOffset = successDash;
2564
+ const inconclusiveOffset = successDash + blockedDash;
2565
+ const donutSvg = `
2566
+ <svg width="160" height="160" viewBox="0 0 160 160">
2567
+ <!-- Background circle -->
2568
+ <circle cx="80" cy="80" r="${donutRadius}" fill="none" stroke="#334155" stroke-width="${donutStroke}"/>
2569
+ <!-- Inconclusive segment (gray) -->
2570
+ ${inconclusivePct > 0 ? `<circle cx="80" cy="80" r="${donutRadius}" fill="none"
2571
+ stroke="#64748b" stroke-width="${donutStroke}"
2572
+ stroke-dasharray="${inconclusiveDash} ${donutCircumference}"
2573
+ stroke-dashoffset="${-inconclusiveOffset}"
2574
+ transform="rotate(-90 80 80)"/>` : ''}
2575
+ <!-- Blocked segment (green) -->
2576
+ ${blockedPct > 0 ? `<circle cx="80" cy="80" r="${donutRadius}" fill="none"
2577
+ stroke="#22c55e" stroke-width="${donutStroke}"
2578
+ stroke-dasharray="${blockedDash} ${donutCircumference}"
2579
+ stroke-dashoffset="${-blockedOffset}"
2580
+ transform="rotate(-90 80 80)"/>` : ''}
2581
+ <!-- Successful segment (red) -->
2582
+ ${successPct > 0 ? `<circle cx="80" cy="80" r="${donutRadius}" fill="none"
2583
+ stroke="#ef4444" stroke-width="${donutStroke}"
2584
+ stroke-dasharray="${successDash} ${donutCircumference}"
2585
+ stroke-dashoffset="${-successOffset}"
2586
+ transform="rotate(-90 80 80)"/>` : ''}
2587
+ <!-- Center text -->
2588
+ <text x="80" y="75" text-anchor="middle" fill="#f1f5f9" font-size="24" font-weight="700">${report.summary.total}</text>
2589
+ <text x="80" y="95" text-anchor="middle" fill="#94a3b8" font-size="12">attacks</text>
2590
+ </svg>`;
2591
+ // Generate category breakdown rows
2592
+ const categoryRows = Object.entries(report.summary.byCategory)
2593
+ .filter(([_, stats]) => stats.total > 0)
2594
+ .map(([cat, stats]) => {
2595
+ const catInfo = index_1.ATTACK_CATEGORIES[cat];
2596
+ const abbrev = categoryAbbrev[cat];
2597
+ const successRate = stats.total > 0 ? Math.round((stats.successful / stats.total) * 100) : 0;
2598
+ const barColor = stats.successful === 0 ? '#22c55e' : successRate > 50 ? '#ef4444' : '#eab308';
2599
+ const statusIcon = stats.successful === 0 ? icons.check : icons.x;
2600
+ const statusClass = stats.successful === 0 ? 'status-pass' : 'status-fail';
2601
+ // Get results for this category
2602
+ const catResults = report.results.filter(r => r.payload.category === cat);
2603
+ const resultRows = catResults.map(r => {
2604
+ const resultIcon = r.success ? icons.x : r.blocked ? icons.check : icons.warning;
2605
+ const resultClass = r.success ? 'status-fail' : r.blocked ? 'status-pass' : 'status-warn';
2606
+ const sevColor = r.payload.severity === 'critical' ? '#ef4444' :
2607
+ r.payload.severity === 'high' ? '#f97316' :
2608
+ r.payload.severity === 'medium' ? '#eab308' : '#22c55e';
2609
+ return `
2610
+ <tr class="attack-row ${r.success ? 'failed' : ''}">
2611
+ <td class="status-cell"><span class="${resultClass}">${resultIcon}</span></td>
2612
+ <td class="id-cell"><code>${r.payload.id}</code></td>
2613
+ <td class="name-cell">${escapeHtml(r.payload.name)}</td>
2614
+ <td class="severity-cell"><span class="severity-badge" style="color: ${sevColor}; background: ${sevColor}20;">${r.payload.severity.toUpperCase()}</span></td>
2615
+ <td class="result-cell">${r.success ? '<span class="result-tag fail">Succeeded</span>' : r.blocked ? '<span class="result-tag pass">Blocked</span>' : '<span class="result-tag warn">Inconclusive</span>'}</td>
2616
+ </tr>`;
2617
+ }).join('');
2618
+ return `
2619
+ <div class="category" id="cat-${abbrev}">
2620
+ <div class="category-header" onclick="toggleCategory('${abbrev}')">
2621
+ <span class="category-abbrev">[${abbrev}]</span>
2622
+ <span class="category-icon ${statusClass}">${statusIcon}</span>
2623
+ <span class="category-name">${escapeHtml(catInfo.name)}</span>
2624
+ <div class="category-meta">
2625
+ <span class="category-score">${stats.successful}/${stats.total} successful</span>
2626
+ <div class="mini-bar"><div class="mini-fill" style="width: ${successRate}%; background: ${barColor};"></div></div>
2627
+ <span class="chevron">▼</span>
2628
+ </div>
2629
+ </div>
2630
+ <div class="category-content">
2631
+ <table class="attacks-table">
2632
+ <thead><tr><th></th><th>ID</th><th>Attack</th><th>Severity</th><th>Result</th></tr></thead>
2633
+ <tbody>${resultRows}</tbody>
2634
+ </table>
2635
+ </div>
2636
+ </div>`;
2637
+ }).join('');
2638
+ // Successful attacks detail section
2639
+ const successfulAttacks = report.results.filter(r => r.success);
2640
+ const successfulDetailsHtml = successfulAttacks.length > 0 ? successfulAttacks.map(r => {
2641
+ const sevColor = r.payload.severity === 'critical' ? '#ef4444' :
2642
+ r.payload.severity === 'high' ? '#f97316' :
2643
+ r.payload.severity === 'medium' ? '#eab308' : '#22c55e';
2644
+ return `
2645
+ <div class="attack-detail">
2646
+ <div class="attack-detail-header">
2647
+ <code class="attack-id">${r.payload.id}</code>
2648
+ <span class="attack-name">${escapeHtml(r.payload.name)}</span>
2649
+ <span class="severity-badge" style="color: ${sevColor}; background: ${sevColor}20;">${r.payload.severity.toUpperCase()}</span>
2650
+ </div>
2651
+ <div class="attack-detail-meta">
2652
+ ${r.payload.oasbControl ? `<span class="meta-tag">OASB ${r.payload.oasbControl}</span>` : ''}
2653
+ ${r.payload.cwe ? `<span class="meta-tag">CWE-${r.payload.cwe}</span>` : ''}
2654
+ <span class="meta-tag">${index_1.ATTACK_CATEGORIES[r.payload.category].name}</span>
2655
+ </div>
2656
+ <div class="attack-detail-body">
2657
+ <div class="detail-section">
2658
+ <strong>Description:</strong> ${escapeHtml(r.payload.description)}
2659
+ </div>
2660
+ <div class="detail-section evidence">
2661
+ <strong>Evidence:</strong> ${escapeHtml(r.evidence)}
2662
+ </div>
2663
+ <div class="detail-section remediation">
2664
+ <strong>Remediation:</strong> ${escapeHtml(r.payload.remediation)}
2665
+ </div>
2666
+ </div>
2667
+ </div>`;
2668
+ }).join('') : '<div class="no-attacks">No successful attacks detected.</div>';
2669
+ return `<!DOCTYPE html>
2670
+ <html lang="en">
2671
+ <head>
2672
+ <meta charset="UTF-8">
2673
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
2674
+ <title>HackMyAgent Attack Report | ${report.riskRating.toUpperCase()}</title>
2675
+ <style>
2676
+ :root {
2677
+ --bg-primary: #0a0f1a;
2678
+ --bg-secondary: #111827;
2679
+ --bg-tertiary: #1f2937;
2680
+ --text-primary: #f1f5f9;
2681
+ --text-secondary: #94a3b8;
2682
+ --text-muted: #64748b;
2683
+ --border: #334155;
2684
+ --accent: #3b82f6;
2685
+ --success: #22c55e;
2686
+ --warning: #eab308;
2687
+ --danger: #ef4444;
2688
+ }
2689
+ * { box-sizing: border-box; margin: 0; padding: 0; }
2690
+ body {
2691
+ font-family: 'SF Pro Display', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
2692
+ background: var(--bg-primary);
2693
+ color: var(--text-primary);
2694
+ line-height: 1.6;
2695
+ padding: 2rem;
2696
+ font-size: 14px;
2697
+ }
2698
+ .container { max-width: 1400px; margin: 0 auto; }
2699
+
2700
+ /* Header */
2701
+ .header {
2702
+ display: flex;
2703
+ justify-content: space-between;
2704
+ align-items: center;
2705
+ margin-bottom: 2rem;
2706
+ padding: 1.5rem 2rem;
2707
+ background: var(--bg-secondary);
2708
+ border-radius: 12px;
2709
+ border: 1px solid var(--border);
2710
+ }
2711
+ .header-left h1 {
2712
+ font-size: 1.5rem;
2713
+ font-weight: 700;
2714
+ display: flex;
2715
+ align-items: center;
2716
+ gap: 0.75rem;
2717
+ }
2718
+ .header-left .meta { color: var(--text-muted); font-size: 0.8rem; margin-top: 0.25rem; }
2719
+ .header-icon { display: inline-flex; margin-right: 0.5rem; }
2720
+ .header-icon .icon { width: 24px; height: 24px; color: var(--danger); }
2721
+ .header-right { display: flex; align-items: center; gap: 1rem; }
2722
+ .rating-badge {
2723
+ display: inline-block;
2724
+ padding: 0.375rem 1rem;
2725
+ border-radius: 6px;
2726
+ font-weight: 600;
2727
+ font-size: 0.875rem;
2728
+ background: ${ratingBg[report.riskRating]};
2729
+ color: ${ratingColor[report.riskRating]};
2730
+ border: 1px solid ${ratingColor[report.riskRating]}40;
2731
+ }
2732
+ .intensity-tag {
2733
+ display: inline-block;
2734
+ padding: 0.375rem 1rem;
2735
+ background: var(--accent);
2736
+ color: white;
2737
+ border-radius: 6px;
2738
+ font-size: 0.875rem;
2739
+ font-weight: 600;
2740
+ text-transform: capitalize;
2741
+ }
2742
+
2743
+ /* SVG Icons */
2744
+ .icon { width: 16px; height: 16px; display: inline-block; vertical-align: middle; }
2745
+ .status-pass { color: var(--success); }
2746
+ .status-fail { color: var(--danger); }
2747
+ .status-warn { color: var(--warning); }
2748
+ .category-icon { display: flex; align-items: center; }
2749
+ .category-icon .icon { width: 18px; height: 18px; }
2750
+ .footer-btn .icon { width: 14px; height: 14px; margin-right: 0.375rem; }
2751
+
2752
+ /* Dashboard grid */
2753
+ .dashboard {
2754
+ display: grid;
2755
+ grid-template-columns: 280px 200px 1fr;
2756
+ gap: 1.5rem;
2757
+ margin-bottom: 2rem;
2758
+ }
2759
+ @media (max-width: 1200px) {
2760
+ .dashboard { grid-template-columns: 1fr 1fr; }
2761
+ .summary-section { grid-column: span 2; }
2762
+ }
2763
+ @media (max-width: 768px) {
2764
+ .dashboard { grid-template-columns: 1fr; }
2765
+ .summary-section { grid-column: span 1; }
2766
+ }
2767
+
2768
+ /* Risk Score card */
2769
+ .score-card {
2770
+ background: var(--bg-secondary);
2771
+ border-radius: 12px;
2772
+ padding: 1.25rem;
2773
+ border: 1px solid var(--border);
2774
+ }
2775
+ .score-header {
2776
+ display: flex;
2777
+ align-items: center;
2778
+ gap: 1rem;
2779
+ margin-bottom: 1.25rem;
2780
+ padding-bottom: 1rem;
2781
+ border-bottom: 1px solid var(--border);
2782
+ }
2783
+ .score-grade {
2784
+ width: 56px;
2785
+ height: 56px;
2786
+ border-radius: 12px;
2787
+ border: 2px solid;
2788
+ display: flex;
2789
+ align-items: center;
2790
+ justify-content: center;
2791
+ }
2792
+ .grade-letter { font-size: 1.75rem; font-weight: 800; }
2793
+ .score-main { flex: 1; }
2794
+ .score-pct { font-size: 2rem; font-weight: 700; color: var(--text-primary); line-height: 1; }
2795
+ .score-label { font-size: 0.75rem; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.05em; margin-top: 0.25rem; }
2796
+
2797
+ .score-stats { margin-top: 1rem; }
2798
+ .stat-row {
2799
+ display: flex;
2800
+ align-items: center;
2801
+ justify-content: space-between;
2802
+ padding: 0.5rem 0;
2803
+ border-bottom: 1px solid var(--border);
2804
+ }
2805
+ .stat-row:last-child { border-bottom: none; }
2806
+ .stat-label { color: var(--text-secondary); font-size: 0.85rem; }
2807
+ .stat-value { font-weight: 600; }
2808
+ .stat-value.danger { color: var(--danger); }
2809
+ .stat-value.success { color: var(--success); }
2810
+ .stat-value.muted { color: var(--text-muted); }
2811
+
2812
+ /* Donut chart section */
2813
+ .donut-section {
2814
+ background: var(--bg-secondary);
2815
+ border-radius: 12px;
2816
+ padding: 1.25rem;
2817
+ border: 1px solid var(--border);
2818
+ display: flex;
2819
+ flex-direction: column;
2820
+ align-items: center;
2821
+ }
2822
+ .donut-section h3 {
2823
+ font-size: 0.85rem;
2824
+ color: var(--text-secondary);
2825
+ text-transform: uppercase;
2826
+ letter-spacing: 0.05em;
2827
+ margin-bottom: 1rem;
2828
+ width: 100%;
2829
+ }
2830
+ .donut-legend {
2831
+ display: flex;
2832
+ flex-direction: column;
2833
+ gap: 0.5rem;
2834
+ margin-top: 1rem;
2835
+ width: 100%;
2836
+ }
2837
+ .legend-item {
2838
+ display: flex;
2839
+ align-items: center;
2840
+ gap: 0.5rem;
2841
+ font-size: 0.8rem;
2842
+ color: var(--text-secondary);
2843
+ }
2844
+ .legend-dot {
2845
+ width: 10px;
2846
+ height: 10px;
2847
+ border-radius: 50%;
2848
+ }
2849
+
2850
+ /* Summary section */
2851
+ .summary-section {
2852
+ background: var(--bg-secondary);
2853
+ border-radius: 12px;
2854
+ padding: 1.5rem;
2855
+ border: 1px solid var(--border);
2856
+ }
2857
+ .summary-section h3 {
2858
+ font-size: 0.85rem;
2859
+ color: var(--text-secondary);
2860
+ text-transform: uppercase;
2861
+ letter-spacing: 0.05em;
2862
+ margin-bottom: 1rem;
2863
+ }
2864
+ .severity-breakdown {
2865
+ display: flex;
2866
+ gap: 1rem;
2867
+ flex-wrap: wrap;
2868
+ }
2869
+ .severity-item {
2870
+ display: flex;
2871
+ align-items: center;
2872
+ gap: 0.5rem;
2873
+ padding: 0.5rem 1rem;
2874
+ background: var(--bg-tertiary);
2875
+ border-radius: 6px;
2876
+ }
2877
+ .severity-count { font-size: 1.25rem; font-weight: 700; }
2878
+ .severity-label { font-size: 0.75rem; color: var(--text-muted); text-transform: uppercase; }
2879
+
2880
+ /* Categories */
2881
+ .categories-section {
2882
+ margin-bottom: 2rem;
2883
+ }
2884
+ .categories-header {
2885
+ display: flex;
2886
+ justify-content: space-between;
2887
+ align-items: center;
2888
+ margin-bottom: 1rem;
2889
+ }
2890
+ .categories-header h2 { font-size: 1.1rem; }
2891
+ .expand-all {
2892
+ background: var(--bg-tertiary);
2893
+ border: 1px solid var(--border);
2894
+ color: var(--text-secondary);
2895
+ padding: 0.5rem 1rem;
2896
+ border-radius: 6px;
2897
+ cursor: pointer;
2898
+ font-size: 0.8rem;
2899
+ }
2900
+ .expand-all:hover { background: var(--border); }
2901
+
2902
+ .category {
2903
+ background: var(--bg-secondary);
2904
+ border-radius: 8px;
2905
+ margin-bottom: 0.75rem;
2906
+ border: 1px solid var(--border);
2907
+ overflow: hidden;
2908
+ }
2909
+ .category-header {
2910
+ display: flex;
2911
+ align-items: center;
2912
+ gap: 0.75rem;
2913
+ padding: 1rem 1.25rem;
2914
+ cursor: pointer;
2915
+ transition: background 0.15s;
2916
+ }
2917
+ .category-header:hover { background: var(--bg-tertiary); }
2918
+ .category-abbrev {
2919
+ font-family: monospace;
2920
+ font-size: 0.85rem;
2921
+ color: var(--accent);
2922
+ font-weight: 600;
2923
+ }
2924
+ .category-icon { font-size: 1.1rem; }
2925
+ .category-name { flex: 1; font-weight: 500; }
2926
+ .category-meta { display: flex; align-items: center; gap: 0.75rem; }
2927
+ .category-score { color: var(--text-secondary); font-size: 0.85rem; font-weight: 500; }
2928
+ .mini-bar { width: 60px; height: 6px; background: var(--border); border-radius: 3px; overflow: hidden; }
2929
+ .mini-fill { height: 100%; border-radius: 3px; }
2930
+ .chevron {
2931
+ color: var(--text-muted);
2932
+ font-size: 0.7rem;
2933
+ transition: transform 0.2s;
2934
+ margin-left: 0.5rem;
2935
+ }
2936
+ .category.collapsed .chevron { transform: rotate(-90deg); }
2937
+ .category.collapsed .category-content { display: none; }
2938
+
2939
+ .category-content { border-top: 1px solid var(--border); }
2940
+ .attacks-table { width: 100%; border-collapse: collapse; }
2941
+ .attacks-table th {
2942
+ padding: 0.75rem 1rem;
2943
+ text-align: left;
2944
+ background: var(--bg-primary);
2945
+ color: var(--text-muted);
2946
+ font-weight: 500;
2947
+ font-size: 0.75rem;
2948
+ text-transform: uppercase;
2949
+ letter-spacing: 0.03em;
2950
+ }
2951
+ .attacks-table td {
2952
+ padding: 0.875rem 1rem;
2953
+ border-top: 1px solid var(--border);
2954
+ vertical-align: middle;
2955
+ }
2956
+ .status-cell { width: 40px; text-align: center; }
2957
+ .id-cell { width: 80px; }
2958
+ .id-cell code {
2959
+ background: var(--bg-tertiary);
2960
+ padding: 0.2rem 0.5rem;
2961
+ border-radius: 4px;
2962
+ font-size: 0.8rem;
2963
+ color: var(--accent);
2964
+ }
2965
+ .name-cell { width: 40%; }
2966
+ .severity-cell { width: 80px; }
2967
+ .result-cell { width: 100px; }
2968
+ .attack-row.failed { background: rgba(239, 68, 68, 0.05); }
2969
+
2970
+ .severity-badge {
2971
+ padding: 0.2rem 0.6rem;
2972
+ border-radius: 4px;
2973
+ font-size: 0.7rem;
2974
+ font-weight: 600;
2975
+ text-transform: uppercase;
2976
+ }
2977
+ .result-tag {
2978
+ padding: 0.2rem 0.6rem;
2979
+ border-radius: 4px;
2980
+ font-size: 0.7rem;
2981
+ font-weight: 600;
2982
+ }
2983
+ .result-tag.pass { background: rgba(34, 197, 94, 0.2); color: var(--success); }
2984
+ .result-tag.fail { background: rgba(239, 68, 68, 0.2); color: var(--danger); }
2985
+ .result-tag.warn { background: rgba(234, 179, 8, 0.2); color: var(--warning); }
2986
+
2987
+ /* Successful attacks detail */
2988
+ .details-section {
2989
+ margin-bottom: 2rem;
2990
+ }
2991
+ .details-section h2 {
2992
+ font-size: 1.1rem;
2993
+ margin-bottom: 1rem;
2994
+ display: flex;
2995
+ align-items: center;
2996
+ gap: 0.5rem;
2997
+ }
2998
+ .details-section h2 .icon { color: var(--danger); }
2999
+ .attack-detail {
3000
+ background: var(--bg-secondary);
3001
+ border-radius: 8px;
3002
+ margin-bottom: 1rem;
3003
+ border: 1px solid var(--border);
3004
+ border-left: 3px solid var(--danger);
3005
+ overflow: hidden;
3006
+ }
3007
+ .attack-detail-header {
3008
+ display: flex;
3009
+ align-items: center;
3010
+ gap: 1rem;
3011
+ padding: 1rem 1.25rem;
3012
+ background: rgba(239, 68, 68, 0.05);
3013
+ }
3014
+ .attack-id {
3015
+ background: var(--bg-tertiary);
3016
+ padding: 0.2rem 0.5rem;
3017
+ border-radius: 4px;
3018
+ font-size: 0.85rem;
3019
+ color: var(--danger);
3020
+ }
3021
+ .attack-name { flex: 1; font-weight: 500; }
3022
+ .attack-detail-meta {
3023
+ display: flex;
3024
+ gap: 0.5rem;
3025
+ padding: 0.75rem 1.25rem;
3026
+ background: var(--bg-tertiary);
3027
+ border-bottom: 1px solid var(--border);
3028
+ }
3029
+ .meta-tag {
3030
+ padding: 0.2rem 0.5rem;
3031
+ background: var(--bg-secondary);
3032
+ border-radius: 4px;
3033
+ font-size: 0.75rem;
3034
+ color: var(--text-muted);
3035
+ }
3036
+ .attack-detail-body { padding: 1rem 1.25rem; }
3037
+ .detail-section {
3038
+ margin-bottom: 0.75rem;
3039
+ font-size: 0.9rem;
3040
+ color: var(--text-secondary);
3041
+ }
3042
+ .detail-section:last-child { margin-bottom: 0; }
3043
+ .detail-section strong { color: var(--text-primary); margin-right: 0.5rem; }
3044
+ .detail-section.evidence {
3045
+ padding: 0.75rem;
3046
+ background: rgba(239, 68, 68, 0.1);
3047
+ border-radius: 6px;
3048
+ border-left: 3px solid var(--danger);
3049
+ }
3050
+ .detail-section.remediation {
3051
+ padding: 0.75rem;
3052
+ background: var(--bg-tertiary);
3053
+ border-radius: 6px;
3054
+ border-left: 3px solid var(--accent);
3055
+ }
3056
+ .no-attacks {
3057
+ padding: 2rem;
3058
+ text-align: center;
3059
+ color: var(--success);
3060
+ background: var(--bg-secondary);
3061
+ border-radius: 8px;
3062
+ border: 1px solid var(--border);
3063
+ }
3064
+
3065
+ /* Footer */
3066
+ .footer {
3067
+ display: flex;
3068
+ justify-content: space-between;
3069
+ align-items: center;
3070
+ margin-top: 2rem;
3071
+ padding: 1.5rem;
3072
+ background: var(--bg-secondary);
3073
+ border-radius: 12px;
3074
+ border: 1px solid var(--border);
3075
+ color: var(--text-muted);
3076
+ font-size: 0.85rem;
3077
+ }
3078
+ .footer a { color: var(--accent); text-decoration: none; }
3079
+ .footer a:hover { text-decoration: underline; }
3080
+ .footer-actions { display: flex; gap: 1rem; }
3081
+ .footer-btn {
3082
+ display: flex;
3083
+ align-items: center;
3084
+ padding: 0.5rem 1rem;
3085
+ background: var(--bg-tertiary);
3086
+ border: 1px solid var(--border);
3087
+ border-radius: 6px;
3088
+ color: var(--text-primary);
3089
+ cursor: pointer;
3090
+ font-size: 0.8rem;
3091
+ }
3092
+ .footer-btn:hover { background: var(--border); }
3093
+
3094
+ /* Print styles */
3095
+ @media print {
3096
+ body { background: white; color: black; padding: 1rem; }
3097
+ .container { max-width: 100%; }
3098
+ .header, .score-card, .donut-section, .summary-section, .category, .attack-detail, .footer {
3099
+ background: white;
3100
+ border: 1px solid #ddd;
3101
+ break-inside: avoid;
3102
+ }
3103
+ .category.collapsed .category-content { display: block !important; }
3104
+ .chevron, .expand-all, .footer-actions { display: none; }
3105
+ .category-header { cursor: default; }
3106
+ .attack-row.failed { background: #fff0f0; }
3107
+ :root {
3108
+ --bg-primary: white;
3109
+ --bg-secondary: white;
3110
+ --bg-tertiary: #f5f5f5;
3111
+ --text-primary: black;
3112
+ --text-secondary: #555;
3113
+ --text-muted: #888;
3114
+ --border: #ddd;
3115
+ }
3116
+ }
3117
+ </style>
3118
+ </head>
3119
+ <body>
3120
+ <div class="container">
3121
+ <header class="header">
3122
+ <div class="header-left">
3123
+ <h1><span class="header-icon">${icons.sword}</span>HackMyAgent Attack Report</h1>
3124
+ <div class="meta">Target: ${escapeHtml(report.target || 'Local Simulation')} • ${new Date(report.endTime).toLocaleString()}</div>
3125
+ </div>
3126
+ <div class="header-right">
3127
+ <div class="rating-badge">${report.riskRating.toUpperCase()} RISK</div>
3128
+ <div class="intensity-tag">${report.intensity}</div>
3129
+ </div>
3130
+ </header>
3131
+
3132
+ <div class="dashboard">
3133
+ <div class="score-card">
3134
+ <div class="score-header">
3135
+ <div class="score-grade" style="background: ${grade.color}20; border-color: ${grade.color};">
3136
+ <span class="grade-letter" style="color: ${grade.color};">${grade.letter}</span>
3137
+ </div>
3138
+ <div class="score-main">
3139
+ <div class="score-pct">${report.riskScore}/100</div>
3140
+ <div class="score-label">Risk Score</div>
3141
+ </div>
3142
+ </div>
3143
+ <div class="score-stats">
3144
+ <div class="stat-row">
3145
+ <span class="stat-label">Successful Attacks</span>
3146
+ <span class="stat-value danger">${report.summary.successful}</span>
3147
+ </div>
3148
+ <div class="stat-row">
3149
+ <span class="stat-label">Blocked Attacks</span>
3150
+ <span class="stat-value success">${report.summary.blocked}</span>
3151
+ </div>
3152
+ <div class="stat-row">
3153
+ <span class="stat-label">Inconclusive</span>
3154
+ <span class="stat-value muted">${report.summary.inconclusive}</span>
3155
+ </div>
3156
+ <div class="stat-row">
3157
+ <span class="stat-label">Duration</span>
3158
+ <span class="stat-value">${report.duration}ms</span>
3159
+ </div>
3160
+ </div>
3161
+ </div>
3162
+
3163
+ <div class="donut-section">
3164
+ <h3>Attack Results</h3>
3165
+ ${donutSvg}
3166
+ <div class="donut-legend">
3167
+ <div class="legend-item"><span class="legend-dot" style="background: #ef4444;"></span> Successful (${report.summary.successful})</div>
3168
+ <div class="legend-item"><span class="legend-dot" style="background: #22c55e;"></span> Blocked (${report.summary.blocked})</div>
3169
+ <div class="legend-item"><span class="legend-dot" style="background: #64748b;"></span> Inconclusive (${report.summary.inconclusive})</div>
3170
+ </div>
3171
+ </div>
3172
+
3173
+ <div class="summary-section">
3174
+ <h3>Severity Breakdown (Successful Attacks)</h3>
3175
+ <div class="severity-breakdown">
3176
+ <div class="severity-item">
3177
+ <span class="severity-count" style="color: #ef4444;">${report.summary.bySeverity.critical || 0}</span>
3178
+ <span class="severity-label">Critical</span>
3179
+ </div>
3180
+ <div class="severity-item">
3181
+ <span class="severity-count" style="color: #f97316;">${report.summary.bySeverity.high || 0}</span>
3182
+ <span class="severity-label">High</span>
3183
+ </div>
3184
+ <div class="severity-item">
3185
+ <span class="severity-count" style="color: #eab308;">${report.summary.bySeverity.medium || 0}</span>
3186
+ <span class="severity-label">Medium</span>
3187
+ </div>
3188
+ <div class="severity-item">
3189
+ <span class="severity-count" style="color: #22c55e;">${report.summary.bySeverity.low || 0}</span>
3190
+ <span class="severity-label">Low</span>
3191
+ </div>
3192
+ </div>
3193
+ </div>
3194
+ </div>
3195
+
3196
+ <div class="categories-section">
3197
+ <div class="categories-header">
3198
+ <h2>Category Breakdown</h2>
3199
+ <button class="expand-all" onclick="toggleAll()">Expand/Collapse All</button>
3200
+ </div>
3201
+ ${categoryRows}
3202
+ </div>
3203
+
3204
+ <div class="details-section">
3205
+ <h2>${icons.x} Successful Attacks Detail</h2>
3206
+ ${successfulDetailsHtml}
3207
+ </div>
3208
+
3209
+ <footer class="footer">
3210
+ <div>Generated by <a href="https://hackmyagent.com">HackMyAgent</a> v${index_1.VERSION} • <a href="https://oasb.ai/attacks">oasb.ai/attacks</a></div>
3211
+ <div class="footer-actions">
3212
+ <button class="footer-btn" onclick="window.print()">${icons.print} Print Report</button>
3213
+ </div>
3214
+ </footer>
3215
+ </div>
3216
+
3217
+ <script>
3218
+ function toggleCategory(id) {
3219
+ const cat = document.getElementById('cat-' + id);
3220
+ cat.classList.toggle('collapsed');
3221
+ }
3222
+ function toggleAll() {
3223
+ const cats = document.querySelectorAll('.category');
3224
+ const allCollapsed = Array.from(cats).every(c => c.classList.contains('collapsed'));
3225
+ cats.forEach(c => {
3226
+ if (allCollapsed) {
3227
+ c.classList.remove('collapsed');
3228
+ } else {
3229
+ c.classList.add('collapsed');
3230
+ }
3231
+ });
3232
+ }
3233
+ </script>
3234
+ </body>
3235
+ </html>`;
3236
+ }
3237
+ // --- fix-all: Run all OpenClaw plugins to scan and remediate ---
3238
+ const credvault_1 = require("./plugins/credvault");
3239
+ const signcrypt_1 = require("./plugins/signcrypt");
3240
+ const skillguard_1 = require("./plugins/skillguard");
3241
+ const aim_core_1 = require("@opena2a/aim-core");
3242
+ const PLUGIN_SEVERITY_DISPLAY = {
3243
+ critical: { symbol: '[!!]', color: () => colors.brightRed },
3244
+ high: { symbol: '[!]', color: () => colors.red },
3245
+ medium: { symbol: '[~]', color: () => colors.yellow },
3246
+ low: { symbol: '[.]', color: () => colors.green },
3247
+ info: { symbol: '[i]', color: () => colors.cyan },
3248
+ };
3249
+ program
3250
+ .command('fix-all')
3251
+ .description(`Run all OpenA2A security plugins to scan and auto-fix agent issues
3252
+
3253
+ Runs the full plugin suite in order:
3254
+ 1. SkillGuard — hash pinning, tamper detection, dangerous patterns
3255
+ 2. SignCrypt — Ed25519 signing, heartbeat hash pins
3256
+ 3. CredVault — credential detection, env var replacement
3257
+
3258
+ Each plugin scans for findings, then auto-fixes what it can.
3259
+ Dangerous patterns (reverse shells, exfil, etc.) require manual review.
3260
+
3261
+ Exit code 1 if critical/high issues remain after fixing.
3262
+
3263
+ Examples:
3264
+ $ hackmyagent fix-all Scan and fix current directory
3265
+ $ hackmyagent fix-all ./my-agent Scan specific directory
3266
+ $ hackmyagent fix-all --dry-run Preview fixes without applying
3267
+ $ hackmyagent fix-all --scan-only Scan without fixing
3268
+ $ hackmyagent fix-all --json JSON output for CI
3269
+ $ hackmyagent fix-all --with-aim Enable AIM identity and audit`)
3270
+ .argument('[directory]', 'Agent directory to scan (default: current directory)', '')
3271
+ .option('--dry-run', 'Preview fixes without applying them')
3272
+ .option('--scan-only', 'Only scan, do not fix')
3273
+ .option('--json', 'Output as JSON (for scripting/CI)')
3274
+ .option('--with-aim', 'Initialize AIM Core for identity-aware audit logging')
3275
+ .option('-v, --verbose', 'Show all findings including passed plugins')
3276
+ .action(async (directory, options) => {
3277
+ try {
3278
+ const path = require('path');
3279
+ const fs = require('fs');
3280
+ // Resolve target directory with symlink protection
3281
+ let targetDir;
3282
+ if (directory && directory !== '') {
3283
+ targetDir = path.isAbsolute(directory) ? directory : path.resolve(process.cwd(), directory);
3284
+ }
3285
+ else {
3286
+ targetDir = process.cwd();
3287
+ }
3288
+ // Resolve realpath atomically — eliminates TOCTOU between existence check and resolution
3289
+ let realTarget;
3290
+ try {
3291
+ realTarget = fs.realpathSync(targetDir);
3292
+ }
3293
+ catch {
3294
+ console.error(`Error: Directory not found: ${targetDir}`);
3295
+ process.exit(1);
3296
+ }
3297
+ // Verify resolved path is a directory (realpath already resolved any symlinks)
3298
+ const resolvedStat = fs.statSync(realTarget);
3299
+ if (!resolvedStat.isDirectory()) {
3300
+ console.error(`Error: Not a directory: ${realTarget}`);
3301
+ process.exit(1);
3302
+ }
3303
+ // Block path traversal via .. in relative paths (but allow absolute paths)
3304
+ if (!path.isAbsolute(directory) && directory && directory !== '') {
3305
+ const realCwd = fs.realpathSync(process.cwd());
3306
+ const relative = path.relative(realCwd, realTarget);
3307
+ if (relative.startsWith('..')) {
3308
+ console.error(`Error: Target directory must not traverse above current working directory. Use an absolute path instead.`);
3309
+ process.exit(1);
3310
+ }
3311
+ }
3312
+ targetDir = realTarget;
3313
+ // Initialize AIM Core if requested
3314
+ let aimCore;
3315
+ if (options.withAim) {
3316
+ aimCore = new aim_core_1.AIMCore({
3317
+ agentName: path.basename(targetDir),
3318
+ dataDir: path.join(targetDir, '.opena2a', 'aim'),
3319
+ });
3320
+ }
3321
+ // Create and initialize plugins in execution order
3322
+ // Order matters: CredVault replaces creds, SignCrypt signs skills,
3323
+ // SkillGuard pins last so hashes reflect the final file state.
3324
+ const pluginFactories = [
3325
+ { name: 'CredVault', create: credvault_1.createPlugin },
3326
+ { name: 'SignCrypt', create: signcrypt_1.createPlugin },
3327
+ { name: 'SkillGuard', create: skillguard_1.createPlugin },
3328
+ ];
3329
+ const plugins = [];
3330
+ for (const factory of pluginFactories) {
3331
+ const plugin = factory.create();
3332
+ await plugin.init(aimCore ? { aimCore } : undefined);
3333
+ plugins.push({ name: factory.name, plugin });
3334
+ }
3335
+ if (!options.json) {
3336
+ console.log(`\n OpenA2A Fix-All Security Report`);
3337
+ console.log(`━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n`);
3338
+ if (options.dryRun) {
3339
+ console.log(`Scanning ${targetDir} (dry-run -- previewing fixes)...\n`);
3340
+ }
3341
+ else if (options.scanOnly) {
3342
+ console.log(`Scanning ${targetDir} (scan-only -- no fixes applied)...\n`);
3343
+ }
3344
+ else {
3345
+ console.log(`Scanning and fixing ${targetDir}...\n`);
3346
+ }
3347
+ }
3348
+ const results = [];
3349
+ let allFindings = [];
3350
+ let allRemediations = [];
3351
+ let pluginErrors = 0;
3352
+ for (const { name, plugin } of plugins) {
3353
+ if (!options.json) {
3354
+ console.log(`${colors.cyan}> ${name}${RESET()}`);
3355
+ }
3356
+ try {
3357
+ // Scan
3358
+ const findings = await plugin.scan(targetDir);
3359
+ let remediations = [];
3360
+ if (!options.scanOnly && findings.length > 0) {
3361
+ remediations = await plugin.fix(targetDir, {
3362
+ dryRun: options.dryRun ?? false,
3363
+ });
3364
+ }
3365
+ results.push({ name, findings, remediations });
3366
+ allFindings.push(...findings);
3367
+ allRemediations.push(...remediations);
3368
+ if (!options.json) {
3369
+ if (findings.length === 0) {
3370
+ console.log(` ${colors.green}[+] No issues found${RESET()}`);
3371
+ }
3372
+ else {
3373
+ console.log(` Found ${findings.length} issue(s)`);
3374
+ if (remediations.length > 0) {
3375
+ console.log(` ${colors.green}[+] Fixed ${remediations.length}${RESET()}`);
3376
+ }
3377
+ }
3378
+ console.log();
3379
+ }
3380
+ }
3381
+ catch (pluginErr) {
3382
+ // Isolate plugin errors — one failing plugin should not crash the entire run
3383
+ pluginErrors++;
3384
+ results.push({ name, findings: [], remediations: [] });
3385
+ if (!options.json) {
3386
+ console.log(` ${colors.brightRed}[!!] Plugin error: ${pluginErr instanceof Error ? pluginErr.message : String(pluginErr)}${RESET()}`);
3387
+ if (pluginErr instanceof Error && pluginErr.stack) {
3388
+ console.error(pluginErr.stack);
3389
+ }
3390
+ console.log();
3391
+ }
3392
+ }
3393
+ }
3394
+ // JSON output
3395
+ if (options.json) {
3396
+ const unfixed = allFindings.filter((f) => !allRemediations.some((r) => r.findingId === f.id));
3397
+ const jsonOutput = {
3398
+ target: targetDir,
3399
+ mode: options.dryRun ? 'dry-run' : options.scanOnly ? 'scan-only' : 'fix',
3400
+ aimEnabled: !!aimCore,
3401
+ totalFindings: allFindings.length,
3402
+ totalFixed: allRemediations.length,
3403
+ remainingIssues: unfixed.length,
3404
+ pluginErrors,
3405
+ scanComplete: pluginErrors === 0,
3406
+ plugins: results.map((r) => ({
3407
+ name: r.name,
3408
+ findings: r.findings,
3409
+ remediations: r.remediations,
3410
+ })),
3411
+ };
3412
+ console.log(JSON.stringify(jsonOutput, null, 2));
3413
+ if (pluginErrors > 0)
3414
+ process.exit(2);
3415
+ return;
3416
+ }
3417
+ // Summary
3418
+ console.log(`━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`);
3419
+ console.log(`\nFindings: ${allFindings.length} total | ${allRemediations.length} fixed\n`);
3420
+ // Show remaining issues (not auto-fixed)
3421
+ const fixedIds = new Set(allRemediations.map((r) => r.findingId));
3422
+ const remainingFindings = allFindings.filter((f) => !fixedIds.has(f.id) || !f.autoFixable);
3423
+ if (remainingFindings.length > 0) {
3424
+ console.log(`${colors.red}Remaining Issues (require manual review):${RESET()}\n`);
3425
+ for (const finding of remainingFindings) {
3426
+ const display = PLUGIN_SEVERITY_DISPLAY[finding.severity];
3427
+ console.log(`${display.color()}${display.symbol} [${finding.id}] ${finding.severity.toUpperCase()}${RESET()}`);
3428
+ console.log(` ${finding.title}`);
3429
+ console.log(` ${finding.description}`);
3430
+ if (finding.filePath) {
3431
+ console.log(` File: ${finding.filePath}`);
3432
+ }
3433
+ console.log();
3434
+ }
3435
+ }
3436
+ // Show remediations applied
3437
+ if (allRemediations.length > 0 && !options.scanOnly) {
3438
+ const label = options.dryRun ? 'Fixes Available (dry-run):' : 'Fixes Applied:';
3439
+ console.log(`${colors.green}[+] ${label}${RESET()}\n`);
3440
+ for (const remediation of allRemediations) {
3441
+ console.log(` ${colors.green}[+]${RESET()} [${remediation.findingId}] ${remediation.description}`);
3442
+ if (remediation.filesModified.length > 0 && options.verbose) {
3443
+ for (const file of remediation.filesModified) {
3444
+ console.log(` ${colors.cyan}→${RESET()} ${file}`);
3445
+ }
3446
+ }
3447
+ }
3448
+ console.log();
3449
+ if (!options.dryRun) {
3450
+ console.log(`${colors.cyan}Note:${RESET()} Plugin data stored in ${targetDir}/.opena2a/`);
3451
+ console.log(` Uninstall with: hackmyagent fix-all ${directory || '.'} --uninstall\n`);
3452
+ }
3453
+ }
3454
+ // All clear message
3455
+ if (allFindings.length === 0) {
3456
+ console.log(`${colors.green}[+] No security issues found. Agent looks good.${RESET()}\n`);
3457
+ }
3458
+ console.log(`━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`);
3459
+ console.log(`Run 'hackmyagent secure' for a full hardening scan.\n`);
3460
+ // Warn if scan is incomplete due to plugin errors
3461
+ if (pluginErrors > 0) {
3462
+ console.log(`\n${colors.brightRed}[!!] WARNING: ${pluginErrors} plugin(s) failed — scan results are incomplete${RESET()}`);
3463
+ console.log(` Re-run with --verbose for details.\n`);
3464
+ }
3465
+ // Exit with non-zero if critical/high issues remain or scan is incomplete
3466
+ if (pluginErrors > 0) {
3467
+ process.exit(2); // Exit 2 = partial/incomplete scan
3468
+ }
3469
+ const criticalOrHigh = remainingFindings.filter((f) => f.severity === 'critical' || f.severity === 'high');
3470
+ if (criticalOrHigh.length > 0) {
3471
+ process.exit(1);
3472
+ }
3473
+ }
3474
+ catch (error) {
3475
+ console.error(`Error: ${error instanceof Error ? error.message : 'Unknown error'}`);
3476
+ process.exit(1);
3477
+ }
3478
+ });
3479
+ // MCP Server command
3480
+ program
3481
+ .command('mcp-serve')
3482
+ .description('Run HackMyAgent as an MCP server (stdio transport)')
3483
+ .action(async () => {
3484
+ try {
3485
+ const { startMcpServer } = await Promise.resolve().then(() => __importStar(require('./mcp-server')));
3486
+ await startMcpServer();
3487
+ }
3488
+ catch (error) {
3489
+ console.error(`Error starting MCP server: ${error instanceof Error ? error.message : error}`);
3490
+ process.exit(1);
3491
+ }
3492
+ });
3493
+ // Init MCP command
3494
+ program
3495
+ .command('init-mcp')
3496
+ .description(`Add HackMyAgent as an MCP server to your AI coding tool
3497
+
3498
+ Detects your IDE (Claude Code, Cursor, VS Code) and configures
3499
+ HackMyAgent as an MCP server for LLM-powered security analysis.
3500
+
3501
+ Once configured, ask your AI assistant:
3502
+ "Run a deep security scan on this project"
3503
+
3504
+ Examples:
3505
+ $ hackmyagent init-mcp
3506
+ $ hackmyagent init-mcp --tool cursor
3507
+ $ hackmyagent init-mcp /path/to/project`)
3508
+ .argument('[directory]', 'Project directory (defaults to current directory)', '.')
3509
+ .option('-t, --tool <name>', 'Force specific tool: claude, cursor, vscode')
3510
+ .action(async (directory, options) => {
3511
+ try {
3512
+ const targetDir = directory.startsWith('/') ? directory : process.cwd() + '/' + directory;
3513
+ const { initMcp } = await Promise.resolve().then(() => __importStar(require('./init-mcp')));
3514
+ const result = initMcp(targetDir, options.tool);
3515
+ if (!result.created) {
3516
+ console.log(`\n HackMyAgent MCP server already configured in ${result.configPath}\n`);
3517
+ return;
3518
+ }
3519
+ console.log(`\n Detected: ${result.tool}\n`);
3520
+ console.log(` Added HackMyAgent MCP server to ${result.configPath}\n`);
3521
+ console.log(` Available tools in ${result.tool}:`);
3522
+ console.log(` hackmyagent_scan — 147+ checks + structural analysis`);
3523
+ console.log(` hackmyagent_deep_scan — Full analysis with LLM reasoning`);
3524
+ console.log(` hackmyagent_analyze_file — Analyze a single file`);
3525
+ console.log(` hackmyagent_benchmark — OASB-1 compliance assessment\n`);
3526
+ console.log(` Try: "Run a deep security scan on this project"\n`);
3527
+ }
3528
+ catch (error) {
3529
+ console.error(`Error: ${error instanceof Error ? error.message : error}`);
3530
+ process.exit(1);
3531
+ }
3532
+ });
3533
+ program.parse();
3534
+ //# sourceMappingURL=cli.js.map