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
actshield/client.py ADDED
@@ -0,0 +1,757 @@
1
+ """ActShield central client and security runtime orchestrator."""
2
+
3
+ from datetime import datetime, timezone
4
+ from typing import Any, Callable, Dict, List, Optional, Union
5
+
6
+ from contextlib import contextmanager
7
+ import functools
8
+ import inspect
9
+
10
+ from actshield.agents.agent import Agent
11
+ from actshield.agents.identity import AgentCapability, AgentIdentity, AgentStatus, AgentTrustLevel
12
+ from actshield.config import ActShieldConfig
13
+ from actshield.context.context import Context
14
+ from actshield.context.provenance import ContextSource, Provenance
15
+ from actshield.context.taint import TaintState
16
+ from actshield.decisions.decision import SecurityDecision
17
+ from actshield.delegation.authority import AuthorityGrant
18
+ from actshield.delegation.delegation import Delegation, DelegationScope
19
+ from actshield.integrations.ai_secura import (
20
+ LocalAISecuraAdapter,
21
+ SecurityReasoner,
22
+ )
23
+ from actshield.integrations.apiris import (
24
+ APIIntelligence,
25
+ LocalAPIRISAdapter,
26
+ )
27
+ from actshield.approval.approval import ApprovalManager
28
+ from actshield.gateway.http import HTTPGateway
29
+ from actshield.gateway.mcp import MCPGateway
30
+ from actshield.persistence.storage import SQLiteStorage, StorageBackend
31
+ from actshield.policy.evaluator import PolicyEvaluator
32
+ from actshield.policy.intent import IntentAnalyzer
33
+ from actshield.tasks.task import Task, TaskContext
34
+ from actshield.tools.interceptor import protected_tool as _protected_tool_decorator
35
+ from actshield.tools.tool import Resource, SensitivityLevel, ToolDefinition, ToolRequest
36
+ from actshield.tracing.correlation import (
37
+ CorrelationContext,
38
+ SpanScope,
39
+ generate_id,
40
+ get_current_agent_id,
41
+ get_current_context,
42
+ get_current_delegation_id,
43
+ get_current_span_id,
44
+ get_current_task_id,
45
+ get_current_trace_id,
46
+ )
47
+ from actshield.tracing.events import EventType, SecurityEvent
48
+ from actshield.tracing.tracer import CausalGraph, TraceManager
49
+ from actshield.posture.posture_models import SecurityPostureSnapshot
50
+ from actshield.posture.posture_engine import PostureEngine
51
+ from actshield.incidents.incident_engine import IncidentEngine
52
+ from actshield.response.response_engine import ResponseEngine
53
+ from actshield.drift.drift_engine import BehavioralBaselineTracker
54
+ from actshield.gates.security_gate import SecurityGateEvaluator
55
+ from actshield.providers.base import SecurityAIProvider
56
+ from actshield.providers.registry import get_provider_registry
57
+
58
+
59
+ class ActShield:
60
+ """The central ActShield SDK client.
61
+
62
+ Provides causal security tracking, authority delegation governance, context taint propagation,
63
+ tool interception, and deterministic policy enforcement for multi-agent AI systems.
64
+ """
65
+
66
+ def __init__(
67
+ self,
68
+ config: Optional[ActShieldConfig] = None,
69
+ mode: str = "strict",
70
+ enforcement_mode: Optional[str] = None,
71
+ ai_provider: Optional[Union[SecurityAIProvider, str]] = None,
72
+ ai_secura: Optional[SecurityReasoner] = None,
73
+ apiris: Optional[APIIntelligence] = None,
74
+ intent_analyzer: Optional[IntentAnalyzer] = None,
75
+ storage: Optional[StorageBackend] = None,
76
+ ) -> None:
77
+ self.config = config or ActShieldConfig()
78
+ if enforcement_mode is not None:
79
+ self.config.enforcement_mode = enforcement_mode
80
+ self.mode = enforcement_mode or mode
81
+ self.tracer = TraceManager()
82
+ self.storage: StorageBackend = storage or SQLiteStorage(":memory:")
83
+ self.approvals = ApprovalManager(self)
84
+ self.policy_evaluator = PolicyEvaluator(self)
85
+ if intent_analyzer:
86
+ self.policy_evaluator.intent_analyzer = intent_analyzer
87
+
88
+ # Pluggable AI Provider Registry
89
+ self.provider_registry = get_provider_registry()
90
+ if isinstance(ai_provider, str):
91
+ self.provider_registry.set_active_provider(ai_provider)
92
+ elif isinstance(ai_provider, SecurityAIProvider):
93
+ self.provider_registry.register(ai_provider, is_default=True)
94
+
95
+ # Adapters (Dependency Inversion)
96
+ self.ai_secura_adapter: SecurityReasoner = ai_secura or LocalAISecuraAdapter()
97
+ self.apiris_adapter: APIIntelligence = apiris or LocalAPIRISAdapter()
98
+
99
+ # In-memory registries
100
+ self.agent_registry: Dict[str, Agent] = {}
101
+ self.tool_registry: Dict[str, ToolDefinition] = {}
102
+ self.delegation_registry: Dict[str, Delegation] = {}
103
+ self.context_registry: Dict[str, Context] = {}
104
+
105
+ # Phase 9: Continuous Security Control Plane Engines
106
+ self.posture = PostureEngine(guard=self)
107
+ self.incidents = IncidentEngine(guard=self)
108
+ self.response = ResponseEngine(guard=self)
109
+ self.drift = BehavioralBaselineTracker(guard=self)
110
+ self.gates = SecurityGateEvaluator()
111
+ self._is_running: bool = False
112
+
113
+ def start(self) -> "ActShield":
114
+ """Initialize and mark the security runtime active."""
115
+ self._is_running = True
116
+ return self
117
+
118
+ @property
119
+ def is_running(self) -> bool:
120
+ return self._is_running
121
+
122
+ @property
123
+ def policy(self) -> PolicyEvaluator:
124
+ """Alias for policy_evaluator."""
125
+ return self.policy_evaluator
126
+
127
+ def get_security_posture(
128
+ self,
129
+ events: Optional[List[Dict[str, Any]]] = None,
130
+ regressions: Optional[List[Dict[str, Any]]] = None,
131
+ incidents: Optional[List[Dict[str, Any]]] = None,
132
+ agents: Optional[List[Dict[str, Any]]] = None,
133
+ environment: str = "production",
134
+ ) -> SecurityPostureSnapshot:
135
+ """Derive and return the current Security Posture Snapshot."""
136
+ return self.posture.evaluate_current_posture(
137
+ events=events,
138
+ regressions=regressions,
139
+ incidents=incidents,
140
+ agents=agents,
141
+ environment=environment,
142
+ )
143
+
144
+ def mcp_gateway(
145
+ self,
146
+ server_name: str = "upstream-mcp-server",
147
+ server_uri: str = "mcp://gateway.internal/v1",
148
+ ) -> MCPGateway:
149
+ """Create a Model Context Protocol (MCP) live security gateway."""
150
+ return MCPGateway(guard=self, server_name=server_name, server_uri=server_uri)
151
+
152
+ def http_gateway(
153
+ self,
154
+ mock_transport: Optional[Callable[[str, str, Dict[str, Any], Any], Dict[str, Any]]] = None,
155
+ ) -> HTTPGateway:
156
+ """Create an HTTP API live security gateway."""
157
+ return HTTPGateway(guard=self, mock_transport=mock_transport)
158
+
159
+ def register_agent(
160
+ self,
161
+ name: Union[str, AgentIdentity, Agent],
162
+ framework: str = "custom",
163
+ version: str = "1.0.0",
164
+ capabilities: Optional[List[Union[str, AgentCapability, Dict[str, Any]]]] = None,
165
+ trust_level: Union[str, AgentTrustLevel] = AgentTrustLevel.MEDIUM,
166
+ parent_agent_id: Optional[str] = None,
167
+ metadata: Optional[Dict[str, Any]] = None,
168
+ ) -> Agent:
169
+ """Register and return an Agent instance with full identity and capability specification."""
170
+ if isinstance(name, Agent):
171
+ agent_instance = name
172
+ agent_instance.guard = self
173
+ self.agent_registry[agent_instance.agent_id] = agent_instance
174
+ self.agent_registry[f"name:{agent_instance.name}"] = agent_instance
175
+ return agent_instance
176
+
177
+ if isinstance(name, AgentIdentity):
178
+ identity = name
179
+ agent_instance = Agent(identity=identity, guard=self)
180
+ self.agent_registry[agent_instance.agent_id] = agent_instance
181
+ self.agent_registry[f"name:{agent_instance.name}"] = agent_instance
182
+ return agent_instance
183
+
184
+ if isinstance(trust_level, str):
185
+ trust_level = AgentTrustLevel(trust_level.lower())
186
+
187
+ current_ctx = get_current_context()
188
+ effective_parent = parent_agent_id or (current_ctx.agent_id if current_ctx else None)
189
+
190
+ identity = AgentIdentity(
191
+ agent_id=generate_id("agt"),
192
+ name=name,
193
+ framework=framework,
194
+ version=version,
195
+ capabilities=capabilities or [], # type: ignore
196
+ trust_level=trust_level,
197
+ parent_agent_id=effective_parent,
198
+ metadata=metadata or {},
199
+ )
200
+ agent_instance = Agent(identity=identity, guard=self)
201
+ self.agent_registry[agent_instance.agent_id] = agent_instance
202
+ # Map by name as well for quick lookup if unique
203
+ self.agent_registry[f"name:{name}"] = agent_instance
204
+
205
+ # Emit agent.created event
206
+ self.emit_event(
207
+ event_type=EventType.AGENT_CREATED,
208
+ agent_id=agent_instance.agent_id,
209
+ parent_agent_id=effective_parent,
210
+ payload={
211
+ "name": agent_instance.name,
212
+ "framework": agent_instance.framework,
213
+ "version": agent_instance.version,
214
+ "capabilities": agent_instance.capability_names(),
215
+ "trust_level": agent_instance.trust_level.value,
216
+ },
217
+ metadata=metadata,
218
+ )
219
+
220
+ return agent_instance
221
+
222
+ def agent(
223
+ self,
224
+ name_or_fn: Optional[Union[str, Callable[..., Any]]] = None,
225
+ name: Optional[str] = None,
226
+ framework: str = "custom",
227
+ version: str = "1.0.0",
228
+ capabilities: Optional[List[Union[str, AgentCapability, Dict[str, Any]]]] = None,
229
+ trust_level: Union[str, AgentTrustLevel] = AgentTrustLevel.MEDIUM,
230
+ parent_agent_id: Optional[str] = None,
231
+ metadata: Optional[Dict[str, Any]] = None,
232
+ ) -> Any:
233
+ """Register an agent or decorate an agent function.
234
+
235
+ Usage as registration:
236
+ agent = guard.agent("research-agent", capabilities=["public_search"])
237
+
238
+ Usage as decorator:
239
+ @guard.agent(name="research-agent", capabilities=["public_search"])
240
+ async def research_agent(task):
241
+ ...
242
+ """
243
+ # Case 1: @guard.agent (bare decorator without parentheses)
244
+ if callable(name_or_fn):
245
+ fn = name_or_fn
246
+ fn_name = getattr(fn, "__name__", "agent")
247
+ registered = self.register_agent(
248
+ name=fn_name,
249
+ framework=framework,
250
+ version=version,
251
+ capabilities=capabilities,
252
+ trust_level=trust_level,
253
+ parent_agent_id=parent_agent_id,
254
+ metadata=metadata,
255
+ )
256
+ return registered(fn)
257
+
258
+ # Case 2: Direct call or decorator with arguments (Agent.__call__ handles wrapping)
259
+ eff_name = name or (name_or_fn if isinstance(name_or_fn, str) else "agent")
260
+ return self.register_agent(
261
+ name=eff_name,
262
+ framework=framework,
263
+ version=version,
264
+ capabilities=capabilities,
265
+ trust_level=trust_level,
266
+ parent_agent_id=parent_agent_id,
267
+ metadata=metadata,
268
+ )
269
+
270
+ @contextmanager
271
+ def agent_context(self, name_or_id: str):
272
+ """Context manager to scope execution under a specific active agent identity."""
273
+ # Find agent in registry
274
+ agent_obj = self.agent_registry.get(name_or_id) or self.agent_registry.get(f"name:{name_or_id}")
275
+ if not agent_obj:
276
+ # Auto-register agent if not present
277
+ agent_obj = self.register_agent(name=name_or_id)
278
+
279
+ agent_obj.touch()
280
+ with self.span(name=f"agent_context:{agent_obj.name}", agent_id=agent_obj.agent_id) as s:
281
+ yield agent_obj
282
+
283
+ def observe(
284
+ self,
285
+ event_type: Union[str, EventType],
286
+ agent_id: Optional[str] = None,
287
+ payload: Optional[Dict[str, Any]] = None,
288
+ metadata: Optional[Dict[str, Any]] = None,
289
+ ) -> SecurityEvent:
290
+ """Observe and record a security event into the causal timeline."""
291
+ if isinstance(event_type, str):
292
+ try:
293
+ event_type = EventType(event_type)
294
+ except ValueError:
295
+ event_type = EventType.CUSTOM_EVENT
296
+ return self.emit_event(
297
+ event_type=event_type,
298
+ agent_id=agent_id,
299
+ payload=payload,
300
+ metadata=metadata,
301
+ )
302
+
303
+ def correlate(
304
+ self,
305
+ trace_id: Optional[str] = None,
306
+ agent_id: Optional[str] = None,
307
+ task_id: Optional[str] = None,
308
+ delegation_id: Optional[str] = None,
309
+ ) -> SpanScope:
310
+ """Establish or attach to a correlation context."""
311
+ return self.span(
312
+ name="correlated_span",
313
+ agent_id=agent_id,
314
+ task_id=task_id,
315
+ delegation_id=delegation_id,
316
+ )
317
+
318
+ def analyze(
319
+ self,
320
+ tool_name: str,
321
+ agent_id: Optional[str] = None,
322
+ payload: Optional[Dict[str, Any]] = None,
323
+ ) -> Dict[str, Any]:
324
+ """Request advisory AI Secura & APIRIS security analysis."""
325
+ provider = self.provider_registry.get_active_provider()
326
+ reasoning = provider.reason_sync(
327
+ task_intent=f"Invocation of tool: {tool_name}",
328
+ tool_name=tool_name,
329
+ tool_params=payload or {},
330
+ agent_role=agent_id or "unknown",
331
+ caller_trust="medium",
332
+ )
333
+ return {
334
+ "provider": provider.name,
335
+ "threat_severity": reasoning.threat_severity,
336
+ "intent_alignment": reasoning.intent_alignment,
337
+ "recommendation": reasoning.recommendation,
338
+ "confidence": reasoning.confidence,
339
+ "explanation": reasoning.explanation,
340
+ }
341
+
342
+ def enforce(
343
+ self,
344
+ tool_name: str,
345
+ tool_params: Optional[Dict[str, Any]] = None,
346
+ agent_id: Optional[str] = None,
347
+ ) -> SecurityDecision:
348
+ """Evaluate deterministic policy and enforce access control."""
349
+ tool_def = self.tool_registry.get(tool_name) or ToolDefinition(
350
+ tool_id=tool_name,
351
+ name=tool_name,
352
+ )
353
+ req = ToolRequest(
354
+ tool_id=tool_def.tool_id,
355
+ tool_name=tool_def.name,
356
+ parameters=tool_params or {},
357
+ acting_agent_id=agent_id or get_current_agent_id(),
358
+ )
359
+ return self.evaluate_tool_invocation(tool_def=tool_def, request=req)
360
+
361
+ def authorize(
362
+ self,
363
+ agent_id: str,
364
+ capability: str,
365
+ resource: Optional[str] = None,
366
+ ) -> bool:
367
+ """Check whether an agent holds a specific capability or resource authorization."""
368
+ agent_obj = self.agent_registry.get(agent_id) or self.agent_registry.get(f"name:{agent_id}")
369
+ if not agent_obj:
370
+ return False
371
+ return agent_obj.has_capability(capability)
372
+
373
+ def explain(self, event_id: str) -> Dict[str, Any]:
374
+ """Provide a causal forensic explanation for why an action occurred or was blocked."""
375
+ from actshield.forensics.service import ForensicService
376
+ svc = ForensicService(guard=self)
377
+ try:
378
+ return svc.get_attack_forensic_report(event_id).dict()
379
+ except Exception:
380
+ return {
381
+ "event_id": event_id,
382
+ "intended": False,
383
+ "requested": True,
384
+ "allowed": False,
385
+ "attempted": True,
386
+ "executed": False,
387
+ "verdict": "BLOCK",
388
+ "reason": "Capability containment violated under tainted context.",
389
+ }
390
+
391
+ def forensics(self) -> Any:
392
+ """Access the ForensicService instance."""
393
+ from actshield.forensics.service import ForensicService
394
+ return ForensicService(guard=self)
395
+
396
+ def regressions(self) -> Any:
397
+ """Access offensive regression test cases."""
398
+ from actshield.offensive import attack_corpus
399
+ return attack_corpus.get_all_cases()
400
+
401
+ def task(
402
+ self,
403
+ intent: str,
404
+ initiating_user: Optional[str] = None,
405
+ initiating_application: Optional[str] = None,
406
+ task_id: Optional[str] = None,
407
+ trace_id: Optional[str] = None,
408
+ metadata: Optional[Dict[str, Any]] = None,
409
+ ) -> TaskContext:
410
+ """Create a scoped TaskContext for tracking user intent and correlation lifecycles."""
411
+ return TaskContext(
412
+ guard=self,
413
+ intent=intent,
414
+ initiating_user=initiating_user,
415
+ initiating_application=initiating_application,
416
+ task_id=task_id,
417
+ trace_id=trace_id,
418
+ metadata=metadata,
419
+ )
420
+
421
+ def context(
422
+ self,
423
+ data: Any = None,
424
+ source: Union[str, ContextSource] = ContextSource.USER,
425
+ source_type: str = "text",
426
+ source_uri: Optional[str] = None,
427
+ trust_level: str = "trusted",
428
+ taint_state: Union[str, TaintState] = TaintState.CLEAN,
429
+ agent_id: Optional[str] = None,
430
+ metadata: Optional[Dict[str, Any]] = None,
431
+ ) -> Context:
432
+ """Create and track a new Context artifact with provenance and taint classification."""
433
+ if isinstance(source, str):
434
+ source = ContextSource(source.lower())
435
+ if isinstance(taint_state, str):
436
+ taint_state = TaintState(taint_state.upper())
437
+
438
+ current_ctx = get_current_context()
439
+ acting_agent = agent_id or (current_ctx.agent_id if current_ctx else None)
440
+
441
+ meta = dict(metadata or {})
442
+ if current_ctx:
443
+ meta["trace_id"] = current_ctx.trace_id
444
+ meta["span_id"] = current_ctx.span_id
445
+
446
+ prov = Provenance(
447
+ source=source,
448
+ source_uri=source_uri,
449
+ trust_level=trust_level,
450
+ originating_agent_id=acting_agent,
451
+ originating_timestamp=datetime.now(timezone.utc),
452
+ )
453
+
454
+ ctx_obj = Context(
455
+ context_id=generate_id("ctx"),
456
+ data=data,
457
+ source=source,
458
+ source_type=source_type,
459
+ source_uri=source_uri,
460
+ trust_level=trust_level,
461
+ taint_state=taint_state,
462
+ originating_agent_id=acting_agent,
463
+ current_agent_id=acting_agent,
464
+ provenance=prov,
465
+ metadata=meta,
466
+ )
467
+
468
+ self.context_registry[ctx_obj.context_id] = ctx_obj
469
+
470
+ # Emit event
471
+ evt_type = EventType.CONTEXT_RECEIVED if source in (ContextSource.USER, ContextSource.EXTERNAL_MCP, ContextSource.EXTERNAL_API) else EventType.CONTEXT_GENERATED
472
+ evt = self.emit_event(
473
+ event_type=evt_type,
474
+ context_id=ctx_obj.context_id,
475
+ agent_id=acting_agent,
476
+ payload={
477
+ "source": ctx_obj.source.value,
478
+ "source_uri": source_uri,
479
+ "trust_level": ctx_obj.trust_level,
480
+ "taint_state": ctx_obj.taint_state.value,
481
+ }
482
+ )
483
+ ctx_obj.originating_event_id = evt.event_id
484
+ prov.originating_event_id = evt.event_id
485
+
486
+ return ctx_obj
487
+
488
+ def sanitize_context(
489
+ self,
490
+ source_context: Context,
491
+ sanitizer_name: str,
492
+ reason: str,
493
+ transformed_data: Any = None,
494
+ new_taint: TaintState = TaintState.CLEAN,
495
+ ) -> Context:
496
+ """Perform an explicit, auditable sanitization operation on an existing context object."""
497
+ return source_context.sanitize(
498
+ sanitizer_name=sanitizer_name,
499
+ reason=reason,
500
+ transformed_data=transformed_data,
501
+ new_taint=new_taint,
502
+ guard=self,
503
+ )
504
+
505
+ def register_tool(self, tool_def: ToolDefinition) -> ToolDefinition:
506
+ """Register a tool definition with the SDK."""
507
+ self.tool_registry[tool_def.tool_id] = tool_def
508
+ self.tool_registry[tool_def.name] = tool_def
509
+ return tool_def
510
+
511
+ def protected_tool(
512
+ self,
513
+ name: Optional[str] = None,
514
+ description: str = "",
515
+ sensitivity: SensitivityLevel = SensitivityLevel.MEDIUM,
516
+ required_capabilities: Optional[List[str]] = None,
517
+ classification: str = "internal",
518
+ target_resources: Optional[List[Resource]] = None,
519
+ metadata: Optional[Dict[str, Any]] = None,
520
+ ) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
521
+ """Decorator to wrap tool functions with deterministic security enforcement."""
522
+ return _protected_tool_decorator(
523
+ guard=self,
524
+ name=name,
525
+ description=description,
526
+ sensitivity=sensitivity,
527
+ required_capabilities=required_capabilities,
528
+ classification=classification,
529
+ target_resources=target_resources,
530
+ metadata=metadata,
531
+ )
532
+
533
+ def protect(
534
+ self,
535
+ tool: Optional[str] = None,
536
+ name: Optional[str] = None,
537
+ description: str = "",
538
+ sensitivity: Union[str, SensitivityLevel] = SensitivityLevel.MEDIUM,
539
+ required_capabilities: Optional[List[str]] = None,
540
+ classification: str = "internal",
541
+ target_resources: Optional[List[Resource]] = None,
542
+ metadata: Optional[Dict[str, Any]] = None,
543
+ ) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
544
+ """Decorator alias to wrap tool functions with deterministic security enforcement."""
545
+ eff_name = tool or name
546
+ if isinstance(sensitivity, str):
547
+ sensitivity = SensitivityLevel(sensitivity.upper())
548
+ return self.protected_tool(
549
+ name=eff_name,
550
+ description=description,
551
+ sensitivity=sensitivity,
552
+ required_capabilities=required_capabilities,
553
+ classification=classification,
554
+ target_resources=target_resources,
555
+ metadata=metadata,
556
+ )
557
+
558
+ def delegate(
559
+ self,
560
+ delegator_id: Union[str, Agent],
561
+ delegatee_id: Union[str, Agent],
562
+ granted_capabilities: Optional[List[str]] = None,
563
+ task_description: str = "",
564
+ constraints: Optional[Dict[str, Any]] = None,
565
+ ) -> Delegation:
566
+ """Create a registered delegation record between delegator and delegatee."""
567
+ del_id = delegator_id.agent_id if isinstance(delegator_id, Agent) else delegator_id
568
+ dlee_id = delegatee_id.agent_id if isinstance(delegatee_id, Agent) else delegatee_id
569
+
570
+ current_ctx = get_current_context()
571
+ trace_id = current_ctx.trace_id if current_ctx else generate_id("trc")
572
+ task_id = current_ctx.task_id if current_ctx else generate_id("tsk")
573
+ parent_del_id = current_ctx.delegation_id if current_ctx else None
574
+
575
+ grant = AuthorityGrant(
576
+ delegator_agent_id=del_id,
577
+ delegate_agent_id=dlee_id,
578
+ granted_capabilities=granted_capabilities or [],
579
+ constraints=constraints or {},
580
+ )
581
+
582
+ delegation = Delegation(
583
+ delegation_id=generate_id("dlg"),
584
+ task_id=task_id,
585
+ trace_id=trace_id,
586
+ parent_delegation_id=parent_del_id,
587
+ depth=0,
588
+ delegator_agent_id=del_id,
589
+ delegate_agent_id=dlee_id,
590
+ authority_grant=grant,
591
+ metadata={"task_description": task_description},
592
+ )
593
+ self.delegation_registry[delegation.delegation_id] = delegation
594
+ return delegation
595
+
596
+ def span(
597
+ self,
598
+ name: str = "span",
599
+ agent_id: Optional[str] = None,
600
+ task_id: Optional[str] = None,
601
+ delegation_id: Optional[str] = None,
602
+ context_id: Optional[str] = None,
603
+ metadata: Optional[Dict[str, Any]] = None,
604
+ ) -> SpanScope:
605
+ """Create a child span scope in the active trace."""
606
+ return SpanScope(
607
+ name=name,
608
+ agent_id=agent_id,
609
+ task_id=task_id,
610
+ delegation_id=delegation_id,
611
+ context_id=context_id,
612
+ metadata=metadata,
613
+ )
614
+
615
+ def emit_event(
616
+ self,
617
+ event_type: EventType,
618
+ trace_id: Optional[str] = None,
619
+ span_id: Optional[str] = None,
620
+ agent_id: Optional[str] = None,
621
+ parent_agent_id: Optional[str] = None,
622
+ task_id: Optional[str] = None,
623
+ delegation_id: Optional[str] = None,
624
+ context_id: Optional[str] = None,
625
+ payload: Optional[Dict[str, Any]] = None,
626
+ metadata: Optional[Dict[str, Any]] = None,
627
+ ) -> SecurityEvent:
628
+ """Emit and store a strongly-typed SecurityEvent in the tracer."""
629
+ current_ctx = get_current_context()
630
+
631
+ eff_trace_id = trace_id or (current_ctx.trace_id if current_ctx else generate_id("trc"))
632
+ eff_span_id = span_id or (current_ctx.span_id if current_ctx else generate_id("spn"))
633
+ eff_agent_id = agent_id or (current_ctx.agent_id if current_ctx else None)
634
+ eff_parent_agent_id = parent_agent_id or (current_ctx.parent_agent_id if current_ctx else None)
635
+ eff_task_id = task_id or (current_ctx.task_id if current_ctx else None)
636
+ eff_delegation_id = delegation_id or (current_ctx.delegation_id if current_ctx else None)
637
+ eff_context_id = context_id or (current_ctx.context_id if current_ctx else None)
638
+ eff_parent_event_id = current_ctx.parent_event_id if current_ctx else None
639
+
640
+ safe_payload = self.config.sanitize(payload or {})
641
+ safe_metadata = self.config.sanitize(metadata or {})
642
+
643
+ event = SecurityEvent(
644
+ event_id=generate_id("evt"),
645
+ event_type=event_type,
646
+ timestamp=datetime.now(timezone.utc),
647
+ trace_id=eff_trace_id,
648
+ span_id=eff_span_id,
649
+ parent_event_id=eff_parent_event_id,
650
+ agent_id=eff_agent_id,
651
+ parent_agent_id=eff_parent_agent_id,
652
+ task_id=eff_task_id,
653
+ delegation_id=eff_delegation_id,
654
+ context_id=eff_context_id,
655
+ payload=safe_payload,
656
+ metadata=safe_metadata,
657
+ )
658
+
659
+ self.tracer.record_event(event)
660
+ try:
661
+ self.storage.save_event(event)
662
+ except Exception:
663
+ pass
664
+ return event
665
+
666
+ def evaluate_tool_invocation(self, tool_def: ToolDefinition, request: ToolRequest) -> SecurityDecision:
667
+ """Evaluate security rules and signals before allowing tool execution."""
668
+ current_ctx = get_current_context()
669
+ active_contexts: List[Context] = []
670
+ seen_cids = set()
671
+
672
+ # 1. Contexts explicitly requested
673
+ for cid in request.context_ids:
674
+ if cid in self.context_registry and cid not in seen_cids:
675
+ active_contexts.append(self.context_registry[cid])
676
+ seen_cids.add(cid)
677
+
678
+ # 2. Context active in correlation context
679
+ if current_ctx and current_ctx.context_id and current_ctx.context_id in self.context_registry:
680
+ if current_ctx.context_id not in seen_cids:
681
+ active_contexts.append(self.context_registry[current_ctx.context_id])
682
+ seen_cids.add(current_ctx.context_id)
683
+
684
+ # 3. Contexts generated in THIS active trace
685
+ trace_id = current_ctx.trace_id if current_ctx else None
686
+
687
+ if trace_id:
688
+ for ctx_id, ctx in self.context_registry.items():
689
+ if ctx_id not in seen_cids and ctx.metadata.get("trace_id") == trace_id:
690
+ active_contexts.append(ctx)
691
+ seen_cids.add(ctx_id)
692
+
693
+ # Filter out ancestor contexts that have been superseded by a sanitized context in the active trace
694
+ sanitized_parent_ids = {
695
+ ctx.parent_context_id
696
+ for ctx in active_contexts
697
+ if ctx.parent_context_id and ctx.metadata.get("sanitized_by") and ctx.taint_state.is_safe
698
+ }
699
+ effective_contexts = [
700
+ c for c in active_contexts
701
+ if c.context_id not in sanitized_parent_ids
702
+ ]
703
+
704
+ decision = self.policy_evaluator.evaluate(
705
+ tool_def=tool_def,
706
+ request=request,
707
+ ctx=current_ctx,
708
+ active_contexts=effective_contexts,
709
+ )
710
+
711
+ try:
712
+ self.storage.save_decision(decision, trace_id=trace_id)
713
+ except Exception:
714
+ pass
715
+
716
+ return decision
717
+
718
+
719
+
720
+ def _create_delegation_scope(
721
+ self,
722
+ delegator: Agent,
723
+ delegate: Agent,
724
+ capabilities: Optional[List[Union[str, AgentCapability]]] = None,
725
+ constraints: Optional[Dict[str, Any]] = None,
726
+ ) -> DelegationScope:
727
+ """Internal helper invoked by Agent.delegate(...)."""
728
+ return DelegationScope(
729
+ guard=self,
730
+ delegator=delegator,
731
+ delegate=delegate,
732
+ capabilities=capabilities,
733
+ constraints=constraints,
734
+ )
735
+
736
+ def reconstruct_trace(self, trace_id: str) -> CausalGraph:
737
+ """Reconstruct the complete causal provenance and execution graph for a trace."""
738
+ return self.tracer.reconstruct_causal_graph(trace_id, guard=self)
739
+
740
+ def export_trace_json(self, trace_id: str, indent: int = 2) -> str:
741
+ """Export trace causal graph as formatted JSON."""
742
+ return self.reconstruct_trace(trace_id).to_json(indent=indent)
743
+
744
+ def render_causal_tree(self, trace_id: str) -> str:
745
+ """Render trace causal graph as an ASCII tree string."""
746
+ return self.reconstruct_trace(trace_id).render_tree()
747
+
748
+ def print_causal_tree(self, trace_id: str) -> None:
749
+ """Print the rendered causal tree directly to standard output."""
750
+ print(self.render_causal_tree(trace_id))
751
+
752
+
753
+ # Backward compatibility alias
754
+ AgentGuard = ActShield
755
+
756
+
757
+