agent-threat-rules 0.4.0 → 1.0.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 (291) hide show
  1. package/README.md +161 -52
  2. package/package.json +3 -1
  3. package/rules/agent-manipulation/{ATR-2026-030-cross-agent-attack.yaml → ATR-2026-00030-cross-agent-attack.yaml} +3 -1
  4. package/rules/agent-manipulation/{ATR-2026-032-goal-hijacking.yaml → ATR-2026-00032-goal-hijacking.yaml} +3 -1
  5. package/rules/agent-manipulation/{ATR-2026-074-cross-agent-privilege-escalation.yaml → ATR-2026-00074-cross-agent-privilege-escalation.yaml} +3 -1
  6. package/rules/agent-manipulation/{ATR-2026-076-inter-agent-message-spoofing.yaml → ATR-2026-00076-inter-agent-message-spoofing.yaml} +3 -1
  7. package/rules/agent-manipulation/{ATR-2026-077-human-trust-exploitation.yaml → ATR-2026-00077-human-trust-exploitation.yaml} +3 -1
  8. package/rules/agent-manipulation/{ATR-2026-108-consensus-sybil-attack.yaml → ATR-2026-00108-consensus-sybil-attack.yaml} +3 -1
  9. package/rules/agent-manipulation/{ATR-2026-116-a2a-message-validation.yaml → ATR-2026-00116-a2a-message-validation.yaml} +4 -2
  10. package/rules/agent-manipulation/{ATR-2026-117-agent-identity-spoofing.yaml → ATR-2026-00117-agent-identity-spoofing.yaml} +4 -2
  11. package/rules/agent-manipulation/{ATR-2026-118-approval-fatigue.yaml → ATR-2026-00118-approval-fatigue.yaml} +3 -1
  12. package/rules/agent-manipulation/{ATR-2026-119-social-engineering-via-agent.yaml → ATR-2026-00119-social-engineering-via-agent.yaml} +3 -1
  13. package/rules/agent-manipulation/ATR-2026-00132-casual-authority-escalation.yaml +105 -0
  14. package/rules/agent-manipulation/ATR-2026-00139-casual-authority-redirect.yaml +53 -0
  15. package/rules/context-exfiltration/{ATR-2026-020-system-prompt-leak.yaml → ATR-2026-00020-system-prompt-leak.yaml} +3 -1
  16. package/rules/context-exfiltration/{ATR-2026-021-api-key-exposure.yaml → ATR-2026-00021-api-key-exposure.yaml} +3 -1
  17. package/rules/context-exfiltration/{ATR-2026-075-agent-memory-manipulation.yaml → ATR-2026-00075-agent-memory-manipulation.yaml} +3 -1
  18. package/rules/context-exfiltration/{ATR-2026-102-disguised-analytics-exfiltration.yaml → ATR-2026-00102-disguised-analytics-exfiltration.yaml} +3 -1
  19. package/rules/context-exfiltration/{ATR-2026-113-credential-theft.yaml → ATR-2026-00113-credential-theft.yaml} +3 -1
  20. package/rules/context-exfiltration/{ATR-2026-114-oauth-token-abuse.yaml → ATR-2026-00114-oauth-token-abuse.yaml} +3 -1
  21. package/rules/context-exfiltration/{ATR-2026-115-env-var-harvesting.yaml → ATR-2026-00115-env-var-harvesting.yaml} +3 -1
  22. package/rules/context-exfiltration/ATR-2026-00136-tool-response-data-piggyback.yaml +100 -0
  23. package/rules/context-exfiltration/ATR-2026-00141-example-format-key-leak.yaml +52 -0
  24. package/rules/context-exfiltration/ATR-2026-00142-piggyback-transition-words.yaml +55 -0
  25. package/rules/context-exfiltration/ATR-2026-00145-obfuscated-key-disclosure.yaml +49 -0
  26. package/rules/context-exfiltration/ATR-2026-00146-env-var-existence-probe.yaml +49 -0
  27. package/rules/data-poisoning/{ATR-2026-070-data-poisoning.yaml → ATR-2026-00070-data-poisoning.yaml} +3 -1
  28. package/rules/excessive-autonomy/{ATR-2026-050-runaway-agent-loop.yaml → ATR-2026-00050-runaway-agent-loop.yaml} +3 -1
  29. package/rules/excessive-autonomy/{ATR-2026-051-resource-exhaustion.yaml → ATR-2026-00051-resource-exhaustion.yaml} +3 -1
  30. package/rules/excessive-autonomy/{ATR-2026-052-cascading-failure.yaml → ATR-2026-00052-cascading-failure.yaml} +3 -1
  31. package/rules/excessive-autonomy/{ATR-2026-098-unauthorized-financial-action.yaml → ATR-2026-00098-unauthorized-financial-action.yaml} +3 -1
  32. package/rules/excessive-autonomy/{ATR-2026-099-high-risk-tool-gate.yaml → ATR-2026-00099-high-risk-tool-gate.yaml} +3 -1
  33. package/rules/model-security/{ATR-2026-072-model-behavior-extraction.yaml → ATR-2026-00072-model-behavior-extraction.yaml} +3 -1
  34. package/rules/model-security/{ATR-2026-073-malicious-finetuning-data.yaml → ATR-2026-00073-malicious-finetuning-data.yaml} +3 -1
  35. package/rules/privilege-escalation/{ATR-2026-040-privilege-escalation.yaml → ATR-2026-00040-privilege-escalation.yaml} +3 -1
  36. package/rules/privilege-escalation/{ATR-2026-041-scope-creep.yaml → ATR-2026-00041-scope-creep.yaml} +3 -1
  37. package/rules/privilege-escalation/{ATR-2026-107-delayed-execution-bypass.yaml → ATR-2026-00107-delayed-execution-bypass.yaml} +3 -1
  38. package/rules/privilege-escalation/{ATR-2026-110-eval-injection.yaml → ATR-2026-00110-eval-injection.yaml} +3 -1
  39. package/rules/privilege-escalation/{ATR-2026-111-shell-escape.yaml → ATR-2026-00111-shell-escape.yaml} +5 -3
  40. package/rules/privilege-escalation/{ATR-2026-112-dynamic-import-exploitation.yaml → ATR-2026-00112-dynamic-import-exploitation.yaml} +3 -1
  41. package/rules/privilege-escalation/ATR-2026-00143-casual-privilege-escalation.yaml +53 -0
  42. package/rules/privilege-escalation/ATR-2026-00144-rationalized-safety-bypass.yaml +49 -0
  43. package/rules/prompt-injection/{ATR-2026-001-direct-prompt-injection.yaml → ATR-2026-00001-direct-prompt-injection.yaml} +3 -1
  44. package/rules/prompt-injection/{ATR-2026-002-indirect-prompt-injection.yaml → ATR-2026-00002-indirect-prompt-injection.yaml} +3 -1
  45. package/rules/prompt-injection/{ATR-2026-003-jailbreak-attempt.yaml → ATR-2026-00003-jailbreak-attempt.yaml} +3 -1
  46. package/rules/prompt-injection/{ATR-2026-004-system-prompt-override.yaml → ATR-2026-00004-system-prompt-override.yaml} +3 -1
  47. package/rules/prompt-injection/{ATR-2026-005-multi-turn-injection.yaml → ATR-2026-00005-multi-turn-injection.yaml} +3 -1
  48. package/rules/prompt-injection/{ATR-2026-080-encoding-evasion.yaml → ATR-2026-00080-encoding-evasion.yaml} +3 -1
  49. package/rules/prompt-injection/{ATR-2026-081-semantic-multi-turn.yaml → ATR-2026-00081-semantic-multi-turn.yaml} +3 -1
  50. package/rules/prompt-injection/{ATR-2026-082-fingerprint-evasion.yaml → ATR-2026-00082-fingerprint-evasion.yaml} +3 -1
  51. package/rules/prompt-injection/{ATR-2026-083-indirect-tool-injection.yaml → ATR-2026-00083-indirect-tool-injection.yaml} +3 -1
  52. package/rules/prompt-injection/{ATR-2026-084-structured-data-injection.yaml → ATR-2026-00084-structured-data-injection.yaml} +3 -1
  53. package/rules/prompt-injection/{ATR-2026-085-audit-evasion.yaml → ATR-2026-00085-audit-evasion.yaml} +3 -1
  54. package/rules/prompt-injection/{ATR-2026-086-visual-spoofing.yaml → ATR-2026-00086-visual-spoofing.yaml} +3 -1
  55. package/rules/prompt-injection/{ATR-2026-087-rule-probing.yaml → ATR-2026-00087-rule-probing.yaml} +3 -1
  56. package/rules/prompt-injection/{ATR-2026-088-adaptive-countermeasure.yaml → ATR-2026-00088-adaptive-countermeasure.yaml} +3 -1
  57. package/rules/prompt-injection/{ATR-2026-089-polymorphic-skill.yaml → ATR-2026-00089-polymorphic-skill.yaml} +3 -1
  58. package/rules/prompt-injection/{ATR-2026-090-threat-intel-exfil.yaml → ATR-2026-00090-threat-intel-exfil.yaml} +3 -1
  59. package/rules/prompt-injection/{ATR-2026-091-nested-payload.yaml → ATR-2026-00091-nested-payload.yaml} +3 -1
  60. package/rules/prompt-injection/{ATR-2026-092-consensus-poisoning.yaml → ATR-2026-00092-consensus-poisoning.yaml} +3 -1
  61. package/rules/prompt-injection/{ATR-2026-093-gradual-escalation.yaml → ATR-2026-00093-gradual-escalation.yaml} +3 -1
  62. package/rules/prompt-injection/{ATR-2026-094-audit-bypass.yaml → ATR-2026-00094-audit-bypass.yaml} +3 -1
  63. package/rules/prompt-injection/{ATR-2026-097-cjk-injection-patterns.yaml → ATR-2026-00097-cjk-injection-patterns.yaml} +3 -1
  64. package/rules/prompt-injection/{ATR-2026-104-persona-hijacking.yaml → ATR-2026-00104-persona-hijacking.yaml} +3 -1
  65. package/rules/prompt-injection/ATR-2026-00130-indirect-authority-claim.yaml +103 -0
  66. package/rules/prompt-injection/ATR-2026-00131-fictional-academic-framing.yaml +99 -0
  67. package/rules/prompt-injection/ATR-2026-00133-paraphrase-injection.yaml +117 -0
  68. package/rules/prompt-injection/ATR-2026-00137-authority-claim-injection.yaml +52 -0
  69. package/rules/prompt-injection/ATR-2026-00138-fictional-framing-bypass.yaml +51 -0
  70. package/rules/prompt-injection/ATR-2026-00140-indirect-reference-reversal.yaml +52 -0
  71. package/rules/prompt-injection/ATR-2026-00148-language-switch-injection.yaml +71 -0
  72. package/rules/skill-compromise/{ATR-2026-060-skill-impersonation.yaml → ATR-2026-00060-skill-impersonation.yaml} +3 -1
  73. package/rules/skill-compromise/{ATR-2026-061-description-behavior-mismatch.yaml → ATR-2026-00061-description-behavior-mismatch.yaml} +3 -1
  74. package/rules/skill-compromise/{ATR-2026-062-hidden-capability.yaml → ATR-2026-00062-hidden-capability.yaml} +3 -1
  75. package/rules/skill-compromise/{ATR-2026-063-skill-chain-attack.yaml → ATR-2026-00063-skill-chain-attack.yaml} +3 -1
  76. package/rules/skill-compromise/{ATR-2026-064-over-permissioned-skill.yaml → ATR-2026-00064-over-permissioned-skill.yaml} +3 -1
  77. package/rules/skill-compromise/{ATR-2026-065-skill-update-attack.yaml → ATR-2026-00065-skill-update-attack.yaml} +3 -1
  78. package/rules/skill-compromise/{ATR-2026-066-parameter-injection.yaml → ATR-2026-00066-parameter-injection.yaml} +3 -1
  79. package/rules/skill-compromise/ATR-2026-00120-skill-instruction-injection.yaml +121 -0
  80. package/rules/skill-compromise/ATR-2026-00121-skill-dangerous-script.yaml +165 -0
  81. package/rules/skill-compromise/ATR-2026-00122-skill-weaponized-instruction.yaml +114 -0
  82. package/rules/skill-compromise/ATR-2026-00123-skill-overreach-permissions.yaml +118 -0
  83. package/rules/skill-compromise/ATR-2026-00124-skill-name-squatting.yaml +98 -0
  84. package/rules/skill-compromise/ATR-2026-00125-context-poisoning-compaction.yaml +93 -0
  85. package/rules/skill-compromise/ATR-2026-00126-skill-rug-pull-setup.yaml +99 -0
  86. package/rules/skill-compromise/ATR-2026-00127-subcommand-overflow.yaml +74 -0
  87. package/rules/skill-compromise/ATR-2026-00128-html-comment-hidden-payload.yaml +79 -0
  88. package/rules/skill-compromise/ATR-2026-00129-unicode-smuggling.yaml +73 -0
  89. package/rules/skill-compromise/ATR-2026-00134-fork-claim-impersonation.yaml +93 -0
  90. package/rules/skill-compromise/ATR-2026-00135-exfil-url-in-instructions.yaml +82 -0
  91. package/rules/skill-compromise/ATR-2026-00147-fork-impersonation.yaml +48 -0
  92. package/rules/tool-poisoning/{ATR-2026-010-mcp-malicious-response.yaml → ATR-2026-00010-mcp-malicious-response.yaml} +3 -1
  93. package/rules/tool-poisoning/{ATR-2026-011-tool-output-injection.yaml → ATR-2026-00011-tool-output-injection.yaml} +3 -1
  94. package/rules/tool-poisoning/{ATR-2026-012-unauthorized-tool-call.yaml → ATR-2026-00012-unauthorized-tool-call.yaml} +3 -1
  95. package/rules/tool-poisoning/{ATR-2026-013-tool-ssrf.yaml → ATR-2026-00013-tool-ssrf.yaml} +3 -1
  96. package/rules/tool-poisoning/{ATR-2026-095-supply-chain-poisoning.yaml → ATR-2026-00095-supply-chain-poisoning.yaml} +3 -1
  97. package/rules/tool-poisoning/{ATR-2026-096-registry-poisoning.yaml → ATR-2026-00096-registry-poisoning.yaml} +3 -1
  98. package/rules/tool-poisoning/{ATR-2026-100-consent-bypass-instruction.yaml → ATR-2026-00100-consent-bypass-instruction.yaml} +3 -1
  99. package/rules/tool-poisoning/{ATR-2026-101-trust-escalation-override.yaml → ATR-2026-00101-trust-escalation-override.yaml} +3 -1
  100. package/rules/tool-poisoning/{ATR-2026-103-hidden-safety-bypass-instruction.yaml → ATR-2026-00103-hidden-safety-bypass-instruction.yaml} +3 -1
  101. package/rules/tool-poisoning/{ATR-2026-105-silent-action-concealment.yaml → ATR-2026-00105-silent-action-concealment.yaml} +3 -1
  102. package/rules/tool-poisoning/{ATR-2026-106-schema-description-contradiction.yaml → ATR-2026-00106-schema-description-contradiction.yaml} +3 -1
  103. package/spec/atr-schema.yaml +32 -3
  104. package/dist/action-executor.d.ts +0 -44
  105. package/dist/action-executor.d.ts.map +0 -1
  106. package/dist/action-executor.js +0 -130
  107. package/dist/action-executor.js.map +0 -1
  108. package/dist/adapters/default-adapter.d.ts +0 -24
  109. package/dist/adapters/default-adapter.d.ts.map +0 -1
  110. package/dist/adapters/default-adapter.js +0 -51
  111. package/dist/adapters/default-adapter.js.map +0 -1
  112. package/dist/adapters/stdio-adapter.d.ts +0 -30
  113. package/dist/adapters/stdio-adapter.d.ts.map +0 -1
  114. package/dist/adapters/stdio-adapter.js +0 -128
  115. package/dist/adapters/stdio-adapter.js.map +0 -1
  116. package/dist/badge.d.ts +0 -42
  117. package/dist/badge.d.ts.map +0 -1
  118. package/dist/badge.js +0 -158
  119. package/dist/badge.js.map +0 -1
  120. package/dist/capability-extractor.d.ts +0 -35
  121. package/dist/capability-extractor.d.ts.map +0 -1
  122. package/dist/capability-extractor.js +0 -91
  123. package/dist/capability-extractor.js.map +0 -1
  124. package/dist/cli.d.ts +0 -12
  125. package/dist/cli.d.ts.map +0 -1
  126. package/dist/cli.js +0 -892
  127. package/dist/cli.js.map +0 -1
  128. package/dist/converters/elastic.d.ts +0 -36
  129. package/dist/converters/elastic.d.ts.map +0 -1
  130. package/dist/converters/elastic.js +0 -125
  131. package/dist/converters/elastic.js.map +0 -1
  132. package/dist/converters/index.d.ts +0 -28
  133. package/dist/converters/index.d.ts.map +0 -1
  134. package/dist/converters/index.js +0 -36
  135. package/dist/converters/index.js.map +0 -1
  136. package/dist/converters/splunk.d.ts +0 -19
  137. package/dist/converters/splunk.d.ts.map +0 -1
  138. package/dist/converters/splunk.js +0 -148
  139. package/dist/converters/splunk.js.map +0 -1
  140. package/dist/coverage-analyzer.d.ts +0 -43
  141. package/dist/coverage-analyzer.d.ts.map +0 -1
  142. package/dist/coverage-analyzer.js +0 -329
  143. package/dist/coverage-analyzer.js.map +0 -1
  144. package/dist/embedding/build-corpus.d.ts +0 -15
  145. package/dist/embedding/build-corpus.d.ts.map +0 -1
  146. package/dist/embedding/build-corpus.js +0 -105
  147. package/dist/embedding/build-corpus.js.map +0 -1
  148. package/dist/embedding/model-loader.d.ts +0 -41
  149. package/dist/embedding/model-loader.d.ts.map +0 -1
  150. package/dist/embedding/model-loader.js +0 -90
  151. package/dist/embedding/model-loader.js.map +0 -1
  152. package/dist/embedding/vector-store.d.ts +0 -41
  153. package/dist/embedding/vector-store.d.ts.map +0 -1
  154. package/dist/embedding/vector-store.js +0 -70
  155. package/dist/embedding/vector-store.js.map +0 -1
  156. package/dist/engine.d.ts +0 -163
  157. package/dist/engine.d.ts.map +0 -1
  158. package/dist/engine.js +0 -869
  159. package/dist/engine.js.map +0 -1
  160. package/dist/eval/corpus.d.ts +0 -42
  161. package/dist/eval/corpus.d.ts.map +0 -1
  162. package/dist/eval/corpus.js +0 -427
  163. package/dist/eval/corpus.js.map +0 -1
  164. package/dist/eval/eval-harness.d.ts +0 -44
  165. package/dist/eval/eval-harness.d.ts.map +0 -1
  166. package/dist/eval/eval-harness.js +0 -296
  167. package/dist/eval/eval-harness.js.map +0 -1
  168. package/dist/eval/index.d.ts +0 -13
  169. package/dist/eval/index.d.ts.map +0 -1
  170. package/dist/eval/index.js +0 -9
  171. package/dist/eval/index.js.map +0 -1
  172. package/dist/eval/metrics.d.ts +0 -74
  173. package/dist/eval/metrics.d.ts.map +0 -1
  174. package/dist/eval/metrics.js +0 -108
  175. package/dist/eval/metrics.js.map +0 -1
  176. package/dist/eval/pint-corpus.d.ts +0 -34
  177. package/dist/eval/pint-corpus.d.ts.map +0 -1
  178. package/dist/eval/pint-corpus.js +0 -109
  179. package/dist/eval/pint-corpus.js.map +0 -1
  180. package/dist/eval/rule-corpus.d.ts +0 -9
  181. package/dist/eval/rule-corpus.d.ts.map +0 -1
  182. package/dist/eval/rule-corpus.js +0 -4780
  183. package/dist/eval/rule-corpus.js.map +0 -1
  184. package/dist/eval/rule-metrics.d.ts +0 -34
  185. package/dist/eval/rule-metrics.d.ts.map +0 -1
  186. package/dist/eval/rule-metrics.js +0 -92
  187. package/dist/eval/rule-metrics.js.map +0 -1
  188. package/dist/eval/run-eval.d.ts +0 -7
  189. package/dist/eval/run-eval.d.ts.map +0 -1
  190. package/dist/eval/run-eval.js +0 -11
  191. package/dist/eval/run-eval.js.map +0 -1
  192. package/dist/eval/run-pint-benchmark.d.ts +0 -18
  193. package/dist/eval/run-pint-benchmark.d.ts.map +0 -1
  194. package/dist/eval/run-pint-benchmark.js +0 -159
  195. package/dist/eval/run-pint-benchmark.js.map +0 -1
  196. package/dist/flywheel.d.ts +0 -54
  197. package/dist/flywheel.d.ts.map +0 -1
  198. package/dist/flywheel.js +0 -121
  199. package/dist/flywheel.js.map +0 -1
  200. package/dist/hook-handler.d.ts +0 -61
  201. package/dist/hook-handler.d.ts.map +0 -1
  202. package/dist/hook-handler.js +0 -178
  203. package/dist/hook-handler.js.map +0 -1
  204. package/dist/index.d.ts +0 -62
  205. package/dist/index.d.ts.map +0 -1
  206. package/dist/index.js +0 -54
  207. package/dist/index.js.map +0 -1
  208. package/dist/layer-integration.d.ts +0 -55
  209. package/dist/layer-integration.d.ts.map +0 -1
  210. package/dist/layer-integration.js +0 -185
  211. package/dist/layer-integration.js.map +0 -1
  212. package/dist/loader.d.ts +0 -21
  213. package/dist/loader.d.ts.map +0 -1
  214. package/dist/loader.js +0 -124
  215. package/dist/loader.js.map +0 -1
  216. package/dist/mcp-server.d.ts +0 -13
  217. package/dist/mcp-server.d.ts.map +0 -1
  218. package/dist/mcp-server.js +0 -220
  219. package/dist/mcp-server.js.map +0 -1
  220. package/dist/mcp-tools/coverage-gaps.d.ts +0 -13
  221. package/dist/mcp-tools/coverage-gaps.d.ts.map +0 -1
  222. package/dist/mcp-tools/coverage-gaps.js +0 -55
  223. package/dist/mcp-tools/coverage-gaps.js.map +0 -1
  224. package/dist/mcp-tools/list-rules.d.ts +0 -17
  225. package/dist/mcp-tools/list-rules.d.ts.map +0 -1
  226. package/dist/mcp-tools/list-rules.js +0 -45
  227. package/dist/mcp-tools/list-rules.js.map +0 -1
  228. package/dist/mcp-tools/scan.d.ts +0 -24
  229. package/dist/mcp-tools/scan.d.ts.map +0 -1
  230. package/dist/mcp-tools/scan.js +0 -94
  231. package/dist/mcp-tools/scan.js.map +0 -1
  232. package/dist/mcp-tools/submit-proposal.d.ts +0 -12
  233. package/dist/mcp-tools/submit-proposal.d.ts.map +0 -1
  234. package/dist/mcp-tools/submit-proposal.js +0 -103
  235. package/dist/mcp-tools/submit-proposal.js.map +0 -1
  236. package/dist/mcp-tools/threat-summary.d.ts +0 -12
  237. package/dist/mcp-tools/threat-summary.d.ts.map +0 -1
  238. package/dist/mcp-tools/threat-summary.js +0 -74
  239. package/dist/mcp-tools/threat-summary.js.map +0 -1
  240. package/dist/mcp-tools/validate.d.ts +0 -15
  241. package/dist/mcp-tools/validate.d.ts.map +0 -1
  242. package/dist/mcp-tools/validate.js +0 -45
  243. package/dist/mcp-tools/validate.js.map +0 -1
  244. package/dist/modules/embedding.d.ts +0 -71
  245. package/dist/modules/embedding.d.ts.map +0 -1
  246. package/dist/modules/embedding.js +0 -141
  247. package/dist/modules/embedding.js.map +0 -1
  248. package/dist/modules/index.d.ts +0 -144
  249. package/dist/modules/index.d.ts.map +0 -1
  250. package/dist/modules/index.js +0 -82
  251. package/dist/modules/index.js.map +0 -1
  252. package/dist/modules/semantic.d.ts +0 -106
  253. package/dist/modules/semantic.d.ts.map +0 -1
  254. package/dist/modules/semantic.js +0 -359
  255. package/dist/modules/semantic.js.map +0 -1
  256. package/dist/modules/session.d.ts +0 -70
  257. package/dist/modules/session.d.ts.map +0 -1
  258. package/dist/modules/session.js +0 -128
  259. package/dist/modules/session.js.map +0 -1
  260. package/dist/rule-scaffolder.d.ts +0 -53
  261. package/dist/rule-scaffolder.d.ts.map +0 -1
  262. package/dist/rule-scaffolder.js +0 -301
  263. package/dist/rule-scaffolder.js.map +0 -1
  264. package/dist/session-tracker.d.ts +0 -58
  265. package/dist/session-tracker.d.ts.map +0 -1
  266. package/dist/session-tracker.js +0 -176
  267. package/dist/session-tracker.js.map +0 -1
  268. package/dist/shadow-evaluator.d.ts +0 -48
  269. package/dist/shadow-evaluator.d.ts.map +0 -1
  270. package/dist/shadow-evaluator.js +0 -128
  271. package/dist/shadow-evaluator.js.map +0 -1
  272. package/dist/skill-fingerprint.d.ts +0 -85
  273. package/dist/skill-fingerprint.d.ts.map +0 -1
  274. package/dist/skill-fingerprint.js +0 -284
  275. package/dist/skill-fingerprint.js.map +0 -1
  276. package/dist/tier0-invariant.d.ts +0 -49
  277. package/dist/tier0-invariant.d.ts.map +0 -1
  278. package/dist/tier0-invariant.js +0 -184
  279. package/dist/tier0-invariant.js.map +0 -1
  280. package/dist/tier1-blacklist.d.ts +0 -48
  281. package/dist/tier1-blacklist.d.ts.map +0 -1
  282. package/dist/tier1-blacklist.js +0 -91
  283. package/dist/tier1-blacklist.js.map +0 -1
  284. package/dist/types.d.ts +0 -190
  285. package/dist/types.d.ts.map +0 -1
  286. package/dist/types.js +0 -6
  287. package/dist/types.js.map +0 -1
  288. package/dist/verdict.d.ts +0 -26
  289. package/dist/verdict.d.ts.map +0 -1
  290. package/dist/verdict.js +0 -127
  291. package/dist/verdict.js.map +0 -1
package/dist/cli.js DELETED
@@ -1,892 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * ATR CLI - Command-line interface for Agent Threat Rules
4
- *
5
- * Usage:
6
- * npx agent-threat-rules scan <events.json> Scan events against all rules
7
- * npx agent-threat-rules validate <rule.yaml> Validate a rule file
8
- * npx agent-threat-rules test <rule.yaml> Run a rule's test cases
9
- * npx agent-threat-rules stats Show rule collection stats
10
- */
11
- import { readFileSync, writeFileSync, readdirSync, existsSync, statSync, mkdirSync } from 'node:fs';
12
- import { resolve, dirname, join, sep } from 'node:path';
13
- import { homedir } from 'node:os';
14
- import { fileURLToPath } from 'node:url';
15
- import { ATREngine } from './engine.js';
16
- import { loadRuleFile, loadRulesFromDirectory, validateRule } from './loader.js';
17
- import { generateBadgeSvg, generateBadgeEndpoint, lookupPackageScan, generateBadgeMarkdown } from './badge.js';
18
- const __filename = fileURLToPath(import.meta.url);
19
- const __dirname = dirname(__filename);
20
- const RULES_DIR = resolve(__dirname, '..', 'rules');
21
- const SEVERITY_COLORS = {
22
- critical: '\x1b[91m', // bright red
23
- high: '\x1b[31m', // red
24
- medium: '\x1b[33m', // yellow
25
- low: '\x1b[36m', // cyan
26
- informational: '\x1b[37m', // white
27
- };
28
- const RESET = '\x1b[0m';
29
- const GREEN = '\x1b[32m';
30
- const RED = '\x1b[31m';
31
- const DIM = '\x1b[2m';
32
- const BOLD = '\x1b[1m';
33
- function printUsage() {
34
- console.log(`
35
- ${BOLD}ATR - Agent Threat Rules${RESET}
36
- Open detection rules for AI agent security threats.
37
-
38
- ${BOLD}Usage:${RESET}
39
- atr scan <events.json> [--rules <dir>] Scan events against ATR rules
40
- atr validate <rule.yaml|dir> Validate rule file(s)
41
- atr test <rule.yaml|dir> Run embedded test cases
42
- atr stats [--rules <dir>] Show rule collection statistics
43
- atr convert <splunk|elastic> [--rules <dir>] [--output <file>]
44
- Convert rules to SIEM query format
45
- atr guard [--rules <dir>] [--dry-run] Start as Claude Code hook (stdio)
46
- atr init [--global] Setup ATR guard hook for Claude Code
47
- atr mcp Start MCP server (stdio transport)
48
- atr scaffold Interactive rule scaffolding
49
- atr badge <package> [--data <audit.json>] [--svg] [--json]
50
- Generate ATR Scanned badge for a package
51
-
52
- ${BOLD}Options:${RESET}
53
- --rules <dir> Custom rules directory (default: bundled rules)
54
- --json Output results as JSON
55
- --output <file> Write output to file instead of stdout (convert)
56
- --severity <s> Minimum severity to report (critical|high|medium|low|informational)
57
- --dry-run Log actions without executing (guard mode)
58
- --fail-open Default to allow on errors (guard mode, default: true)
59
- --timeout <ms> Evaluation timeout in ms (guard mode, default: 5000)
60
- --global Write hook to ~/.claude/settings.json instead of project (init)
61
- --help Show this help message
62
-
63
- ${BOLD}Examples:${RESET}
64
- ${DIM}# Scan agent events for threats${RESET}
65
- atr scan events.json
66
-
67
- ${DIM}# Validate a custom rule${RESET}
68
- atr validate my-rules/custom-rule.yaml
69
-
70
- ${DIM}# Test all rules against their embedded test cases${RESET}
71
- atr test rules/
72
-
73
- ${DIM}# Show stats for bundled rules${RESET}
74
- atr stats
75
-
76
- ${DIM}# One-command Claude Code hook setup${RESET}
77
- atr init
78
-
79
- ${DIM}# Run as a Claude Code guard hook${RESET}
80
- atr guard --rules ./my-rules
81
-
82
- ${DIM}# Start MCP server for AI agent integration${RESET}
83
- atr mcp
84
-
85
- ${DIM}# Convert all rules to Splunk SPL${RESET}
86
- atr convert splunk --output splunk-queries.txt
87
-
88
- ${DIM}# Convert all rules to Elasticsearch Query DSL${RESET}
89
- atr convert elastic --json --output elastic-queries.json
90
-
91
- ${DIM}# Interactively scaffold a new rule${RESET}
92
- atr scaffold
93
- `);
94
- }
95
- function parseArgs(argv) {
96
- const args = argv.slice(2);
97
- const command = args[0] ?? 'help';
98
- const options = {};
99
- let target = '';
100
- for (let i = 1; i < args.length; i++) {
101
- if (args[i].startsWith('--')) {
102
- const key = args[i].slice(2);
103
- if (key === 'json' || key === 'help' || key === 'dry-run' || key === 'fail-open' || key === 'global' || key === 'svg') {
104
- options[key] = 'true';
105
- }
106
- else {
107
- options[key] = args[++i] ?? '';
108
- }
109
- }
110
- else if (!target) {
111
- target = args[i];
112
- }
113
- }
114
- return { command, target, options };
115
- }
116
- // --- SCAN command ---
117
- async function cmdScan(target, options) {
118
- if (!target) {
119
- console.error(`${RED}Error: Missing events file. Usage: atr scan <events.json>${RESET}`);
120
- process.exit(1);
121
- }
122
- const eventsPath = resolve(target);
123
- if (!existsSync(eventsPath)) {
124
- console.error(`${RED}Error: File not found: ${eventsPath}${RESET}`);
125
- process.exit(1);
126
- }
127
- const rulesDir = options['rules'] ? resolve(options['rules']) : RULES_DIR;
128
- const minSeverity = options['severity'] ?? 'informational';
129
- const jsonOutput = options['json'] === 'true';
130
- const raw = readFileSync(eventsPath, 'utf-8');
131
- let events;
132
- try {
133
- const parsed = JSON.parse(raw);
134
- events = Array.isArray(parsed) ? parsed : [parsed];
135
- }
136
- catch {
137
- console.error(`${RED}Error: Invalid JSON in ${eventsPath}${RESET}`);
138
- process.exit(1);
139
- }
140
- const engine = new ATREngine({ rulesDir });
141
- await engine.loadRules();
142
- const severityOrder = ['informational', 'low', 'medium', 'high', 'critical'];
143
- const minIdx = severityOrder.indexOf(minSeverity);
144
- const allMatches = [];
145
- let totalThreats = 0;
146
- for (const event of events) {
147
- const matches = engine.evaluate(event)
148
- .filter(m => severityOrder.indexOf(m.rule.severity) >= minIdx);
149
- if (matches.length > 0) {
150
- allMatches.push({ event, matches });
151
- totalThreats += matches.length;
152
- }
153
- }
154
- if (jsonOutput) {
155
- console.log(JSON.stringify({
156
- eventsScanned: events.length,
157
- threatsDetected: totalThreats,
158
- rulesLoaded: engine.getRuleCount(),
159
- results: allMatches.map(({ event, matches }) => ({
160
- event: { type: event.type, timestamp: event.timestamp, contentPreview: event.content.slice(0, 100) },
161
- matches: matches.map(m => ({
162
- ruleId: m.rule.id,
163
- title: m.rule.title,
164
- severity: m.rule.severity,
165
- confidence: m.confidence,
166
- matchedConditions: m.matchedConditions,
167
- })),
168
- })),
169
- }, null, 2));
170
- return;
171
- }
172
- console.log(`\n${BOLD}ATR Scan Results${RESET}`);
173
- console.log(`${DIM}${'─'.repeat(60)}${RESET}`);
174
- console.log(` Events scanned: ${events.length}`);
175
- console.log(` Rules loaded: ${engine.getRuleCount()}`);
176
- console.log(` Threats found: ${totalThreats > 0 ? RED + totalThreats + RESET : GREEN + '0' + RESET}`);
177
- console.log(`${DIM}${'─'.repeat(60)}${RESET}`);
178
- console.log(`${DIM} Open source (MIT). Star: https://github.com/anthropic-security/agent-threat-rules${RESET}`);
179
- console.log('');
180
- if (totalThreats === 0) {
181
- console.log(`${GREEN}No threats detected.${RESET}\n`);
182
- return;
183
- }
184
- for (const { event, matches } of allMatches) {
185
- const preview = event.content.slice(0, 80).replace(/\n/g, ' ');
186
- console.log(` ${DIM}Event: [${event.type}] "${preview}..."${RESET}`);
187
- for (const m of matches) {
188
- const color = SEVERITY_COLORS[m.rule.severity] ?? '';
189
- console.log(` ${color}${m.rule.severity.toUpperCase().padEnd(13)}${RESET} ${m.rule.id} - ${m.rule.title}`);
190
- console.log(` ${DIM}Confidence: ${(m.confidence * 100).toFixed(0)}% | Conditions: ${m.matchedConditions.join(', ')}${RESET}`);
191
- }
192
- console.log('');
193
- }
194
- }
195
- // --- VALIDATE command ---
196
- function cmdValidate(target, options) {
197
- if (!target) {
198
- console.error(`${RED}Error: Missing rule file/directory. Usage: atr validate <rule.yaml|dir>${RESET}`);
199
- process.exit(1);
200
- }
201
- const targetPath = resolve(target);
202
- if (!existsSync(targetPath)) {
203
- console.error(`${RED}Error: Not found: ${targetPath}${RESET}`);
204
- process.exit(1);
205
- }
206
- const jsonOutput = options['json'] === 'true';
207
- const files = [];
208
- if (statSync(targetPath).isDirectory()) {
209
- collectYamlFiles(targetPath, files);
210
- }
211
- else {
212
- files.push(targetPath);
213
- }
214
- let passed = 0;
215
- let failed = 0;
216
- const results = [];
217
- for (const file of files) {
218
- try {
219
- const rule = loadRuleFile(file);
220
- const result = validateRule(rule);
221
- results.push({ file, valid: result.valid, errors: result.errors });
222
- if (result.valid) {
223
- passed++;
224
- }
225
- else {
226
- failed++;
227
- }
228
- }
229
- catch (e) {
230
- const msg = e instanceof Error ? e.message : String(e);
231
- results.push({ file, valid: false, errors: [msg] });
232
- failed++;
233
- }
234
- }
235
- if (jsonOutput) {
236
- console.log(JSON.stringify({ total: files.length, passed, failed, results }, null, 2));
237
- return;
238
- }
239
- console.log(`\n${BOLD}ATR Rule Validation${RESET}`);
240
- console.log(`${DIM}${'─'.repeat(60)}${RESET}`);
241
- for (const r of results) {
242
- const icon = r.valid ? `${GREEN}PASS${RESET}` : `${RED}FAIL${RESET}`;
243
- const shortPath = r.file.replace(process.cwd() + '/', '');
244
- console.log(` ${icon} ${shortPath}`);
245
- if (!r.valid) {
246
- for (const err of r.errors) {
247
- console.log(` ${RED}${err}${RESET}`);
248
- }
249
- }
250
- }
251
- console.log(`${DIM}${'─'.repeat(60)}${RESET}`);
252
- console.log(` ${GREEN}${passed} passed${RESET} ${failed > 0 ? RED + failed + ' failed' + RESET : ''}\n`);
253
- if (failed > 0)
254
- process.exit(1);
255
- }
256
- function collectYamlFiles(dir, out) {
257
- const entries = readdirSync(dir);
258
- for (const entry of entries) {
259
- const full = join(dir, entry);
260
- if (statSync(full).isDirectory()) {
261
- collectYamlFiles(full, out);
262
- }
263
- else if (full.endsWith('.yaml') || full.endsWith('.yml')) {
264
- out.push(full);
265
- }
266
- }
267
- }
268
- // --- TEST command ---
269
- async function cmdTest(target, options) {
270
- if (!target) {
271
- // Default: test bundled rules
272
- target = RULES_DIR;
273
- }
274
- const targetPath = resolve(target);
275
- if (!existsSync(targetPath)) {
276
- console.error(`${RED}Error: Not found: ${targetPath}${RESET}`);
277
- process.exit(1);
278
- }
279
- const jsonOutput = options['json'] === 'true';
280
- const rules = [];
281
- if (statSync(targetPath).isDirectory()) {
282
- rules.push(...loadRulesFromDirectory(targetPath));
283
- }
284
- else {
285
- rules.push(loadRuleFile(targetPath));
286
- }
287
- let totalTests = 0;
288
- let passed = 0;
289
- let failed = 0;
290
- const failures = [];
291
- // Map extended agent_source types to basic event-compatible source types
292
- // so the engine's source type filter doesn't skip rules during testing.
293
- // The engine only recognizes: llm_io, tool_call, mcp_exchange, agent_behavior, multi_agent_comm
294
- const EXTENDED_SOURCE_TO_BASE = {
295
- context_window: 'llm_io',
296
- memory_access: 'llm_io',
297
- skill_lifecycle: 'tool_call',
298
- skill_permission: 'tool_call',
299
- skill_chain: 'tool_call',
300
- };
301
- for (const rule of rules) {
302
- if (!rule.test_cases)
303
- continue;
304
- // For testing, normalize extended source types so the engine doesn't filter them out
305
- const originalSourceType = rule.agent_source?.type;
306
- const baseSourceType = EXTENDED_SOURCE_TO_BASE[originalSourceType ?? ''];
307
- // Override status so draft/deprecated rules are not skipped during testing
308
- const testRule = {
309
- ...rule,
310
- status: 'experimental',
311
- ...(baseSourceType
312
- ? { agent_source: { ...rule.agent_source, type: baseSourceType } }
313
- : {}),
314
- };
315
- const engine = new ATREngine({ rules: [testRule] });
316
- await engine.loadRules();
317
- const tp = rule.test_cases.true_positives ?? [];
318
- const tn = rule.test_cases.true_negatives ?? [];
319
- for (const tc of tp) {
320
- totalTests++;
321
- const event = buildEventFromTestCase(tc, rule);
322
- const matches = engine.evaluate(event);
323
- const triggered = matches.some(m => m.rule.id === rule.id);
324
- if (triggered) {
325
- passed++;
326
- }
327
- else {
328
- failed++;
329
- failures.push({
330
- ruleId: rule.id,
331
- testType: 'true_positive',
332
- input: String(tc.input ?? tc.tool_response ?? tc.agent_output ?? '').slice(0, 100),
333
- expected: 'triggered',
334
- got: 'not_triggered',
335
- });
336
- }
337
- }
338
- for (const tc of tn) {
339
- totalTests++;
340
- const event = buildEventFromTestCase(tc, rule);
341
- const matches = engine.evaluate(event);
342
- const triggered = matches.some(m => m.rule.id === rule.id);
343
- if (!triggered) {
344
- passed++;
345
- }
346
- else {
347
- failed++;
348
- failures.push({
349
- ruleId: rule.id,
350
- testType: 'true_negative',
351
- input: String(tc.input ?? tc.tool_response ?? tc.agent_output ?? '').slice(0, 100),
352
- expected: 'not_triggered',
353
- got: 'triggered',
354
- });
355
- }
356
- }
357
- }
358
- if (jsonOutput) {
359
- console.log(JSON.stringify({ totalRules: rules.length, totalTests, passed, failed, failures }, null, 2));
360
- return;
361
- }
362
- console.log(`\n${BOLD}ATR Test Runner${RESET}`);
363
- console.log(`${DIM}${'─'.repeat(60)}${RESET}`);
364
- console.log(` Rules tested: ${rules.length}`);
365
- console.log(` Test cases: ${totalTests}`);
366
- console.log(` ${GREEN}Passed:${RESET} ${passed}`);
367
- if (failed > 0) {
368
- console.log(` ${RED}Failed:${RESET} ${failed}`);
369
- }
370
- console.log(`${DIM}${'─'.repeat(60)}${RESET}`);
371
- if (failures.length > 0) {
372
- console.log(`\n${RED}Failures:${RESET}\n`);
373
- for (const f of failures) {
374
- console.log(` ${RED}FAIL${RESET} ${f.ruleId} [${f.testType}]`);
375
- console.log(` ${DIM}Input: "${f.input}..."${RESET}`);
376
- console.log(` Expected: ${f.expected}, Got: ${f.got}\n`);
377
- }
378
- process.exit(1);
379
- }
380
- else {
381
- console.log(`\n${GREEN}All tests passed.${RESET}\n`);
382
- }
383
- }
384
- function buildEventFromTestCase(tc, rule) {
385
- // Stringify any object values
386
- const str = (v) => {
387
- if (v === undefined || v === null)
388
- return '';
389
- if (typeof v === 'string')
390
- return v;
391
- return JSON.stringify(v);
392
- };
393
- // Extract fields, handling multiple test case formats:
394
- // Object-style: input: { tool_name: "...", tool_args: "...", response: "..." }
395
- // Flat-style: input: "...", tool_response: "...", tool_name: "..."
396
- // Tool-call-style: tool_call: { name: "...", args: "..." }
397
- const rawInput = tc['input'];
398
- let input = '';
399
- let toolName = str(tc['tool_name']);
400
- let toolArgs = str(tc['tool_args']);
401
- let toolResponse = str(tc['tool_response']);
402
- const agentOutput = str(tc['agent_output']);
403
- const toolDescription = str(tc['tool_description']);
404
- const rawContent = str(tc['content']);
405
- // Handle tool_call: { name, args } format (used by tool_call rules like ATR-2026-098)
406
- const rawToolCall = tc['tool_call'];
407
- if (rawToolCall !== null && rawToolCall !== undefined && typeof rawToolCall === 'object' && !Array.isArray(rawToolCall)) {
408
- const tcObj = rawToolCall;
409
- if (tcObj['name'] && !toolName)
410
- toolName = str(tcObj['name']);
411
- if (tcObj['args'] && !toolArgs)
412
- toolArgs = str(tcObj['args']);
413
- }
414
- if (rawInput !== null && rawInput !== undefined && typeof rawInput === 'object' && !Array.isArray(rawInput)) {
415
- const inputObj = rawInput;
416
- if (inputObj['tool_name'] && !toolName)
417
- toolName = str(inputObj['tool_name']);
418
- if (inputObj['tool_args'] && !toolArgs)
419
- toolArgs = str(inputObj['tool_args']);
420
- // Handle 'response' as alias for 'tool_response' (used in ATR-065 etc.)
421
- if (inputObj['response'] && !toolResponse)
422
- toolResponse = str(inputObj['response']);
423
- if (inputObj['tool_response'] && !toolResponse)
424
- toolResponse = str(inputObj['tool_response']);
425
- // For object inputs, use tool_args as the primary string input.
426
- // If no tool_args, only use JSON-stringified input as fallback when there's
427
- // no other meaningful field (tool_response/response) extracted.
428
- if (toolArgs) {
429
- input = toolArgs;
430
- }
431
- else if (!toolResponse) {
432
- input = str(rawInput);
433
- }
434
- }
435
- else {
436
- input = str(rawInput);
437
- }
438
- // Infer event type from rule's agent_source and test case structure
439
- const sourceType = rule.agent_source?.type ?? 'llm_io';
440
- const SOURCE_TO_EVENT = {
441
- llm_io: 'llm_input',
442
- tool_call: 'tool_call',
443
- mcp_exchange: 'tool_response',
444
- agent_behavior: 'agent_behavior',
445
- multi_agent_comm: 'multi_agent_message',
446
- context_window: 'llm_input',
447
- memory_access: 'llm_input',
448
- skill_lifecycle: 'tool_call',
449
- skill_permission: 'tool_call',
450
- skill_chain: 'tool_call',
451
- };
452
- let type = SOURCE_TO_EVENT[sourceType] ?? 'llm_input';
453
- // If rule expects tool_call but test case has only plain text input
454
- // (no tool_name, tool_args, or tool_response), use llm_input event type
455
- // since the content is natural language, not a tool invocation.
456
- // This prevents the engine's tool_name fallback from treating text as a tool name.
457
- if (type === 'tool_call' && !toolName && !toolArgs && !toolResponse && !toolDescription) {
458
- type = 'llm_input';
459
- }
460
- // Determine the primary content based on what the rule conditions check
461
- // Problem 2 & 3: For rules that check tool_response, the tool_response
462
- // should be the primary content when the event type resolves tool_response from content
463
- let content;
464
- if (toolResponse && type === 'tool_response') {
465
- // If event type is tool_response, engine resolves field:tool_response from event.content
466
- content = toolResponse;
467
- }
468
- else {
469
- content = input || toolDescription || rawContent || toolResponse || agentOutput || '';
470
- }
471
- const fields = {};
472
- // Populate ALL known field aliases so the engine can resolve any field name
473
- if (input)
474
- fields['user_input'] = input;
475
- if (toolResponse)
476
- fields['tool_response'] = toolResponse;
477
- if (agentOutput)
478
- fields['agent_output'] = agentOutput;
479
- if (toolDescription)
480
- fields['tool_description'] = toolDescription;
481
- // Always set tool_name (even empty) to prevent engine fallback
482
- // from using event.content as tool_name for tool_call events
483
- fields['tool_name'] = toolName;
484
- if (toolArgs)
485
- fields['tool_args'] = toolArgs;
486
- // For content-based rules, set content and agent_message fields
487
- fields['content'] = content;
488
- fields['agent_message'] = content;
489
- return {
490
- type,
491
- timestamp: new Date().toISOString(),
492
- content,
493
- fields,
494
- sessionId: 'test-session',
495
- agentId: 'test-agent',
496
- };
497
- }
498
- // --- STATS command ---
499
- function cmdStats(options) {
500
- const rulesDir = options['rules'] ? resolve(options['rules']) : RULES_DIR;
501
- const jsonOutput = options['json'] === 'true';
502
- const rules = loadRulesFromDirectory(rulesDir);
503
- const byCategory = {};
504
- const bySeverity = {};
505
- const byMaturity = {};
506
- const byTier = {};
507
- let totalTP = 0;
508
- let totalTN = 0;
509
- let totalEvasion = 0;
510
- let withCVE = 0;
511
- for (const rule of rules) {
512
- const cat = rule.tags?.category ?? 'unknown';
513
- byCategory[cat] = (byCategory[cat] ?? 0) + 1;
514
- bySeverity[rule.severity] = (bySeverity[rule.severity] ?? 0) + 1;
515
- const maturity = rule.maturity ?? 'experimental';
516
- byMaturity[maturity] = (byMaturity[maturity] ?? 0) + 1;
517
- const tier = rule.detection_tier ?? 'pattern';
518
- byTier[tier] = (byTier[tier] ?? 0) + 1;
519
- if (rule.test_cases) {
520
- totalTP += rule.test_cases.true_positives?.length ?? 0;
521
- totalTN += rule.test_cases.true_negatives?.length ?? 0;
522
- }
523
- const evasion = rule['evasion_tests'];
524
- if (Array.isArray(evasion))
525
- totalEvasion += evasion.length;
526
- if (rule.references?.cve && rule.references.cve.length > 0)
527
- withCVE++;
528
- }
529
- if (jsonOutput) {
530
- console.log(JSON.stringify({
531
- totalRules: rules.length,
532
- byCategory, bySeverity, byMaturity, byTier,
533
- testCases: { truePositives: totalTP, trueNegatives: totalTN, evasionTests: totalEvasion },
534
- rulesWithCVE: withCVE,
535
- }, null, 2));
536
- return;
537
- }
538
- console.log(`\n${BOLD}ATR Rule Collection Statistics${RESET}`);
539
- console.log(`${DIM}${'─'.repeat(60)}${RESET}`);
540
- console.log(` Total rules: ${rules.length}`);
541
- console.log(` True positives: ${totalTP}`);
542
- console.log(` True negatives: ${totalTN}`);
543
- console.log(` Evasion tests: ${totalEvasion}`);
544
- console.log(` Rules with CVE: ${withCVE}`);
545
- console.log(`\n ${BOLD}By Category:${RESET}`);
546
- for (const [cat, count] of Object.entries(byCategory).sort((a, b) => b[1] - a[1])) {
547
- console.log(` ${cat.padEnd(24)} ${count}`);
548
- }
549
- console.log(`\n ${BOLD}By Severity:${RESET}`);
550
- for (const sev of ['critical', 'high', 'medium', 'low', 'informational']) {
551
- if (bySeverity[sev]) {
552
- const color = SEVERITY_COLORS[sev] ?? '';
553
- console.log(` ${color}${sev.padEnd(24)}${RESET} ${bySeverity[sev]}`);
554
- }
555
- }
556
- console.log(`\n ${BOLD}By Maturity:${RESET}`);
557
- for (const [mat, count] of Object.entries(byMaturity).sort((a, b) => b[1] - a[1])) {
558
- console.log(` ${mat.padEnd(24)} ${count}`);
559
- }
560
- console.log(`\n ${BOLD}By Detection Tier:${RESET}`);
561
- for (const [tier, count] of Object.entries(byTier).sort((a, b) => b[1] - a[1])) {
562
- console.log(` ${tier.padEnd(24)} ${count}`);
563
- }
564
- console.log('');
565
- }
566
- // --- GUARD command ---
567
- async function cmdGuard(options) {
568
- const rulesDir = options['rules'] ? resolve(options['rules']) : RULES_DIR;
569
- const dryRun = options['dry-run'] === 'true';
570
- const failOpen = options['fail-open'] !== 'false';
571
- const parsedTimeout = options['timeout'] ? parseInt(options['timeout'], 10) : 5000;
572
- const timeoutMs = (Number.isFinite(parsedTimeout) && parsedTimeout > 0) ? parsedTimeout : 5000;
573
- const { ActionExecutor } = await import('./action-executor.js');
574
- const { StdioAdapter } = await import('./adapters/stdio-adapter.js');
575
- const { HookHandler } = await import('./hook-handler.js');
576
- const engine = new ATREngine({ rulesDir });
577
- const ruleCount = await engine.loadRules();
578
- const adapter = new StdioAdapter();
579
- const executor = new ActionExecutor({ adapter, dryRun });
580
- const handler = new HookHandler({ engine, executor, timeoutMs, failOpen });
581
- process.stderr.write(`[atr-guard] Loaded ${ruleCount} rules from ${rulesDir}` +
582
- `${dryRun ? ' (dry-run)' : ''}\n`);
583
- await handler.startStdioLoop();
584
- }
585
- // --- MCP command ---
586
- async function cmdMcp() {
587
- const { startMCPServer } = await import('./mcp-server.js');
588
- await startMCPServer();
589
- }
590
- // --- SCAFFOLD command ---
591
- async function cmdScaffold() {
592
- const { createInterface } = await import('node:readline');
593
- const { RuleScaffolder } = await import('./rule-scaffolder.js');
594
- const rl = createInterface({ input: process.stdin, output: process.stdout });
595
- const ask = (question) => new Promise((resolve) => rl.question(question, resolve));
596
- console.log(`\n${BOLD}ATR Rule Scaffolder${RESET}`);
597
- console.log(`${DIM}Generate a draft ATR detection rule interactively.${RESET}\n`);
598
- const title = await ask('Rule title: ');
599
- if (!title.trim()) {
600
- console.error(`${RED}Error: Title is required.${RESET}`);
601
- rl.close();
602
- process.exit(1);
603
- }
604
- const categories = [
605
- 'prompt-injection', 'tool-poisoning', 'context-exfiltration',
606
- 'agent-manipulation', 'privilege-escalation', 'excessive-autonomy',
607
- 'data-poisoning', 'model-abuse', 'skill-compromise',
608
- ];
609
- console.log(`\nCategories: ${categories.join(', ')}`);
610
- const category = await ask('Category: ');
611
- if (!categories.includes(category.trim())) {
612
- console.error(`${RED}Error: Invalid category.${RESET}`);
613
- rl.close();
614
- process.exit(1);
615
- }
616
- const attackDescription = await ask('Attack description: ');
617
- if (!attackDescription.trim()) {
618
- console.error(`${RED}Error: Description is required.${RESET}`);
619
- rl.close();
620
- process.exit(1);
621
- }
622
- console.log('\nEnter example payloads (one per line, empty line to finish):');
623
- const payloads = [];
624
- while (true) {
625
- const payload = await ask(` Payload ${payloads.length + 1}: `);
626
- if (!payload.trim())
627
- break;
628
- payloads.push(payload.trim());
629
- }
630
- if (payloads.length === 0) {
631
- console.error(`${RED}Error: At least one example payload is required.${RESET}`);
632
- rl.close();
633
- process.exit(1);
634
- }
635
- const severities = ['critical', 'high', 'medium', 'low', 'informational'];
636
- const severity = await ask(`Severity [${severities.join('/')}] (default: medium): `);
637
- const finalSeverity = severity.trim() && severities.includes(severity.trim())
638
- ? severity.trim()
639
- : 'medium';
640
- rl.close();
641
- const scaffolder = new RuleScaffolder();
642
- const result = scaffolder.scaffold({
643
- title: title.trim(),
644
- category: category.trim(),
645
- attackDescription: attackDescription.trim(),
646
- examplePayloads: payloads,
647
- severity: finalSeverity,
648
- });
649
- console.log(`\n${GREEN}Generated rule ${result.id}:${RESET}\n`);
650
- console.log(`${DIM}${'─'.repeat(60)}${RESET}`);
651
- console.log(result.yaml);
652
- console.log(`${DIM}${'─'.repeat(60)}${RESET}`);
653
- if (result.warnings.length > 0) {
654
- console.log(`\n${BOLD}Warnings:${RESET}`);
655
- for (const w of result.warnings) {
656
- console.log(` - ${w}`);
657
- }
658
- }
659
- console.log(`\n${DIM}Copy this YAML to a .yaml file in rules/${category.trim()}/ and validate with: atr validate <file>${RESET}\n`);
660
- }
661
- // --- INIT command ---
662
- function cmdInit(options) {
663
- const isGlobal = options['global'] === 'true';
664
- const cwd = process.cwd();
665
- // Detect Claude Code project (unless --global)
666
- if (!isGlobal) {
667
- const hasClaudeDir = existsSync(join(cwd, '.claude'));
668
- const hasClaudeMd = existsSync(join(cwd, 'CLAUDE.md'));
669
- if (!hasClaudeDir && !hasClaudeMd) {
670
- console.error(`${RED}Error: Not a Claude Code project (no .claude/ directory or CLAUDE.md found).${RESET}\n` +
671
- `Run this command from your project root, or use ${BOLD}atr init --global${RESET} to configure globally.`);
672
- process.exit(1);
673
- }
674
- }
675
- const hookEntry = {
676
- // Empty matcher means "match all tools" in Claude Code hooks —
677
- // this is intentional so every tool call is scanned against ATR rules.
678
- matcher: '',
679
- command: 'npx agent-threat-rules guard',
680
- };
681
- // Determine target settings file
682
- const settingsPath = isGlobal
683
- ? join(homedir(), '.claude', 'settings.json')
684
- : join(cwd, '.claude', 'settings.local.json');
685
- // Ensure parent directory exists
686
- const settingsDir = dirname(settingsPath);
687
- if (!existsSync(settingsDir)) {
688
- mkdirSync(settingsDir, { recursive: true });
689
- }
690
- // Read existing settings or start fresh
691
- let settings = {};
692
- if (existsSync(settingsPath)) {
693
- let raw;
694
- try {
695
- raw = readFileSync(settingsPath, 'utf-8');
696
- }
697
- catch (e) {
698
- const code = e.code;
699
- console.error(`${RED}Error: Cannot read ${settingsPath} (${code ?? 'permission denied'}). Check file permissions.${RESET}`);
700
- process.exit(1);
701
- }
702
- try {
703
- settings = JSON.parse(raw);
704
- }
705
- catch {
706
- console.error(`${RED}Error: Invalid JSON in ${settingsPath}. Fix the syntax manually or delete it and retry.${RESET}`);
707
- process.exit(1);
708
- }
709
- }
710
- // Navigate into hooks.PreToolUse, creating structure as needed
711
- if (!settings['hooks'] || typeof settings['hooks'] !== 'object') {
712
- settings['hooks'] = {};
713
- }
714
- const hooks = settings['hooks'];
715
- if (!Array.isArray(hooks['PreToolUse'])) {
716
- hooks['PreToolUse'] = [];
717
- }
718
- const preToolUse = hooks['PreToolUse'];
719
- // Check if hook is already configured (validate each element is an object before accessing .command)
720
- const alreadyConfigured = preToolUse.some((entry) => typeof entry === 'object' &&
721
- entry !== null &&
722
- !Array.isArray(entry) &&
723
- entry['command'] === hookEntry.command);
724
- if (alreadyConfigured) {
725
- console.log(`${GREEN}ATR guard hook already configured${RESET} in ${settingsPath}`);
726
- return;
727
- }
728
- // Add the hook entry
729
- preToolUse.push(hookEntry);
730
- // Write back
731
- writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n', 'utf-8');
732
- const relPath = settingsPath.startsWith(cwd + sep)
733
- ? settingsPath.slice(cwd.length + 1)
734
- : settingsPath;
735
- console.log(`\n${GREEN}ATR guard hook configured successfully.${RESET}\n`);
736
- console.log(` File: ${relPath}`);
737
- console.log(` Hook: PreToolUse -> npx agent-threat-rules guard\n`);
738
- console.log(`${DIM}Every tool call Claude Code makes will now be scanned against ATR`);
739
- console.log(`threat detection rules before execution. Suspicious actions will be`);
740
- console.log(`flagged with severity and recommendation.${RESET}\n`);
741
- }
742
- // --- CONVERT command ---
743
- async function cmdConvert(target, options) {
744
- const validFormats = ['splunk', 'elastic'];
745
- if (!target || !validFormats.includes(target)) {
746
- console.error(`${RED}Error: Specify format: atr convert <splunk|elastic>${RESET}`);
747
- process.exit(1);
748
- }
749
- const format = target;
750
- const rulesDir = options['rules'] ? resolve(options['rules']) : RULES_DIR;
751
- const outputFile = options['output'];
752
- const jsonOutput = options['json'] === 'true';
753
- const { convertAllRules } = await import('./converters/index.js');
754
- const results = convertAllRules(rulesDir, format);
755
- if (results.length === 0) {
756
- console.error(`${RED}Error: No rules found in ${rulesDir}${RESET}`);
757
- process.exit(1);
758
- }
759
- let output;
760
- if (jsonOutput) {
761
- output = JSON.stringify(results, null, 2);
762
- }
763
- else if (format === 'elastic') {
764
- // For Elastic, JSON output is the natural format
765
- output = JSON.stringify(results.map(r => JSON.parse(r.query)), null, 2);
766
- }
767
- else {
768
- // For Splunk, emit each query separated by blank lines
769
- output = results.map(r => r.query).join('\n\n' + '='.repeat(80) + '\n\n');
770
- }
771
- if (outputFile) {
772
- writeFileSync(resolve(outputFile), output, 'utf-8');
773
- console.log(`${GREEN}Converted ${results.length} rules to ${format} format.${RESET}`);
774
- console.log(` Output: ${resolve(outputFile)}`);
775
- }
776
- else {
777
- console.log(output);
778
- }
779
- }
780
- // --- Badge command ---
781
- function cmdBadge(target, options) {
782
- if (!target) {
783
- console.error(`${RED}Usage: atr badge <package-name> [--data <audit.json>] [--svg] [--json]${RESET}`);
784
- process.exit(1);
785
- }
786
- // Find audit data
787
- const dataPath = typeof options['data'] === 'string'
788
- ? resolve(options['data'])
789
- : resolve(__dirname, '..', 'data', 'audit-v3-full.json');
790
- const altDataPath = resolve(__dirname, '..', 'data', 'audit-v3-sample.json');
791
- let summary = null;
792
- if (existsSync(dataPath)) {
793
- summary = lookupPackageScan(dataPath, target);
794
- }
795
- if (!summary && existsSync(altDataPath)) {
796
- summary = lookupPackageScan(altDataPath, target);
797
- }
798
- const outputSvg = options['svg'] === 'true';
799
- const outputJson = options['json'] === 'true';
800
- if (outputSvg) {
801
- console.log(generateBadgeSvg(summary));
802
- return;
803
- }
804
- if (outputJson) {
805
- console.log(JSON.stringify(generateBadgeEndpoint(summary), null, 2));
806
- return;
807
- }
808
- // Default: human-readable output
809
- const endpoint = generateBadgeEndpoint(summary);
810
- console.log(`\n${BOLD}ATR Badge: ${target}${RESET}`);
811
- console.log(`${'─'.repeat(50)}`);
812
- if (summary) {
813
- const colorMap = {
814
- '#2ea44f': GREEN,
815
- '#dfb317': '\x1b[33m',
816
- '#e05d44': RED,
817
- '#9f9f9f': DIM,
818
- };
819
- const termColor = colorMap[endpoint.color] ?? '';
820
- console.log(` Status: ${termColor}${endpoint.message}${RESET}`);
821
- console.log(` Package: ${summary.packageName}@${summary.version ?? 'unknown'}`);
822
- console.log(` Risk: ${summary.riskLevel} (score: ${summary.riskScore})`);
823
- console.log(` Findings: critical=${summary.findings.critical} high=${summary.findings.high} medium=${summary.findings.medium} low=${summary.findings.low}`);
824
- if (summary.scannedAt) {
825
- console.log(` Scanned: ${summary.scannedAt}`);
826
- }
827
- }
828
- else {
829
- console.log(` ${DIM}No scan data found for "${target}"${RESET}`);
830
- console.log(` ${DIM}Run: atr badge ${target} --data <audit-results.json>${RESET}`);
831
- }
832
- console.log(`\n${BOLD}Embed:${RESET}`);
833
- console.log(` ${DIM}Markdown:${RESET} ${generateBadgeMarkdown(target)}`);
834
- console.log(` ${DIM}SVG:${RESET} atr badge ${target} --svg > badge.svg`);
835
- console.log(` ${DIM}JSON:${RESET} atr badge ${target} --json`);
836
- console.log();
837
- }
838
- // --- Main ---
839
- async function main() {
840
- const { command, target, options } = parseArgs(process.argv);
841
- if (command === 'help' || command === '--help' || command === '-h' || options['help']) {
842
- printUsage();
843
- return;
844
- }
845
- if (command === 'version' || command === '--version' || command === '-v') {
846
- const pkgPath = resolve(__dirname, '..', 'package.json');
847
- const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
848
- console.log(pkg.version);
849
- return;
850
- }
851
- switch (command) {
852
- case 'scan':
853
- await cmdScan(target, options);
854
- break;
855
- case 'validate':
856
- cmdValidate(target, options);
857
- break;
858
- case 'test':
859
- await cmdTest(target, options);
860
- break;
861
- case 'stats':
862
- cmdStats(options);
863
- break;
864
- case 'convert':
865
- await cmdConvert(target, options);
866
- break;
867
- case 'guard':
868
- await cmdGuard(options);
869
- break;
870
- case 'init':
871
- cmdInit(options);
872
- break;
873
- case 'mcp':
874
- await cmdMcp();
875
- break;
876
- case 'scaffold':
877
- await cmdScaffold();
878
- break;
879
- case 'badge':
880
- cmdBadge(target, options);
881
- break;
882
- default:
883
- console.error(`${RED}Unknown command: ${command}${RESET}`);
884
- printUsage();
885
- process.exit(1);
886
- }
887
- }
888
- main().catch(err => {
889
- console.error(`${RED}Error: ${err instanceof Error ? err.message : String(err)}${RESET}`);
890
- process.exit(1);
891
- });
892
- //# sourceMappingURL=cli.js.map