actshield 0.9.0__py3-none-any.whl

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 (366) hide show
  1. actshield/__init__.py +439 -0
  2. actshield/__main__.py +46 -0
  3. actshield/agents/__init__.py +12 -0
  4. actshield/agents/agent.py +200 -0
  5. actshield/agents/identity.py +114 -0
  6. actshield/api/__init__.py +34 -0
  7. actshield/api/models.py +198 -0
  8. actshield/api/server.py +669 -0
  9. actshield/api/service.py +599 -0
  10. actshield/approval/__init__.py +15 -0
  11. actshield/approval/approval.py +164 -0
  12. actshield/cli/__init__.py +8 -0
  13. actshield/cli/graph.py +49 -0
  14. actshield/cli/main.py +802 -0
  15. actshield/cli/shell.py +406 -0
  16. actshield/cli/theme.py +110 -0
  17. actshield/client.py +757 -0
  18. actshield/config.py +88 -0
  19. actshield/context/__init__.py +14 -0
  20. actshield/context/context.py +161 -0
  21. actshield/context/provenance.py +108 -0
  22. actshield/context/taint.py +48 -0
  23. actshield/decisions/decision.py +74 -0
  24. actshield/delegation/__init__.py +11 -0
  25. actshield/delegation/authority.py +63 -0
  26. actshield/delegation/delegation.py +180 -0
  27. actshield/demo.py +139 -0
  28. actshield/doctor.py +94 -0
  29. actshield/drift/__init__.py +27 -0
  30. actshield/drift/drift_engine.py +149 -0
  31. actshield/drift/drift_models.py +63 -0
  32. actshield/evaluation/__init__.py +1 -0
  33. actshield/evaluation/cases/__init__.py +1 -0
  34. actshield/evaluation/eval_suite.py +254 -0
  35. actshield/forensics/__init__.py +53 -0
  36. actshield/forensics/access_graph.py +216 -0
  37. actshield/forensics/authority_graph.py +186 -0
  38. actshield/forensics/delegation_graph.py +119 -0
  39. actshield/forensics/explanation.py +254 -0
  40. actshield/forensics/models.py +488 -0
  41. actshield/forensics/queries.py +367 -0
  42. actshield/forensics/serializers.py +68 -0
  43. actshield/forensics/service.py +310 -0
  44. actshield/forensics/snapshots.py +174 -0
  45. actshield/gates/__init__.py +13 -0
  46. actshield/gates/gate_models.py +46 -0
  47. actshield/gates/security_gate.py +191 -0
  48. actshield/gateway/__init__.py +13 -0
  49. actshield/gateway/http.py +191 -0
  50. actshield/gateway/mcp.py +292 -0
  51. actshield/incidents/__init__.py +25 -0
  52. actshield/incidents/incident.py +94 -0
  53. actshield/incidents/incident_engine.py +163 -0
  54. actshield/incidents/incident_rules.py +116 -0
  55. actshield/incidents/incident_state.py +58 -0
  56. actshield/integrations/__init__.py +24 -0
  57. actshield/integrations/ai_secura.py +169 -0
  58. actshield/integrations/ai_secura_analysis.py +236 -0
  59. actshield/integrations/apiris.py +122 -0
  60. actshield/integrations/few_shot.py +104 -0
  61. actshield/integrations/ollama_adapter.py +538 -0
  62. actshield/integrations/prompts/__init__.py +1 -0
  63. actshield/integrations/prompts/examples/__init__.py +1 -0
  64. actshield/integrations/security_packet.py +82 -0
  65. actshield/llm_config.py +89 -0
  66. actshield/offensive/__init__.py +95 -0
  67. actshield/offensive/adaptive/__init__.py +22 -0
  68. actshield/offensive/adaptive/adaptive_engine.py +165 -0
  69. actshield/offensive/adaptive/attack_selector.py +51 -0
  70. actshield/offensive/adaptive/boundary_search.py +99 -0
  71. actshield/offensive/adaptive/campaign_memory.py +123 -0
  72. actshield/offensive/adaptive/feedback.py +84 -0
  73. actshield/offensive/adaptive/mutation_planner.py +57 -0
  74. actshield/offensive/attack.py +78 -0
  75. actshield/offensive/campaign.py +219 -0
  76. actshield/offensive/corpus.py +155 -0
  77. actshield/offensive/engine.py +466 -0
  78. actshield/offensive/evaluator.py +116 -0
  79. actshield/offensive/evidence.py +63 -0
  80. actshield/offensive/generator.py +128 -0
  81. actshield/offensive/mutation.py +101 -0
  82. actshield/offensive/results.py +77 -0
  83. actshield/offensive/safety.py +80 -0
  84. actshield/offensive/strategies/__init__.py +22 -0
  85. actshield/offensive/strategies/authority_escalation.py +100 -0
  86. actshield/offensive/strategies/base.py +34 -0
  87. actshield/offensive/strategies/context_manipulation.py +100 -0
  88. actshield/offensive/strategies/data_exfiltration.py +100 -0
  89. actshield/offensive/strategies/delegation_attacks.py +100 -0
  90. actshield/offensive/strategies/mcp_attacks.py +100 -0
  91. actshield/offensive/strategies/prompt_injection.py +100 -0
  92. actshield/offensive/strategies/tool_poisoning.py +100 -0
  93. actshield/offensive/target.py +113 -0
  94. actshield/offensive/vulnerable_target.py +121 -0
  95. actshield/persistence/__init__.py +18 -0
  96. actshield/persistence/siem.py +102 -0
  97. actshield/persistence/storage.py +240 -0
  98. actshield/policy/__init__.py +12 -0
  99. actshield/policy/evaluator.py +294 -0
  100. actshield/policy/intent.py +113 -0
  101. actshield/policy/policy.py +48 -0
  102. actshield/posture/__init__.py +26 -0
  103. actshield/posture/posture_engine.py +186 -0
  104. actshield/posture/posture_models.py +152 -0
  105. actshield/posture/posture_rules.py +137 -0
  106. actshield/providers/__init__.py +32 -0
  107. actshield/providers/anthropic_provider.py +146 -0
  108. actshield/providers/base.py +160 -0
  109. actshield/providers/gemini_provider.py +133 -0
  110. actshield/providers/null_provider.py +40 -0
  111. actshield/providers/ollama_provider.py +135 -0
  112. actshield/providers/openai_provider.py +142 -0
  113. actshield/providers/registry.py +244 -0
  114. actshield/py.typed +0 -0
  115. actshield/response/__init__.py +14 -0
  116. actshield/response/response_actions.py +39 -0
  117. actshield/response/response_audit.py +32 -0
  118. actshield/response/response_engine.py +139 -0
  119. actshield/risk/models.py +104 -0
  120. actshield/serve.py +117 -0
  121. actshield/static/404.html +1 -0
  122. actshield/static/__next.__PAGE__.txt +24 -0
  123. actshield/static/__next._full.txt +20 -0
  124. actshield/static/__next._tree.txt +4 -0
  125. actshield/static/_next/static/Yp_swxN-C9UoCRNOwkYnY/_buildManifest.js +11 -0
  126. actshield/static/_next/static/Yp_swxN-C9UoCRNOwkYnY/_clientMiddlewareManifest.js +1 -0
  127. actshield/static/_next/static/Yp_swxN-C9UoCRNOwkYnY/_ssgManifest.js +1 -0
  128. actshield/static/_next/static/chunks/09kny2eqjc2or.js +1 -0
  129. actshield/static/_next/static/chunks/0c1zba1u9whdp.js +1 -0
  130. actshield/static/_next/static/chunks/0cz1d0mv5g_q7.js +1 -0
  131. actshield/static/_next/static/chunks/0dbjjeg01x25o.js +1 -0
  132. actshield/static/_next/static/chunks/0duobs55ne238.js +1 -0
  133. actshield/static/_next/static/chunks/0iaz41l6068e4.js +1 -0
  134. actshield/static/_next/static/chunks/0mjhyv72x2idp.js +1 -0
  135. actshield/static/_next/static/chunks/0nelpqfu_ciq6.js +1 -0
  136. actshield/static/_next/static/chunks/0p1bci9ch797v.js +1 -0
  137. actshield/static/_next/static/chunks/0tld_ygho5tgm.js +1 -0
  138. actshield/static/_next/static/chunks/0w1-y7oni66j9.js +1 -0
  139. actshield/static/_next/static/chunks/10537zyl427zj.js +1 -0
  140. actshield/static/_next/static/chunks/11pc_jkl3aggn.js +1 -0
  141. actshield/static/_next/static/chunks/16k7nw0b3t8ys.js +1 -0
  142. actshield/static/_next/static/chunks/19mx3mg6lkumu.js +1 -0
  143. actshield/static/_next/static/chunks/1_8p47b-kxg2a.js +1 -0
  144. actshield/static/_next/static/chunks/1dszjozwfqegx.js +1 -0
  145. actshield/static/_next/static/chunks/1h43jcb90-m2r.js +22 -0
  146. actshield/static/_next/static/chunks/1ly04cvt_wh_j.js +1 -0
  147. actshield/static/_next/static/chunks/1odxrypuwuwp_.js +1 -0
  148. actshield/static/_next/static/chunks/1oes4c35qz5ku.js +1 -0
  149. actshield/static/_next/static/chunks/1ow883jl4ln-r.js +16 -0
  150. actshield/static/_next/static/chunks/1po7bna1as8tc.js +1 -0
  151. actshield/static/_next/static/chunks/1rsh0og8ifkzx.js +1 -0
  152. actshield/static/_next/static/chunks/1v0lwzv-859z_.js +1 -0
  153. actshield/static/_next/static/chunks/25iat2epwx25a.js +1 -0
  154. actshield/static/_next/static/chunks/25uvt4s2f39a4.js +1 -0
  155. actshield/static/_next/static/chunks/27t_qfc-3_lzs.js +1 -0
  156. actshield/static/_next/static/chunks/28bkmy8mez7gm.js +1 -0
  157. actshield/static/_next/static/chunks/2b6brmw_drw1p.js +1 -0
  158. actshield/static/_next/static/chunks/2fnhtxi4j6vge.js +1 -0
  159. actshield/static/_next/static/chunks/2p087eftxemg4.js +6 -0
  160. actshield/static/_next/static/chunks/2s8pao_z8pgtq.js +1 -0
  161. actshield/static/_next/static/chunks/2yj7ooc2c-zpg.js +1 -0
  162. actshield/static/_next/static/chunks/3-mrrr8udt5tk.js +1 -0
  163. actshield/static/_next/static/chunks/310vm2bl3xxpt.js +31 -0
  164. actshield/static/_next/static/chunks/35mvsv251jx1r.css +3 -0
  165. actshield/static/_next/static/chunks/370vf0oetlsx9.js +1 -0
  166. actshield/static/_next/static/chunks/3e-2m72tyo556.js +1 -0
  167. actshield/static/_next/static/chunks/3fntmmi971322.js +1 -0
  168. actshield/static/_next/static/chunks/3fzw-g0hycl2c.js +1 -0
  169. actshield/static/_next/static/chunks/3lopq3lhmcjxg.js +129 -0
  170. actshield/static/_next/static/chunks/3mpe2jejel0v2.css +1 -0
  171. actshield/static/_next/static/chunks/3t8o4f2lzr2sf.js +1 -0
  172. actshield/static/_next/static/chunks/3y-v1neyllne5.js +1 -0
  173. actshield/static/_next/static/chunks/43oiwy8mx6cs1.js +1 -0
  174. actshield/static/_next/static/chunks/turbopack-23wzuebcy_lxm.js +1 -0
  175. actshield/static/_next/static/media/1bffadaabf893a1e-s.3-6t-g6q0vh0a.woff2 +0 -0
  176. actshield/static/_next/static/media/2bbe8d2671613f1f-s.0k62hbripvv8p.woff2 +0 -0
  177. actshield/static/_next/static/media/2c55a0e60120577a-s.0-dom-5bn10r2.woff2 +0 -0
  178. actshield/static/_next/static/media/5476f68d60460930-s.2uwcyprjm3xu3.woff2 +0 -0
  179. actshield/static/_next/static/media/83afe278b6a6bb3c-s.p.2bn3s6zvc0dyp.woff2 +0 -0
  180. actshield/static/_next/static/media/9c72aa0f40e4eef8-s.1y4-pdgsjb-pw.woff2 +0 -0
  181. actshield/static/_next/static/media/ad66f9afd8947f86-s.3lvt2whj97whp.woff2 +0 -0
  182. actshield/static/_next/static/media/favicon.2vob68tjqpejf.ico +0 -0
  183. actshield/static/_not-found/__next._full.txt +17 -0
  184. actshield/static/_not-found/__next._not-found/__PAGE__.txt +23 -0
  185. actshield/static/_not-found/__next._tree.txt +3 -0
  186. actshield/static/_not-found.html +1 -0
  187. actshield/static/_not-found.txt +17 -0
  188. actshield/static/access/matrix/__next._full.txt +24 -0
  189. actshield/static/access/matrix/__next._tree.txt +4 -0
  190. actshield/static/access/matrix/__next.access/matrix/__PAGE__.txt +30 -0
  191. actshield/static/access/matrix.html +1 -0
  192. actshield/static/access/matrix.txt +24 -0
  193. actshield/static/activity/__next._full.txt +22 -0
  194. actshield/static/activity/__next._tree.txt +4 -0
  195. actshield/static/activity/__next.activity/__PAGE__.txt +27 -0
  196. actshield/static/activity.html +1 -0
  197. actshield/static/activity.txt +22 -0
  198. actshield/static/agents/__next._full.txt +22 -0
  199. actshield/static/agents/__next._tree.txt +4 -0
  200. actshield/static/agents/__next.agents/__PAGE__.txt +27 -0
  201. actshield/static/agents.html +1 -0
  202. actshield/static/agents.txt +22 -0
  203. actshield/static/ai-secura/__next._full.txt +22 -0
  204. actshield/static/ai-secura/__next._tree.txt +4 -0
  205. actshield/static/ai-secura/__next.ai-secura/__PAGE__.txt +27 -0
  206. actshield/static/ai-secura.html +1 -0
  207. actshield/static/ai-secura.txt +22 -0
  208. actshield/static/api/__next._full.txt +22 -0
  209. actshield/static/api/__next._tree.txt +4 -0
  210. actshield/static/api/__next.api/__PAGE__.txt +27 -0
  211. actshield/static/api.html +15 -0
  212. actshield/static/api.txt +22 -0
  213. actshield/static/apiris/__next._full.txt +22 -0
  214. actshield/static/apiris/__next._tree.txt +4 -0
  215. actshield/static/apiris/__next.apiris/__PAGE__.txt +27 -0
  216. actshield/static/apiris.html +1 -0
  217. actshield/static/apiris.txt +22 -0
  218. actshield/static/attack-graph/__next._full.txt +23 -0
  219. actshield/static/attack-graph/__next._tree.txt +5 -0
  220. actshield/static/attack-graph/__next.attack-graph/__PAGE__.txt +28 -0
  221. actshield/static/attack-graph.html +2 -0
  222. actshield/static/attack-graph.txt +23 -0
  223. actshield/static/campaigns/__next._full.txt +22 -0
  224. actshield/static/campaigns/__next._tree.txt +4 -0
  225. actshield/static/campaigns/__next.campaigns/__PAGE__.txt +27 -0
  226. actshield/static/campaigns.html +1 -0
  227. actshield/static/campaigns.txt +22 -0
  228. actshield/static/context/__next._full.txt +22 -0
  229. actshield/static/context/__next._tree.txt +4 -0
  230. actshield/static/context/__next.context/__PAGE__.txt +27 -0
  231. actshield/static/context.html +1 -0
  232. actshield/static/context.txt +22 -0
  233. actshield/static/dashboard/__next._full.txt +22 -0
  234. actshield/static/dashboard/__next._tree.txt +4 -0
  235. actshield/static/dashboard/__next.dashboard/__PAGE__.txt +27 -0
  236. actshield/static/dashboard.html +1 -0
  237. actshield/static/dashboard.txt +22 -0
  238. actshield/static/delegations/__next._full.txt +22 -0
  239. actshield/static/delegations/__next._tree.txt +4 -0
  240. actshield/static/delegations/__next.delegations/__PAGE__.txt +27 -0
  241. actshield/static/delegations.html +1 -0
  242. actshield/static/delegations.txt +22 -0
  243. actshield/static/demo/__next._full.txt +22 -0
  244. actshield/static/demo/__next._tree.txt +4 -0
  245. actshield/static/demo/__next.demo/__PAGE__.txt +27 -0
  246. actshield/static/demo.html +1 -0
  247. actshield/static/demo.txt +22 -0
  248. actshield/static/drift/__next._full.txt +22 -0
  249. actshield/static/drift/__next._tree.txt +4 -0
  250. actshield/static/drift/__next.drift/__PAGE__.txt +27 -0
  251. actshield/static/drift.html +1 -0
  252. actshield/static/drift.txt +22 -0
  253. actshield/static/favicon.ico +0 -0
  254. actshield/static/file.svg +1 -0
  255. actshield/static/forensics/__next._full.txt +22 -0
  256. actshield/static/forensics/__next._tree.txt +4 -0
  257. actshield/static/forensics/__next.forensics/__PAGE__.txt +27 -0
  258. actshield/static/forensics.html +1 -0
  259. actshield/static/forensics.txt +22 -0
  260. actshield/static/globe.svg +1 -0
  261. actshield/static/incidents/__next._full.txt +22 -0
  262. actshield/static/incidents/__next._tree.txt +4 -0
  263. actshield/static/incidents/__next.incidents/__PAGE__.txt +27 -0
  264. actshield/static/incidents.html +1 -0
  265. actshield/static/incidents.txt +22 -0
  266. actshield/static/index.html +3 -0
  267. actshield/static/index.txt +20 -0
  268. actshield/static/logo.png +0 -0
  269. actshield/static/metrics/__next._full.txt +22 -0
  270. actshield/static/metrics/__next._tree.txt +4 -0
  271. actshield/static/metrics/__next.metrics/__PAGE__.txt +27 -0
  272. actshield/static/metrics.html +1 -0
  273. actshield/static/metrics.txt +22 -0
  274. actshield/static/next.svg +1 -0
  275. actshield/static/offensive/__next._full.txt +22 -0
  276. actshield/static/offensive/__next._tree.txt +4 -0
  277. actshield/static/offensive/__next.offensive/__PAGE__.txt +27 -0
  278. actshield/static/offensive.html +1 -0
  279. actshield/static/offensive.txt +22 -0
  280. actshield/static/policies/__next._full.txt +22 -0
  281. actshield/static/policies/__next._tree.txt +4 -0
  282. actshield/static/policies/__next.policies/__PAGE__.txt +27 -0
  283. actshield/static/policies.html +1 -0
  284. actshield/static/policies.txt +22 -0
  285. actshield/static/posture/__next._full.txt +22 -0
  286. actshield/static/posture/__next._tree.txt +4 -0
  287. actshield/static/posture/__next.posture/__PAGE__.txt +27 -0
  288. actshield/static/posture.html +1 -0
  289. actshield/static/posture.txt +22 -0
  290. actshield/static/regressions/__next._full.txt +22 -0
  291. actshield/static/regressions/__next._tree.txt +4 -0
  292. actshield/static/regressions/__next.regressions/__PAGE__.txt +27 -0
  293. actshield/static/regressions.html +1 -0
  294. actshield/static/regressions.txt +22 -0
  295. actshield/static/reports/__next._full.txt +22 -0
  296. actshield/static/reports/__next._tree.txt +4 -0
  297. actshield/static/reports/__next.reports/__PAGE__.txt +27 -0
  298. actshield/static/reports.html +1 -0
  299. actshield/static/reports.txt +22 -0
  300. actshield/static/resources/__next._full.txt +22 -0
  301. actshield/static/resources/__next._tree.txt +4 -0
  302. actshield/static/resources/__next.resources/__PAGE__.txt +27 -0
  303. actshield/static/resources.html +1 -0
  304. actshield/static/resources.txt +22 -0
  305. actshield/static/security-gates/__next._full.txt +22 -0
  306. actshield/static/security-gates/__next._tree.txt +4 -0
  307. actshield/static/security-gates/__next.security-gates/__PAGE__.txt +27 -0
  308. actshield/static/security-gates.html +1 -0
  309. actshield/static/security-gates.txt +22 -0
  310. actshield/static/settings/__next._full.txt +22 -0
  311. actshield/static/settings/__next._tree.txt +4 -0
  312. actshield/static/settings/__next.settings/__PAGE__.txt +27 -0
  313. actshield/static/settings.html +1 -0
  314. actshield/static/settings.txt +22 -0
  315. actshield/static/tasks/__next._full.txt +22 -0
  316. actshield/static/tasks/__next._tree.txt +4 -0
  317. actshield/static/tasks/__next.tasks/__PAGE__.txt +27 -0
  318. actshield/static/tasks.html +1 -0
  319. actshield/static/tasks.txt +22 -0
  320. actshield/static/threats/__next._full.txt +22 -0
  321. actshield/static/threats/__next._tree.txt +4 -0
  322. actshield/static/threats/__next.threats/__PAGE__.txt +27 -0
  323. actshield/static/threats.html +1 -0
  324. actshield/static/threats.txt +22 -0
  325. actshield/static/tools/__next._full.txt +22 -0
  326. actshield/static/tools/__next._tree.txt +4 -0
  327. actshield/static/tools/__next.tools/__PAGE__.txt +27 -0
  328. actshield/static/tools.html +1 -0
  329. actshield/static/tools.txt +22 -0
  330. actshield/static/traces/__next._full.txt +22 -0
  331. actshield/static/traces/__next._tree.txt +4 -0
  332. actshield/static/traces/__next.traces/__PAGE__.txt +27 -0
  333. actshield/static/traces.html +1 -0
  334. actshield/static/traces.txt +22 -0
  335. actshield/static/vercel.svg +1 -0
  336. actshield/static/window.svg +1 -0
  337. actshield/tasks/__init__.py +6 -0
  338. actshield/tasks/task.py +143 -0
  339. actshield/threatmodel/__init__.py +70 -0
  340. actshield/threatmodel/actors.py +210 -0
  341. actshield/threatmodel/analyzer.py +131 -0
  342. actshield/threatmodel/assets.py +167 -0
  343. actshield/threatmodel/boundaries.py +165 -0
  344. actshield/threatmodel/exporters.py +49 -0
  345. actshield/threatmodel/mitigations.py +209 -0
  346. actshield/threatmodel/models.py +80 -0
  347. actshield/threatmodel/report.py +341 -0
  348. actshield/threatmodel/scenarios.py +212 -0
  349. actshield/threatmodel/scoring.py +66 -0
  350. actshield/threatmodel/threats.py +387 -0
  351. actshield/tools/__init__.py +20 -0
  352. actshield/tools/interceptor.py +256 -0
  353. actshield/tools/tool.py +71 -0
  354. actshield/tracing/__init__.py +38 -0
  355. actshield/tracing/correlation.py +173 -0
  356. actshield/tracing/events.py +75 -0
  357. actshield/tracing/tracer.py +305 -0
  358. actshield/watch.py +129 -0
  359. actshield-0.9.0.dist-info/METADATA +57 -0
  360. actshield-0.9.0.dist-info/RECORD +366 -0
  361. actshield-0.9.0.dist-info/WHEEL +5 -0
  362. actshield-0.9.0.dist-info/entry_points.txt +4 -0
  363. actshield-0.9.0.dist-info/top_level.txt +2 -0
  364. agentguard/__init__.py +39 -0
  365. agentguard/__main__.py +8 -0
  366. agentguard/py.typed +1 -0
@@ -0,0 +1,49 @@
1
+ """Threat model exporters — JSON, YAML, Markdown."""
2
+ from __future__ import annotations
3
+
4
+ import json
5
+ from typing import Literal
6
+
7
+ from actshield.threatmodel.models import AnalysisResult
8
+ from actshield.threatmodel.report import ThreatReportRenderer
9
+
10
+ ExportFormat = Literal["json", "yaml", "markdown"]
11
+
12
+
13
+ class ThreatModelExporter:
14
+ """Exports threat analysis results to external formats."""
15
+
16
+ def export(self, result: AnalysisResult, format: ExportFormat = "json") -> str:
17
+ if format == "json":
18
+ return self._to_json(result)
19
+ elif format == "yaml":
20
+ return self._to_yaml(result)
21
+ elif format == "markdown":
22
+ renderer = ThreatReportRenderer()
23
+ return renderer.to_markdown(result)
24
+ else:
25
+ raise ValueError(f"Unsupported export format: {format}. Use json, yaml, or markdown.")
26
+
27
+ def _to_json(self, result: AnalysisResult) -> str:
28
+ return json.dumps(result.to_dict(), indent=2, default=str)
29
+
30
+ def _to_yaml(self, result: AnalysisResult) -> str:
31
+ try:
32
+ import yaml # type: ignore[import]
33
+ return yaml.dump(result.to_dict(), default_flow_style=False, allow_unicode=True)
34
+ except ImportError:
35
+ # Fallback: minimal YAML-like output without PyYAML
36
+ data = result.to_dict()
37
+ lines = ["# ActShield Threat Model Export (YAML)"]
38
+ lines.append(f"overall_risk_score: {data.get('overall_risk_score', 0)}")
39
+ lines.append("model:")
40
+ model = data.get("model", {})
41
+ for k, v in model.items():
42
+ lines.append(f" {k}: {json.dumps(v)}")
43
+ lines.append("threats_by_severity:")
44
+ for sev, threats in data.get("threats_by_severity", {}).items():
45
+ lines.append(f" {sev}: {len(threats)} threat(s)")
46
+ lines.append(
47
+ "# Install PyYAML for full YAML export: pip install pyyaml"
48
+ )
49
+ return "\n".join(lines)
@@ -0,0 +1,209 @@
1
+ """Control-to-threat mapping — how ActShield controls mitigate each threat."""
2
+ from __future__ import annotations
3
+
4
+ from pydantic import BaseModel, Field
5
+
6
+ from actshield.threatmodel.models import ControlEffectiveness
7
+
8
+
9
+ class Mitigation(BaseModel):
10
+ """A single ActShield control and its relationship to a threat."""
11
+ control_id: str
12
+ control_name: str
13
+ description: str
14
+ # How the control works mechanically
15
+ mechanism: str
16
+ # Enforcement action
17
+ enforcement_action: str # BLOCK | MONITOR | DETECT | ALERT
18
+ # Where in the ActShield pipeline this runs
19
+ pipeline_stage: str # provenance | taint | intent | authority | policy | evidence
20
+
21
+
22
+ class ControlMapping(BaseModel):
23
+ """Maps a threat to the set of ActShield controls that mitigate it."""
24
+ threat_id: str
25
+ controls: list[Mitigation] = Field(default_factory=list)
26
+ combined_effectiveness: ControlEffectiveness = ControlEffectiveness.REDUCES
27
+ notes: str = ""
28
+
29
+
30
+ # ── ActShield control definitions ─────────────────────────────────────────────
31
+
32
+ ACTSHIELD_CONTROLS: dict[str, Mitigation] = {
33
+ "provenance_tracking": Mitigation(
34
+ control_id="ctl_provenance",
35
+ control_name="Context Provenance Tracking",
36
+ description=(
37
+ "Every piece of context entering the agent system is tagged with its "
38
+ "origin source, trust level, and a provenance chain."
39
+ ),
40
+ mechanism=(
41
+ "ProvenanceHop records are attached to each context element. "
42
+ "External sources receive EXTERNAL trust level."
43
+ ),
44
+ enforcement_action="DETECT",
45
+ pipeline_stage="provenance",
46
+ ),
47
+ "taint_tracking": Mitigation(
48
+ control_id="ctl_taint",
49
+ control_name="Taint Propagation",
50
+ description=(
51
+ "Context derived from untrusted or external sources is marked TAINTED "
52
+ "and that taint propagates through the reasoning chain."
53
+ ),
54
+ mechanism=(
55
+ "TaintState.TAINTED is set on context from external sources. "
56
+ "Tainted context cannot authorize CRITICAL tool execution."
57
+ ),
58
+ enforcement_action="BLOCK",
59
+ pipeline_stage="taint",
60
+ ),
61
+ "mcp_gateway": Mitigation(
62
+ control_id="ctl_mcp",
63
+ control_name="MCP Gateway Interception",
64
+ description="All MCP server communications are intercepted and inspected.",
65
+ mechanism=(
66
+ "MCPGateway wraps all MCP calls. Responses are analyzed, "
67
+ "taint-marked, and provenance-recorded before entering agent context."
68
+ ),
69
+ enforcement_action="BLOCK",
70
+ pipeline_stage="provenance",
71
+ ),
72
+ "http_gateway": Mitigation(
73
+ control_id="ctl_http",
74
+ control_name="HTTP Gateway Interception",
75
+ description="All external HTTP calls are intercepted and responses are taint-marked.",
76
+ mechanism=(
77
+ "HTTPGateway intercepts outbound requests and inbound responses. "
78
+ "External responses marked EXTERNAL and TAINTED."
79
+ ),
80
+ enforcement_action="DETECT",
81
+ pipeline_stage="provenance",
82
+ ),
83
+ "intent_analysis": Mitigation(
84
+ control_id="ctl_intent",
85
+ control_name="Intent Analysis (AI Secura / Deterministic)",
86
+ description=(
87
+ "AI-powered and deterministic intent analysis classifies whether "
88
+ "a tool request is aligned with the declared task."
89
+ ),
90
+ mechanism=(
91
+ "DeterministicIntentAnalyzer checks keywords, patterns, and taint. "
92
+ "AI Secura provides probabilistic scoring. Results inform policy."
93
+ ),
94
+ enforcement_action="ALERT",
95
+ pipeline_stage="intent",
96
+ ),
97
+ "authority_containment": Mitigation(
98
+ control_id="ctl_authority",
99
+ control_name="Authority Containment",
100
+ description=(
101
+ "Agents cannot claim authority beyond what was explicitly delegated. "
102
+ "The containment invariant: effective ≤ delegated ≤ declared."
103
+ ),
104
+ mechanism=(
105
+ "Effective authority is computed from the delegation chain. "
106
+ "Tool requests requiring authority exceeding effective are blocked."
107
+ ),
108
+ enforcement_action="BLOCK",
109
+ pipeline_stage="authority",
110
+ ),
111
+ "effective_authority_check": Mitigation(
112
+ control_id="ctl_eff_auth",
113
+ control_name="Effective Authority Verification",
114
+ description="Verifies the computed effective authority before any tool execution.",
115
+ mechanism=(
116
+ "DECLARED → DELEGATED → EFFECTIVE chain is computed and compared "
117
+ "against required tool authority."
118
+ ),
119
+ enforcement_action="BLOCK",
120
+ pipeline_stage="authority",
121
+ ),
122
+ "delegation_tracking": Mitigation(
123
+ control_id="ctl_delegation",
124
+ control_name="Delegation Chain Tracking",
125
+ description="Full delegation graph is maintained and verified for every agent action.",
126
+ mechanism="Delegation records are immutable and cryptographically linked.",
127
+ enforcement_action="DETECT",
128
+ pipeline_stage="authority",
129
+ ),
130
+ "policy_evaluation": Mitigation(
131
+ control_id="ctl_policy",
132
+ control_name="Deterministic Policy Evaluation",
133
+ description=(
134
+ "The PolicyEvaluator applies rules deterministically — AI advice "
135
+ "is input to policy, not the policy itself."
136
+ ),
137
+ mechanism=(
138
+ "PolicyEvaluator evaluates RuleCondition sets. "
139
+ "Output: ALLOW | MONITOR | HITL | QUARANTINE | BLOCK | REVOKE."
140
+ ),
141
+ enforcement_action="BLOCK",
142
+ pipeline_stage="policy",
143
+ ),
144
+ "tool_interception": Mitigation(
145
+ control_id="ctl_tool",
146
+ control_name="Tool Interception (@protect decorator)",
147
+ description="Protected tools cannot execute without passing the full ActShield pipeline.",
148
+ mechanism=(
149
+ "@guard.protect() wraps tool functions. All executions pass through "
150
+ "provenance → taint → intent → authority → policy before execution."
151
+ ),
152
+ enforcement_action="BLOCK",
153
+ pipeline_stage="policy",
154
+ ),
155
+ "audit_logging": Mitigation(
156
+ control_id="ctl_audit",
157
+ control_name="Structured Audit Logging",
158
+ description="Every security decision produces a structured, immutable audit record.",
159
+ mechanism=(
160
+ "SecurityEvent records are written with: who, what, when, why, "
161
+ "context, authority, tool, resource, decision, outcome."
162
+ ),
163
+ enforcement_action="DETECT",
164
+ pipeline_stage="evidence",
165
+ ),
166
+ "source_attribution": Mitigation(
167
+ control_id="ctl_attribution",
168
+ control_name="Source Attribution",
169
+ description="Context elements are attributed to their originating source at retrieval time.",
170
+ mechanism="ProvenanceHop records source URL, trust level, and retrieval timestamp.",
171
+ enforcement_action="DETECT",
172
+ pipeline_stage="provenance",
173
+ ),
174
+ "sensitivity_classification": Mitigation(
175
+ control_id="ctl_sensitivity",
176
+ control_name="Tool Sensitivity Classification",
177
+ description="Tools are classified by sensitivity level (LOW → CRITICAL).",
178
+ mechanism=(
179
+ "SensitivityLevel is declared on ToolDefinition. "
180
+ "CRITICAL tools require authority + clean taint + explicit policy."
181
+ ),
182
+ enforcement_action="BLOCK",
183
+ pipeline_stage="policy",
184
+ ),
185
+ }
186
+
187
+
188
+ def get_control(control_id: str) -> Mitigation | None:
189
+ return ACTSHIELD_CONTROLS.get(control_id)
190
+
191
+
192
+ def get_all_controls() -> list[Mitigation]:
193
+ return list(ACTSHIELD_CONTROLS.values())
194
+
195
+
196
+ def build_control_coverage_map(threat_control_ids: list[str]) -> dict[str, bool]:
197
+ """Return which ActShield control domains are covered."""
198
+ domains = {
199
+ "Identity": any(c in threat_control_ids for c in ["provenance_tracking", "delegation_tracking"]),
200
+ "Authority": any(c in threat_control_ids for c in ["authority_containment", "effective_authority_check", "delegation_tracking"]),
201
+ "Context": any(c in threat_control_ids for c in ["taint_tracking", "provenance_tracking", "source_attribution"]),
202
+ "Tool Security": any(c in threat_control_ids for c in ["tool_interception", "sensitivity_classification"]),
203
+ "MCP": any(c in threat_control_ids for c in ["mcp_gateway"]),
204
+ "Data Access": any(c in threat_control_ids for c in ["authority_containment", "policy_evaluation"]),
205
+ "Delegation": any(c in threat_control_ids for c in ["delegation_tracking", "authority_containment"]),
206
+ "Enforcement": any(c in threat_control_ids for c in ["policy_evaluation"]),
207
+ "Evidence": any(c in threat_control_ids for c in ["audit_logging"]),
208
+ }
209
+ return domains
@@ -0,0 +1,80 @@
1
+ """Threat modeling core models — ThreatModel, AnalysisResult, enumerations."""
2
+ from __future__ import annotations
3
+
4
+ import uuid
5
+ from datetime import datetime, timezone
6
+ from enum import Enum
7
+ from typing import Any
8
+
9
+ from pydantic import BaseModel, Field
10
+
11
+
12
+ class ThreatSeverity(str, Enum):
13
+ """CVSS-inspired severity classification."""
14
+ CRITICAL = "CRITICAL"
15
+ HIGH = "HIGH"
16
+ MEDIUM = "MEDIUM"
17
+ LOW = "LOW"
18
+ INFORMATIONAL = "INFORMATIONAL"
19
+
20
+
21
+ class ThreatCategory(str, Enum):
22
+ """Top-level threat category aligned to STRIDE + MITRE ATLAS concepts."""
23
+ # STRIDE
24
+ SPOOFING = "SPOOFING" # Identity / agent impersonation
25
+ TAMPERING = "TAMPERING" # Context or data tampering
26
+ REPUDIATION = "REPUDIATION" # Lack of audit trail
27
+ INFORMATION_DISCLOSURE = "INFO_DISCLOSURE" # Data exfiltration
28
+ DENIAL_OF_SERVICE = "DENIAL_OF_SERVICE"
29
+ ELEVATION_OF_PRIVILEGE = "PRIVILEGE_ESCALATION"
30
+ # MITRE ATLAS / Agentic AI specific
31
+ PROMPT_INJECTION = "PROMPT_INJECTION"
32
+ INDIRECT_PROMPT_INJECTION = "INDIRECT_PROMPT_INJECTION"
33
+ TOOL_POISONING = "TOOL_POISONING"
34
+ CONTEXT_POISONING = "CONTEXT_POISONING"
35
+ DELEGATION_ABUSE = "DELEGATION_ABUSE"
36
+ CREDENTIAL_THEFT = "CREDENTIAL_THEFT"
37
+ MCP_COMPROMISE = "MCP_COMPROMISE"
38
+ SUPPLY_CHAIN = "SUPPLY_CHAIN"
39
+ RAG_POISONING = "RAG_POISONING"
40
+
41
+
42
+ class ControlEffectiveness(str, Enum):
43
+ """How effectively the mapped ActShield control mitigates the threat."""
44
+ PREVENTS = "PREVENTS" # Control fully prevents exploitation
45
+ DETECTS = "DETECTS" # Control detects but may not prevent
46
+ REDUCES = "REDUCES" # Control reduces likelihood / impact
47
+ MONITORS = "MONITORS" # Control provides observability only
48
+ NONE = "NONE" # No current control coverage
49
+
50
+
51
+ class ThreatModel(BaseModel):
52
+ """Container for the full threat model of a deployed agent system."""
53
+ model_id: str = Field(default_factory=lambda: f"tm_{uuid.uuid4().hex[:8]}")
54
+ system_name: str = "ActShield-Monitored System"
55
+ created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
56
+ version: str = "1.0.0"
57
+
58
+ # Populated by the registry/analyzer
59
+ asset_count: int = 0
60
+ actor_count: int = 0
61
+ boundary_count: int = 0
62
+ threat_count: int = 0
63
+ scenario_count: int = 0
64
+
65
+ def to_dict(self) -> dict[str, Any]:
66
+ return self.model_dump(mode="json")
67
+
68
+
69
+ class AnalysisResult(BaseModel):
70
+ """Complete output of a ThreatAnalyzer.analyze() run."""
71
+ model: ThreatModel
72
+ threats_by_severity: dict[str, list[dict[str, Any]]] = Field(default_factory=dict)
73
+ control_coverage: dict[str, bool] = Field(default_factory=dict)
74
+ residual_risks: list[dict[str, Any]] = Field(default_factory=list)
75
+ attack_paths: list[dict[str, Any]] = Field(default_factory=list)
76
+ analysis_timestamp: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
77
+ overall_risk_score: float = 0.0 # 0-10 scale
78
+
79
+ def to_dict(self) -> dict[str, Any]:
80
+ return self.model_dump(mode="json")
@@ -0,0 +1,341 @@
1
+ """Threat model Rich CLI renderer — professional security report output."""
2
+ from __future__ import annotations
3
+
4
+ from rich.console import Console
5
+ from rich.panel import Panel
6
+ from rich.table import Table
7
+ from rich.tree import Tree
8
+ from rich.columns import Columns
9
+ from rich.text import Text
10
+ from rich import box
11
+
12
+ from actshield.cli.theme import ACTSHIELD_THEME
13
+ from actshield.threatmodel.models import (
14
+ ThreatModel,
15
+ AnalysisResult,
16
+ ThreatSeverity,
17
+ ControlEffectiveness,
18
+ )
19
+ from actshield.threatmodel.threats import Threat
20
+
21
+ console = Console(theme=ACTSHIELD_THEME)
22
+
23
+ # Severity → Rich style
24
+ _SEV_STYLE: dict[str, str] = {
25
+ ThreatSeverity.CRITICAL.value: "ag.critical",
26
+ ThreatSeverity.HIGH.value: "ag.attack",
27
+ ThreatSeverity.MEDIUM.value: "ag.warning",
28
+ ThreatSeverity.LOW.value: "ag.monitor",
29
+ ThreatSeverity.INFORMATIONAL.value: "ag.dim",
30
+ }
31
+
32
+ _EFF_LABEL: dict[ControlEffectiveness, str] = {
33
+ ControlEffectiveness.PREVENTS: "✓ PREVENTS",
34
+ ControlEffectiveness.DETECTS: "◉ DETECTS",
35
+ ControlEffectiveness.REDUCES: "◈ REDUCES",
36
+ ControlEffectiveness.MONITORS: "○ MONITORS",
37
+ ControlEffectiveness.NONE: "✗ NONE",
38
+ }
39
+
40
+ _EFF_STYLE: dict[ControlEffectiveness, str] = {
41
+ ControlEffectiveness.PREVENTS: "ag.healthy",
42
+ ControlEffectiveness.DETECTS: "ag.monitor",
43
+ ControlEffectiveness.REDUCES: "ag.warning",
44
+ ControlEffectiveness.MONITORS: "ag.hitl",
45
+ ControlEffectiveness.NONE: "ag.violation",
46
+ }
47
+
48
+
49
+ class ThreatReportRenderer:
50
+ """Renders threat model data as Rich terminal output."""
51
+
52
+ # ── Summary ──────────────────────────────────────────────────────────────
53
+
54
+ def render_model_summary(self, model: ThreatModel) -> None:
55
+ console.print()
56
+ console.print(Panel(
57
+ f"[ag.header]System[/ag.header] {model.system_name}\n"
58
+ f"[ag.header]Model ID[/ag.header] [ag.id]{model.model_id}[/ag.id]\n"
59
+ f"[ag.header]Version[/ag.header] {model.version}\n\n"
60
+ f"[ag.label]Assets [/ag.label][bold]{model.asset_count}[/bold]\n"
61
+ f"[ag.label]Threat Actors [/ag.label][bold]{model.actor_count}[/bold]\n"
62
+ f"[ag.label]Trust Boundaries[/ag.label][bold]{model.boundary_count}[/bold]\n"
63
+ f"[ag.label]Threats [/ag.label][bold]{model.threat_count}[/bold]\n"
64
+ f"[ag.label]Attack Scenarios[/ag.label][bold]{model.scenario_count}[/bold]",
65
+ title="[ag.brand]ACTSHIELD THREAT MODEL[/ag.brand]",
66
+ border_style="rgb(59,130,246)",
67
+ expand=False,
68
+ ))
69
+ console.print()
70
+
71
+ # ── Threat list ──────────────────────────────────────────────────────────
72
+
73
+ def render_threat_list(self, threats: list[Threat]) -> None:
74
+ table = Table(
75
+ box=box.SIMPLE_HEAD,
76
+ show_header=True,
77
+ header_style="ag.header",
78
+ pad_edge=False,
79
+ expand=False,
80
+ )
81
+ table.add_column("ID", style="ag.id", no_wrap=True)
82
+ table.add_column("Name", min_width=32)
83
+ table.add_column("Category", style="ag.label")
84
+ table.add_column("Severity", no_wrap=True)
85
+ table.add_column("Control Effectiveness", no_wrap=True)
86
+ table.add_column("Entry Point(s)", style="ag.dim")
87
+
88
+ for t in sorted(
89
+ threats,
90
+ key=lambda x: list(ThreatSeverity).index(x.severity)
91
+ ):
92
+ sev_style = _SEV_STYLE.get(t.severity.value, "")
93
+ eff_label = _EFF_LABEL.get(t.control_effectiveness, "?")
94
+ eff_style = _EFF_STYLE.get(t.control_effectiveness, "")
95
+ table.add_row(
96
+ t.threat_id,
97
+ t.name,
98
+ t.category.value.replace("_", " "),
99
+ Text(t.severity.value, style=sev_style),
100
+ Text(eff_label, style=eff_style),
101
+ ", ".join(t.entry_points[:2]),
102
+ )
103
+
104
+ console.print()
105
+ console.print(table)
106
+ console.print(f" [ag.dim]{len(threats)} threat(s)[/ag.dim]")
107
+ console.print()
108
+
109
+ # ── Threat detail ────────────────────────────────────────────────────────
110
+
111
+ def render_threat_detail(self, threat: Threat) -> None:
112
+ sev_style = _SEV_STYLE.get(threat.severity.value, "")
113
+ eff_label = _EFF_LABEL.get(threat.control_effectiveness, "?")
114
+ eff_style = _EFF_STYLE.get(threat.control_effectiveness, "")
115
+
116
+ # Attack path tree
117
+ path_tree = Tree("[ag.label]Attack Path[/ag.label]")
118
+ for step in threat.attack_path:
119
+ path_tree.add(f"[ag.dim]{step}[/ag.dim]")
120
+
121
+ # Controls table
122
+ controls_text = "\n".join(
123
+ f" [ag.healthy]·[/ag.healthy] {c.replace('_', ' ').title()}"
124
+ for c in threat.actshield_controls
125
+ ) or " [ag.violation]No controls mapped[/ag.violation]"
126
+
127
+ content = (
128
+ f"[ag.label]ID [/ag.label] {threat.threat_id}\n"
129
+ f"[ag.label]Category [/ag.label] {threat.category.value.replace('_', ' ')}\n"
130
+ f"[ag.label]Severity [/ag.label] [{sev_style}]{threat.severity.value}[/{sev_style}]\n"
131
+ f"[ag.label]STRIDE [/ag.label] {threat.stride_category or 'N/A'}\n"
132
+ f"[ag.label]MITRE Atlas[/ag.label] {threat.mitre_atlas_id or 'N/A'}\n\n"
133
+ f"[ag.header]Description[/ag.header]\n {threat.description}\n\n"
134
+ f"[ag.header]ActShield Controls[/ag.header]\n{controls_text}\n\n"
135
+ f"[ag.header]Control Effectiveness[/ag.header]\n [{eff_style}]{eff_label}[/{eff_style}]\n\n"
136
+ f"[ag.header]Residual Risk[/ag.header]\n {threat.residual_risk}\n\n"
137
+ f"[ag.header]Evidence Generated[/ag.header]\n "
138
+ + ", ".join(threat.evidence_generated)
139
+ )
140
+
141
+ console.print()
142
+ console.print(Panel(
143
+ content,
144
+ title=f"[ag.brand]{threat.name}[/ag.brand]",
145
+ border_style="rgb(59,130,246)",
146
+ expand=False,
147
+ ))
148
+ console.print()
149
+ console.print(path_tree)
150
+ console.print()
151
+
152
+ # ── Full analysis report ─────────────────────────────────────────────────
153
+
154
+ def render_analysis_report(self, result: AnalysisResult) -> None:
155
+ model = result.model
156
+ sev_counts = {s.value: 0 for s in ThreatSeverity}
157
+ for sev, threats in result.threats_by_severity.items():
158
+ sev_counts[sev] = len(threats)
159
+
160
+ console.print()
161
+ # Header panel
162
+ console.print(Panel(
163
+ f"[ag.label]System [/ag.label]{model.system_name}\n"
164
+ f"[ag.label]Analysis Time [/ag.label]{result.analysis_timestamp.strftime('%Y-%m-%d %H:%M:%S UTC')}\n"
165
+ f"[ag.label]Overall Risk [/ag.label][bold]{result.overall_risk_score:.1f}[/bold] / 10.0\n",
166
+ title="[ag.brand]ACTSHIELD THREAT ANALYSIS[/ag.brand]",
167
+ border_style="rgb(59,130,246)",
168
+ expand=False,
169
+ ))
170
+
171
+ # Counts row
172
+ console.print()
173
+ console.print(" [ag.header]Threat Inventory[/ag.header]")
174
+ counts_table = Table(box=box.SIMPLE, show_header=False, pad_edge=False)
175
+ counts_table.add_column("", style="ag.label", no_wrap=True)
176
+ counts_table.add_column("", style="bold", no_wrap=True)
177
+ counts_table.add_row("Assets", str(model.asset_count))
178
+ counts_table.add_row("Trust Boundaries", str(model.boundary_count))
179
+ counts_table.add_row("Threat Actors", str(model.actor_count))
180
+ counts_table.add_row("Identified Threats", str(model.threat_count))
181
+ counts_table.add_row("Attack Scenarios", str(model.scenario_count))
182
+ console.print(counts_table)
183
+
184
+ # Severity breakdown
185
+ console.print()
186
+ console.print(" [ag.header]Severity Breakdown[/ag.header]")
187
+ sev_table = Table(box=box.SIMPLE, show_header=False, pad_edge=False)
188
+ sev_table.add_column("", no_wrap=True)
189
+ sev_table.add_column("", no_wrap=True)
190
+ for sev, style in _SEV_STYLE.items():
191
+ count = sev_counts.get(sev, 0)
192
+ bar = "█" * count
193
+ sev_table.add_row(
194
+ Text(f" {sev:<14}", style=style),
195
+ Text(f"{count:>3} {bar}", style=style),
196
+ )
197
+ console.print(sev_table)
198
+
199
+ # Coverage map
200
+ console.print()
201
+ console.print(" [ag.header]Control Domain Coverage[/ag.header]")
202
+ cov_table = Table(box=box.SIMPLE, show_header=False, pad_edge=False)
203
+ cov_table.add_column("", style="ag.label", no_wrap=True)
204
+ cov_table.add_column("", no_wrap=True)
205
+ for domain, covered in result.control_coverage.items():
206
+ mark = Text(" ✓", style="ag.healthy") if covered else Text(" ✗", style="ag.violation")
207
+ cov_table.add_row(f" {domain:<24}", mark)
208
+ console.print(cov_table)
209
+
210
+ # Attack paths
211
+ if result.attack_paths:
212
+ console.print()
213
+ console.print(" [ag.header]Attack Scenarios[/ag.header]")
214
+ for path in result.attack_paths:
215
+ sev_style = _SEV_STYLE.get(path["severity"], "")
216
+ enforcement = path["enforcement"]
217
+ enf_style = "ag.block" if enforcement == "BLOCK" else "ag.monitor"
218
+ console.print(
219
+ f" [{sev_style}]{path['severity']:<10}[/{sev_style}]"
220
+ f" {path['name']:<52}"
221
+ f" [{enf_style}]{enforcement}[/{enf_style}]"
222
+ )
223
+
224
+ # Residual risks
225
+ if result.residual_risks:
226
+ console.print()
227
+ console.print(" [ag.header]Residual Risks[/ag.header]")
228
+ for risk in result.residual_risks:
229
+ sev_style = _SEV_STYLE.get(risk["severity"], "")
230
+ console.print(
231
+ f" [{sev_style}]{risk['severity']:<10}[/{sev_style}]"
232
+ f" {risk['name']:<40}"
233
+ f" score={risk['residual_score']:.1f}"
234
+ )
235
+
236
+ console.print()
237
+
238
+ # ── Attack graph (Rich tree) ──────────────────────────────────────────────
239
+
240
+ def render_attack_graph(self, result: AnalysisResult) -> None:
241
+ from actshield.threatmodel.scenarios import get_builtin_scenarios
242
+ scenarios = get_builtin_scenarios()
243
+
244
+ console.print()
245
+ root = Tree("[ag.brand]ACTSHIELD ATTACK GRAPH[/ag.brand]")
246
+ for scenario in scenarios:
247
+ sev_style = _SEV_STYLE.get(scenario.severity.value, "")
248
+ scenario_node = root.add(
249
+ f"[{sev_style}]{scenario.severity.value}[/{sev_style}] {scenario.name}"
250
+ )
251
+ for step in scenario.steps:
252
+ outcome_style = (
253
+ "ag.healthy" if step.outcome == "blocked"
254
+ else "ag.warning" if step.outcome == "detected"
255
+ else "ag.dim"
256
+ )
257
+ label = f"[{outcome_style}]{step.outcome.upper():>8}[/{outcome_style}] Step {step.step_num}: {step.description[:60]}"
258
+ scenario_node.add(label)
259
+ console.print(root)
260
+ console.print()
261
+
262
+ # ── Markdown export ───────────────────────────────────────────────────────
263
+
264
+ def to_markdown(self, result: AnalysisResult) -> str:
265
+ model = result.model
266
+ lines = [
267
+ f"# ActShield Threat Model Report",
268
+ f"",
269
+ f"**System:** {model.system_name} ",
270
+ f"**Date:** {result.analysis_timestamp.strftime('%Y-%m-%d %H:%M UTC')} ",
271
+ f"**Overall Risk Score:** {result.overall_risk_score:.1f} / 10.0 ",
272
+ f"**Model ID:** `{model.model_id}`",
273
+ f"",
274
+ f"---",
275
+ f"",
276
+ f"## Inventory",
277
+ f"",
278
+ f"| Category | Count |",
279
+ f"|----------|-------|",
280
+ f"| Assets | {model.asset_count} |",
281
+ f"| Trust Boundaries | {model.boundary_count} |",
282
+ f"| Threat Actors | {model.actor_count} |",
283
+ f"| Identified Threats | {model.threat_count} |",
284
+ f"| Attack Scenarios | {model.scenario_count} |",
285
+ f"",
286
+ f"## Severity Breakdown",
287
+ f"",
288
+ f"| Severity | Count |",
289
+ f"|----------|-------|",
290
+ ]
291
+ for sev in ThreatSeverity:
292
+ count = len(result.threats_by_severity.get(sev.value, []))
293
+ lines.append(f"| {sev.value} | {count} |")
294
+
295
+ lines += [
296
+ f"",
297
+ f"## Control Domain Coverage",
298
+ f"",
299
+ f"| Domain | Covered |",
300
+ f"|--------|---------|",
301
+ ]
302
+ for domain, covered in result.control_coverage.items():
303
+ mark = "✓" if covered else "✗"
304
+ lines.append(f"| {domain} | {mark} |")
305
+
306
+ lines += [
307
+ f"",
308
+ f"## Threats",
309
+ f"",
310
+ ]
311
+ for sev in ThreatSeverity:
312
+ threats = result.threats_by_severity.get(sev.value, [])
313
+ if threats:
314
+ lines.append(f"### {sev.value}")
315
+ lines.append(f"")
316
+ for t in threats:
317
+ lines.append(f"#### {t['name']}")
318
+ lines.append(f"")
319
+ lines.append(f"- **ID:** `{t['threat_id']}`")
320
+ lines.append(f"- **Category:** {t['category']}")
321
+ lines.append(f"- **Controls:** {', '.join(t.get('actshield_controls', []))}")
322
+ lines.append(f"- **Effectiveness:** {t.get('control_effectiveness', 'N/A')}")
323
+ lines.append(f"- **Residual Risk:** {t.get('residual_risk', 'N/A')}")
324
+ lines.append(f"")
325
+
326
+ if result.residual_risks:
327
+ lines += [f"## Residual Risks", f""]
328
+ for risk in result.residual_risks:
329
+ lines.append(
330
+ f"- **{risk['name']}** ({risk['severity']}) — "
331
+ f"residual score: {risk['residual_score']:.1f} — {risk['residual_risk']}"
332
+ )
333
+ lines.append(f"")
334
+
335
+ lines += [
336
+ f"---",
337
+ f"",
338
+ f"*Generated by ActShield v0.9.0*",
339
+ f"",
340
+ ]
341
+ return "\n".join(lines)