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,305 @@
1
+ """Causal trace manager, event repository, and causal graph reconstructor."""
2
+
3
+ import json
4
+ from datetime import datetime
5
+ from typing import TYPE_CHECKING, Any, Dict, List, Optional
6
+ from pydantic import BaseModel, Field
7
+
8
+ from actshield.tracing.events import EventType, SecurityEvent
9
+
10
+ if TYPE_CHECKING:
11
+ from actshield.client import ActShield
12
+
13
+
14
+ class CausalNode(BaseModel):
15
+ """A discrete entity in the multi-agent causal graph."""
16
+ node_id: str
17
+ node_type: str # USER, TASK, AGENT, DELEGATION, CONTEXT, TOOL, RESOURCE, DECISION, EVENT
18
+ label: str
19
+ details: Dict[str, Any] = Field(default_factory=dict)
20
+ timestamp: Optional[str] = None
21
+
22
+
23
+ class CausalEdge(BaseModel):
24
+ """A directed causal dependency or data-flow relationship."""
25
+ source_id: str
26
+ target_id: str
27
+ relation: str # INITIATED, DELEGATED_TO, INGESTED_OR_PROPAGATED, INVOKED, TARGETS, GOVERNED_BY
28
+ metadata: Dict[str, Any] = Field(default_factory=dict)
29
+
30
+
31
+ class CausalGraph(BaseModel):
32
+ """Reconstructed end-to-end causal chain and provenance graph for a trace."""
33
+ trace_id: str
34
+ nodes: List[CausalNode] = Field(default_factory=list)
35
+ edges: List[CausalEdge] = Field(default_factory=list)
36
+
37
+ def to_json(self, indent: int = 2) -> str:
38
+ """Export graph as machine-readable JSON."""
39
+ return self.model_dump_json(indent=indent)
40
+
41
+ def get_influencing_contexts(self, tool_name_or_id: Optional[str] = None) -> List[Dict[str, Any]]:
42
+ """Query and return all context nodes that causally influenced tool requests in this trace."""
43
+ context_nodes = [n for n in self.nodes if n.node_type == "CONTEXT"]
44
+ results: List[Dict[str, Any]] = []
45
+ for cn in context_nodes:
46
+ cid = cn.details.get("context_id") or (cn.node_id[4:] if cn.node_id.startswith("ctx_") else cn.node_id)
47
+ results.append({
48
+ "context_id": cid,
49
+ "label": cn.label,
50
+ "details": cn.details,
51
+ "timestamp": cn.timestamp,
52
+ })
53
+ return results
54
+
55
+ def get_propagation_chain(self) -> List[str]:
56
+ """Extract chronological agent propagation chain from graph nodes."""
57
+ agent_nodes = [n for n in self.nodes if n.node_type == "AGENT"]
58
+ seen = set()
59
+ chain = []
60
+ for n in agent_nodes:
61
+ # Extract clean agent name from label e.g. "Agent: PlannerAgent"
62
+ name = n.label.split("(")[0].replace("Agent:", "").strip()
63
+ if name not in seen:
64
+ seen.add(name)
65
+ chain.append(name)
66
+ return chain
67
+
68
+ def render_tree(self) -> str:
69
+ """Render a formatted human-readable causal execution tree using ASCII characters for universal terminal support."""
70
+ lines: List[str] = [
71
+ f"=== ActShield CAUSAL TRACE: {self.trace_id} ===",
72
+ ""
73
+ ]
74
+
75
+ node_map = {n.node_id: n for n in self.nodes}
76
+ # Find root nodes (nodes with no incoming edges)
77
+ targets = {e.target_id for e in self.edges}
78
+ roots = [n for n in self.nodes if n.node_id not in targets]
79
+
80
+ if not roots and self.nodes:
81
+ roots = [self.nodes[0]]
82
+
83
+ # Build adjacency list: source -> list of (target_id, relation)
84
+ adj: Dict[str, List[tuple[str, str]]] = {}
85
+ for e in self.edges:
86
+ adj.setdefault(e.source_id, []).append((e.target_id, e.relation))
87
+
88
+ visited = set()
89
+
90
+ def _dfs(current_id: str, prefix: str = "", is_last: bool = True) -> None:
91
+ if current_id in visited:
92
+ return
93
+ visited.add(current_id)
94
+ node = node_map.get(current_id)
95
+ if not node:
96
+ return
97
+
98
+ connector = "\\-- " if is_last else "+-- "
99
+ type_tag = f"[{node.node_type}]"
100
+ lines.append(f"{prefix}{connector}{type_tag} {node.label}")
101
+
102
+ child_prefix = prefix + (" " if is_last else "| ")
103
+ children = adj.get(current_id, [])
104
+ for i, (child_id, rel) in enumerate(children):
105
+ child_is_last = (i == len(children) - 1)
106
+ _dfs(child_id, child_prefix, child_is_last)
107
+
108
+ for i, root in enumerate(roots):
109
+ _dfs(root.node_id, "", is_last=(i == len(roots) - 1))
110
+
111
+ # Add unvisited nodes if any disconnected events exist
112
+ unvisited = [n for n in self.nodes if n.node_id not in visited]
113
+ if unvisited:
114
+ lines.append("")
115
+ lines.append("--- Additional Correlated Events ---")
116
+ for node in unvisited:
117
+ lines.append(f" * [{node.node_type}] {node.label}")
118
+
119
+ return "\n".join(lines)
120
+
121
+
122
+ class TraceManager:
123
+ """Thread-safe and async-safe in-memory security event repository."""
124
+
125
+ def __init__(self) -> None:
126
+ self._events: List[SecurityEvent] = []
127
+
128
+ def record_event(self, event: SecurityEvent) -> None:
129
+ """Store an emitted security event."""
130
+ self._events.append(event)
131
+
132
+ def get_events(self, trace_id: Optional[str] = None) -> List[SecurityEvent]:
133
+ """Query events matching optional trace ID."""
134
+ if trace_id is None:
135
+ return list(self._events)
136
+ return [e for e in self._events if e.trace_id == trace_id]
137
+
138
+ def get_propagation_chain_for_context(self, context_id: str) -> List[str]:
139
+ """Return the sequence of agent IDs that propagated a specific context."""
140
+ events = [e for e in self._events if e.context_id == context_id or e.payload.get("parent_context_id") == context_id]
141
+ agents = []
142
+ for e in events:
143
+ if e.agent_id and e.agent_id not in agents:
144
+ agents.append(e.agent_id)
145
+ return agents
146
+
147
+ def reconstruct_causal_graph(self, trace_id: str, guard: Optional["ActShield"] = None) -> CausalGraph:
148
+ """Reconstruct the complete causal chain from stored events and registries."""
149
+ events = self.get_events(trace_id)
150
+ nodes: List[CausalNode] = []
151
+ edges: List[CausalEdge] = []
152
+ node_ids = set()
153
+
154
+ def add_node(n: CausalNode) -> None:
155
+ if n.node_id not in node_ids:
156
+ node_ids.add(n.node_id)
157
+ nodes.append(n)
158
+
159
+ def add_edge(src: str, dst: str, relation: str) -> None:
160
+ edges.append(CausalEdge(source_id=src, target_id=dst, relation=relation))
161
+
162
+ # Process Task Started
163
+ user_node_id = None
164
+ task_node_id = None
165
+ last_entity_id = None
166
+
167
+ for evt in events:
168
+ evt_time_str = evt.timestamp.isoformat() if hasattr(evt.timestamp, "isoformat") else str(evt.timestamp)
169
+
170
+ if evt.event_type == EventType.TASK_STARTED:
171
+ user_name = evt.payload.get("initiating_user") or "User/Client"
172
+ user_node_id = f"user_{evt.trace_id}"
173
+ add_node(CausalNode(
174
+ node_id=user_node_id,
175
+ node_type="USER",
176
+ label=f"User: {user_name}",
177
+ details={"initiating_user": user_name},
178
+ timestamp=evt_time_str,
179
+ ))
180
+
181
+ task_id = evt.task_id or f"task_{evt.trace_id}"
182
+ task_node_id = task_id
183
+ add_node(CausalNode(
184
+ node_id=task_id,
185
+ node_type="TASK",
186
+ label=f"Task: {evt.payload.get('intent', 'Task')}",
187
+ details=evt.payload,
188
+ timestamp=evt_time_str,
189
+ ))
190
+ add_edge(user_node_id, task_id, "INITIATED")
191
+ last_entity_id = task_id
192
+
193
+ elif evt.event_type == EventType.AGENT_DELEGATED:
194
+ delegator_id = evt.payload.get("delegator_id")
195
+ delegator_name = evt.payload.get("delegator_name", delegator_id)
196
+ delegate_id = evt.payload.get("delegate_id")
197
+ delegate_name = evt.payload.get("delegate_name", delegate_id)
198
+ caps = evt.payload.get("granted_capabilities", [])
199
+
200
+ delegator_node_id = f"agent_{delegator_id}"
201
+ delegate_node_id = f"agent_{delegate_id}"
202
+
203
+ add_node(CausalNode(
204
+ node_id=delegator_node_id,
205
+ node_type="AGENT",
206
+ label=f"Agent: {delegator_name}",
207
+ details={"agent_id": delegator_id},
208
+ timestamp=evt_time_str,
209
+ ))
210
+
211
+ add_node(CausalNode(
212
+ node_id=delegate_node_id,
213
+ node_type="AGENT",
214
+ label=f"Agent: {delegate_name} (Granted: {caps})",
215
+ details={"agent_id": delegate_id, "granted_capabilities": caps},
216
+ timestamp=evt_time_str,
217
+ ))
218
+
219
+ # Link from task or delegator
220
+ if last_entity_id and last_entity_id == task_node_id:
221
+ add_edge(task_node_id, delegator_node_id, "ENGAGED")
222
+
223
+ add_edge(delegator_node_id, delegate_node_id, "DELEGATED_TO")
224
+ last_entity_id = delegate_node_id
225
+
226
+ elif evt.event_type in (EventType.CONTEXT_RECEIVED, EventType.CONTEXT_PROPAGATED, EventType.CONTEXT_SANITIZED):
227
+ ctx_id = evt.context_id or f"ctx_{len(nodes)}"
228
+ taint = evt.payload.get("taint_state", evt.payload.get("new_taint", "CLEAN"))
229
+ src = evt.payload.get("source", "external")
230
+ action = evt.payload.get("action", evt.event_type.value)
231
+ ctx_node_id = f"ctx_{ctx_id}"
232
+ node_details = dict(evt.payload)
233
+ node_details["context_id"] = ctx_id
234
+
235
+ add_node(CausalNode(
236
+ node_id=ctx_node_id,
237
+ node_type="CONTEXT",
238
+ label=f"Context: {src} [Taint: {taint}]",
239
+ details=node_details,
240
+ timestamp=evt_time_str,
241
+ ))
242
+
243
+ acting_agent_id = f"agent_{evt.agent_id}" if evt.agent_id else last_entity_id
244
+ if acting_agent_id:
245
+ add_edge(acting_agent_id, ctx_node_id, "INGESTED_OR_PROPAGATED")
246
+ last_entity_id = ctx_node_id
247
+
248
+ elif evt.event_type == EventType.TOOL_REQUESTED:
249
+ tool_name = evt.payload.get("tool_name", "Tool")
250
+ tool_id = evt.payload.get("tool_id", "tool")
251
+ sens = evt.payload.get("sensitivity", "MEDIUM")
252
+ tool_node_id = f"tool_req_{tool_id}_{evt.event_id}"
253
+
254
+ add_node(CausalNode(
255
+ node_id=tool_node_id,
256
+ node_type="TOOL",
257
+ label=f"Tool: {tool_name} (Sensitivity: {sens})",
258
+ details=evt.payload,
259
+ timestamp=evt_time_str,
260
+ ))
261
+
262
+ agent_id = f"agent_{evt.agent_id}" if evt.agent_id else last_entity_id
263
+ if agent_id:
264
+ add_edge(agent_id, tool_node_id, "INVOKED")
265
+ last_entity_id = tool_node_id
266
+
267
+ elif evt.event_type == EventType.RESOURCE_ACCESS_REQUESTED:
268
+ rsc_name = evt.payload.get("resource_name", "Resource")
269
+ rsc_id = evt.payload.get("resource_id", "rsc")
270
+ sens = evt.payload.get("sensitivity", "MEDIUM")
271
+ rsc_node_id = f"rsc_{rsc_id}_{evt.event_id}"
272
+
273
+ add_node(CausalNode(
274
+ node_id=rsc_node_id,
275
+ node_type="RESOURCE",
276
+ label=f"Resource: {rsc_name} [{sens}]",
277
+ details=evt.payload,
278
+ timestamp=evt_time_str,
279
+ ))
280
+
281
+ if last_entity_id:
282
+ add_edge(last_entity_id, rsc_node_id, "TARGETS")
283
+ last_entity_id = rsc_node_id
284
+
285
+ elif evt.event_type == EventType.SECURITY_DECISION:
286
+ action = evt.payload.get("action", "DECISION")
287
+ reason = evt.payload.get("reason_code", "")
288
+ score = evt.payload.get("risk_score", 0.0)
289
+ dec_node_id = f"dec_{evt.payload.get('decision_id', evt.event_id)}"
290
+
291
+ add_node(CausalNode(
292
+ node_id=dec_node_id,
293
+ node_type="DECISION",
294
+ label=f"Decision: {action} (Reason: {reason}, Risk: {score})",
295
+ details=evt.payload,
296
+ timestamp=evt_time_str,
297
+ ))
298
+
299
+ if last_entity_id:
300
+ add_edge(last_entity_id, dec_node_id, "GOVERNED_BY")
301
+ last_entity_id = dec_node_id
302
+
303
+ return CausalGraph(trace_id=trace_id, nodes=nodes, edges=edges)
304
+
305
+
actshield/watch.py ADDED
@@ -0,0 +1,129 @@
1
+ """
2
+ ActShield Live Event Watcher
3
+ =============================
4
+ Renders a live terminal feed of security events, posture changes, and incidents using Rich.
5
+ """
6
+ from __future__ import annotations
7
+
8
+ import sys
9
+ import time
10
+ from datetime import datetime, timezone
11
+ from typing import Any, Dict, List, Optional
12
+
13
+ if sys.platform == "win32":
14
+ try:
15
+ sys.stdout.reconfigure(encoding="utf-8")
16
+ sys.stderr.reconfigure(encoding="utf-8")
17
+ except Exception:
18
+ pass
19
+
20
+ from rich.console import Console
21
+ from rich.layout import Layout
22
+ from rich.live import Live
23
+ from rich.panel import Panel
24
+ from rich.table import Table
25
+ from rich.text import Text
26
+
27
+
28
+ def _get_severity_style(severity: str) -> str:
29
+ s = str(severity).upper()
30
+ if s in ("CRITICAL", "BLOCK", "DENY"):
31
+ return "bold red"
32
+ elif s in ("HIGH", "QUARANTINE", "RESTRICT"):
33
+ return "bold yellow"
34
+ elif s in ("MEDIUM", "SUSPICIOUS", "WARN"):
35
+ return "yellow"
36
+ elif s in ("LOW", "INFO", "ALLOW"):
37
+ return "green"
38
+ return "dim"
39
+
40
+
41
+ def generate_watch_table(events: List[Dict[str, Any]]) -> Table:
42
+ """Generate a rich table for live events."""
43
+ table = Table(
44
+ title="[bold cyan]⚡ LIVE CAUSAL SECURITY EVENT STREAM[/bold cyan]",
45
+ title_justify="left",
46
+ border_style="dim white",
47
+ header_style="bold bright_white on grey23",
48
+ expand=True,
49
+ )
50
+ table.add_column("TIME", style="dim", width=12)
51
+ table.add_column("EVENT ID", style="bold cyan", width=14)
52
+ table.add_column("AGENT", style="magenta", width=16)
53
+ table.add_column("ACTION / TOOL", style="white", width=22)
54
+ table.add_column("DECISION", width=12)
55
+ table.add_column("SEVERITY", width=12)
56
+ table.add_column("DETAILS", style="dim", no_wrap=False)
57
+
58
+ if not events:
59
+ table.add_row(
60
+ datetime.now(timezone.utc).strftime("%H:%M:%S"),
61
+ "—",
62
+ "—",
63
+ "Waiting for live events...",
64
+ "[green]PASS[/green]",
65
+ "[dim]INFO[/dim]",
66
+ "Telemetry stream listening on active runtime",
67
+ )
68
+ return table
69
+
70
+ for ev in reversed(events[-15:]):
71
+ ts = ev.get("timestamp", datetime.now(timezone.utc).strftime("%H:%M:%S"))
72
+ if isinstance(ts, datetime):
73
+ ts = ts.strftime("%H:%M:%S")
74
+ elif "T" in str(ts):
75
+ ts = str(ts).split("T")[1][:8]
76
+
77
+ sev = ev.get("severity", "LOW")
78
+ sev_styled = f"[{_get_severity_style(sev)}]{sev}[/{_get_severity_style(sev)}]"
79
+ dec = ev.get("decision", "ALLOW")
80
+ dec_styled = f"[{_get_severity_style(dec)}]{dec}[/{_get_severity_style(dec)}]"
81
+
82
+ table.add_row(
83
+ str(ts),
84
+ str(ev.get("event_id", "evt_live"))[:12],
85
+ str(ev.get("agent_id", "agent_main")),
86
+ str(ev.get("tool_name", ev.get("action", "unknown"))),
87
+ dec_styled,
88
+ sev_styled,
89
+ str(ev.get("reason", ev.get("details", "OK")))[:50],
90
+ )
91
+
92
+ return table
93
+
94
+
95
+ def run_live_watcher(poll_interval: float = 1.0, max_iterations: Optional[int] = None) -> None:
96
+ """Run interactive continuous live event watcher."""
97
+ console = Console()
98
+ events_buffer: List[Dict[str, Any]] = []
99
+
100
+ # Check if API or SDK runtime is active
101
+ from actshield.client import ActShield
102
+ ag = ActShield()
103
+
104
+ # Seed with initial telemetry if available
105
+ try:
106
+ if hasattr(ag, "forensics") and hasattr(ag.forensics, "get_all_events"):
107
+ events_buffer = ag.forensics.get_all_events()
108
+ except Exception:
109
+ pass
110
+
111
+ iteration = 0
112
+ with Live(generate_watch_table(events_buffer), console=console, refresh_per_second=4, screen=False) as live:
113
+ try:
114
+ while max_iterations is None or iteration < max_iterations:
115
+ time.sleep(poll_interval)
116
+ iteration += 1
117
+
118
+ # Poll live runtime state or mock trace if idle
119
+ try:
120
+ from actshield.posture import PostureEngine
121
+ posture = PostureEngine().evaluate_current_posture()
122
+ except Exception:
123
+ pass
124
+
125
+ live.update(generate_watch_table(events_buffer))
126
+ except KeyboardInterrupt:
127
+ console.print("\n[yellow]Live event watcher stopped.[/yellow]")
128
+
129
+
@@ -0,0 +1,57 @@
1
+ Metadata-Version: 2.4
2
+ Name: actshield
3
+ Version: 0.9.0
4
+ Summary: Enterprise Security Control Plane SDK for Autonomous and Multi-Agent AI Systems
5
+ Author: ActShield Security Team
6
+ License: Apache-2.0
7
+ Project-URL: Homepage, https://github.com/agentguard/agentguard
8
+ Project-URL: Documentation, https://agentguard.dev/docs
9
+ Project-URL: Repository, https://github.com/agentguard/agentguard
10
+ Project-URL: Issue Tracker, https://github.com/agentguard/agentguard/issues
11
+ Project-URL: Changelog, https://github.com/agentguard/agentguard/blob/main/CHANGELOG.md
12
+ Keywords: ai-security,multi-agent,provenance,causal-tracing,policy-enforcement,llm-security,taint-tracking,threat-modeling,sdk,cli,security-control-plane,agent-security
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Intended Audience :: Information Technology
16
+ Classifier: Intended Audience :: System Administrators
17
+ Classifier: Topic :: Security
18
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
19
+ Classifier: Topic :: System :: Monitoring
20
+ Classifier: Programming Language :: Python :: 3
21
+ Classifier: Programming Language :: Python :: 3.11
22
+ Classifier: Programming Language :: Python :: 3.12
23
+ Classifier: Programming Language :: Python :: 3.13
24
+ Classifier: Operating System :: OS Independent
25
+ Classifier: License :: OSI Approved :: Apache Software License
26
+ Classifier: Typing :: Typed
27
+ Requires-Python: >=3.11
28
+ Description-Content-Type: text/markdown
29
+ Requires-Dist: pydantic>=2.5.0
30
+ Requires-Dist: typing-extensions>=4.8.0
31
+ Provides-Extra: cli
32
+ Requires-Dist: rich>=13.7.0; extra == "cli"
33
+ Requires-Dist: typer>=0.12.0; extra == "cli"
34
+ Requires-Dist: click>=8.1.0; extra == "cli"
35
+ Provides-Extra: dev
36
+ Requires-Dist: pytest>=8.0.0; extra == "dev"
37
+ Requires-Dist: pytest-asyncio>=0.23.0; extra == "dev"
38
+ Requires-Dist: ruff>=0.4.0; extra == "dev"
39
+ Requires-Dist: mypy>=1.10.0; extra == "dev"
40
+ Requires-Dist: build>=1.0.0; extra == "dev"
41
+ Provides-Extra: server
42
+ Requires-Dist: fastapi>=0.110.0; extra == "server"
43
+ Requires-Dist: uvicorn[standard]>=0.29.0; extra == "server"
44
+ Requires-Dist: httpx>=0.27.0; extra == "server"
45
+ Requires-Dist: pydantic-settings>=2.0.0; extra == "server"
46
+ Provides-Extra: dashboard
47
+ Requires-Dist: actshield[cli,server]; extra == "dashboard"
48
+ Provides-Extra: openai
49
+ Requires-Dist: openai>=1.30.0; extra == "openai"
50
+ Provides-Extra: gemini
51
+ Requires-Dist: google-generativeai>=0.7.0; extra == "gemini"
52
+ Provides-Extra: anthropic
53
+ Requires-Dist: anthropic>=0.28.0; extra == "anthropic"
54
+ Provides-Extra: ollama
55
+ Requires-Dist: httpx>=0.27.0; extra == "ollama"
56
+ Provides-Extra: all
57
+ Requires-Dist: actshield[anthropic,cli,gemini,ollama,openai,server]; extra == "all"