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,466 @@
1
+ """ActShield Offensive Validation Engine — Orchestrating continuous security evaluation."""
2
+
3
+ import json
4
+ import logging
5
+ import os
6
+ import pathlib
7
+ import time
8
+ from typing import Any, Dict, List, Optional, Tuple
9
+
10
+ from actshield.client import ActShield
11
+ from actshield.context.provenance import ContextSource
12
+ from actshield.context.taint import TaintState
13
+ from actshield.decisions.decision import DecisionAction, SecurityDecision
14
+ from actshield.integrations.ai_secura import SecurityContext
15
+ from actshield.offensive.attack import AttackCase, AttackType, ExpectedBehavior, SafetyClass
16
+ from actshield.offensive.campaign import AttackCampaign, CampaignSummary
17
+ from actshield.offensive.corpus import AttackCorpus, attack_corpus
18
+ from actshield.offensive.evaluator import AttackEvaluator
19
+ from actshield.offensive.evidence import SecurityAnalysisEvidence
20
+ from actshield.offensive.results import AttackStatus, ExecutionEvidence, OffensiveAttackResult
21
+ from actshield.offensive.safety import SafetyValidator, safety_validator
22
+ from actshield.offensive.target import AttackTarget, TargetRegistry, target_registry
23
+ from actshield.tools.tool import Resource, SensitivityLevel, ToolDefinition, ToolRequest
24
+ from actshield.tracing.correlation import generate_id
25
+
26
+ logger = logging.getLogger("actshield.offensive.engine")
27
+
28
+
29
+ class OffensiveEngine:
30
+ """The central orchestrator for autonomous offensive security validation."""
31
+
32
+ def __init__(
33
+ self,
34
+ guard: Optional[ActShield] = None,
35
+ validator: Optional[SafetyValidator] = None,
36
+ registry: Optional[TargetRegistry] = None,
37
+ corpus: Optional[AttackCorpus] = None,
38
+ ) -> None:
39
+ self.guard = guard or ActShield()
40
+ self.validator = validator or safety_validator
41
+ self.registry = registry or target_registry
42
+ self.corpus = corpus or attack_corpus
43
+
44
+ # Execution tracking counters
45
+ self.customer_read_calls = 0
46
+ self.total_tool_executions = 0
47
+ self._setup_tools()
48
+
49
+ def _setup_tools(self) -> None:
50
+ """Register synthetic sandbox tools with hard execution monitoring."""
51
+ from actshield.tools.interceptor import protected_tool
52
+ from actshield.tools.tool import Resource, SensitivityLevel
53
+
54
+ # 1. Protected Sensitive Customer DB Sink
55
+ cust_res = Resource(
56
+ resource_id="res_cust",
57
+ name="customer_pii_vault",
58
+ sensitivity=SensitivityLevel.CRITICAL,
59
+ )
60
+
61
+ @protected_tool(
62
+ guard=self.guard,
63
+ name="customer_db.read",
64
+ description="Reads customer PII records from vault",
65
+ sensitivity=SensitivityLevel.CRITICAL,
66
+ required_capabilities=["customer_db.read"],
67
+ target_resources=[cust_res],
68
+ )
69
+ def customer_db_read(table: str = "customers", fields: Optional[List[str]] = None, **kwargs: Any) -> Dict[str, Any]:
70
+ self.customer_read_calls += 1
71
+ self.total_tool_executions += 1
72
+ return {"status": "SUCCESS", "rows": [{"id": 1, "name": "Alice", "pii": "EXTRACTED"}]}
73
+
74
+ # 2. Protected Low-Sensitivity Public Tool
75
+ pub_res = Resource(
76
+ resource_id="res_pub",
77
+ name="public_filing_cache",
78
+ sensitivity=SensitivityLevel.LOW,
79
+ )
80
+
81
+ @protected_tool(
82
+ guard=self.guard,
83
+ name="sec_edgar.fetch_filing",
84
+ description="Fetches public Form 10-K SEC filings",
85
+ sensitivity=SensitivityLevel.LOW,
86
+ required_capabilities=["public_documents", "public_search"],
87
+ target_resources=[pub_res],
88
+ )
89
+ def fetch_filing(ticker: str = "ACME", year: int = 2026, **kwargs: Any) -> Dict[str, Any]:
90
+ self.total_tool_executions += 1
91
+ return {"status": "SUCCESS", "filing": "ACME FY2026 10-K: Revenue $14.2B"}
92
+
93
+ # 3. HTTP Outbound Tool
94
+ ext_res = Resource(
95
+ resource_id="res_ext",
96
+ name="external_api",
97
+ sensitivity=SensitivityLevel.HIGH,
98
+ )
99
+
100
+ @protected_tool(
101
+ guard=self.guard,
102
+ name="http_post",
103
+ description="Posts outbound payload to synthetic endpoint",
104
+ sensitivity=SensitivityLevel.HIGH,
105
+ required_capabilities=["external_api"],
106
+ target_resources=[ext_res],
107
+ )
108
+ def http_post(url: str = "https://synthetic.internal", data: Any = None, **kwargs: Any) -> Dict[str, Any]:
109
+ self.total_tool_executions += 1
110
+ return {"status": "POSTED", "url": url}
111
+
112
+ self._tool_callables = {
113
+ "customer_db.read": customer_db_read,
114
+ "sec_edgar.fetch_filing": fetch_filing,
115
+ "http_post": http_post,
116
+ }
117
+
118
+ def execute_attack(
119
+ self,
120
+ attack: AttackCase,
121
+ target: Optional[AttackTarget] = None,
122
+ campaign_id: Optional[str] = None,
123
+ mutation_lineage: Optional[List[str]] = None,
124
+ ) -> OffensiveAttackResult:
125
+ """Execute a single attack case against the local synthetic environment."""
126
+ target = target or self.registry.get("ActShield-demo")
127
+ start_time = time.perf_counter()
128
+
129
+ # 1. Strict Safety Validation
130
+ safety_check = self.validator.validate(attack, target)
131
+ if not safety_check.safe:
132
+ elapsed_ms = (time.perf_counter() - start_time) * 1000
133
+ return OffensiveAttackResult(
134
+ attack_id=attack.attack_id,
135
+ campaign_id=campaign_id,
136
+ attack_type=attack.attack_type,
137
+ target=target.target_id if target else "unknown",
138
+ entry_point=attack.entry_point,
139
+ expected_decision=attack.expected_behavior.value,
140
+ actual_decision="REFUSED",
141
+ status=AttackStatus.REFUSED,
142
+ error_message=safety_check.reason,
143
+ latency_ms=elapsed_ms,
144
+ severity=attack.severity,
145
+ )
146
+
147
+ # 2. Reset hard execution counters before test
148
+ initial_db_calls = self.customer_read_calls
149
+ initial_tool_execs = self.total_tool_executions
150
+
151
+ try:
152
+ # 3. Initialize Multi-Agent Task Context
153
+ trace_id = generate_id("tr_atk")
154
+
155
+ # Setup synthetic agent with appropriate capabilities
156
+ if attack.attack_type in (AttackType.AUTHORITY_ESCALATION, AttackType.AUTHORITY_IMPERSONATION, AttackType.DELEGATION_ESCALATION):
157
+ agent_caps = [attack.required_capability] if attack.required_capability else ["public_search", "financial_extract"]
158
+ else:
159
+ agent_caps = [
160
+ "public_search",
161
+ "public_documents",
162
+ "financial_extract",
163
+ "customer_db.read",
164
+ "external_api",
165
+ ]
166
+ if attack.required_capability and attack.required_capability not in agent_caps:
167
+ agent_caps.append(attack.required_capability)
168
+
169
+ agent = self.guard.agent(
170
+ name=attack.target_agent,
171
+ framework="custom",
172
+ capabilities=agent_caps,
173
+ metadata={"synthetic_target": True},
174
+ )
175
+
176
+ # Ingest context & evaluate within task and span scope
177
+ with self.guard.task(intent=attack.task_intent, trace_id=trace_id) as task_obj:
178
+ with self.guard.span(name="attack_execution", agent_id=agent.agent_id):
179
+ # Ingest adversarial context
180
+ source_enum = ContextSource.EXTERNAL_MCP if "mcp" in attack.entry_point else ContextSource.EXTERNAL_API
181
+ taint_enum = TaintState.TAINTED if attack.context_taint == "TAINTED" else TaintState.CLEAN
182
+
183
+ adv_ctx = self.guard.context(
184
+ source=source_enum,
185
+ data=attack.payload,
186
+ taint_state=taint_enum,
187
+ agent_id=agent.agent_id,
188
+ )
189
+
190
+ tool_name = attack.target_tool or "customer_db.read"
191
+ target_tool_def = self.guard.tool_registry.get(tool_name)
192
+ if target_tool_def is None:
193
+ req_cap = attack.required_capability or tool_name
194
+ res_name = attack.target_resource or tool_name.split(".")[0]
195
+ tool_res = Resource(
196
+ resource_id=f"res_{res_name}",
197
+ name=res_name,
198
+ sensitivity=SensitivityLevel.CRITICAL if any(k in res_name for k in ("vault", "pii", "ledger", "customer")) else SensitivityLevel.HIGH,
199
+ )
200
+ target_tool_def = ToolDefinition(
201
+ tool_id=generate_id("tool"),
202
+ name=tool_name,
203
+ description=f"Synthetic tool {tool_name}",
204
+ sensitivity=SensitivityLevel.CRITICAL if any(k in res_name for k in ("vault", "pii", "ledger", "customer")) else SensitivityLevel.HIGH,
205
+ required_capabilities=[req_cap],
206
+ target_resources=[tool_res],
207
+ )
208
+ self.guard.register_tool(target_tool_def)
209
+
210
+ tool_req = ToolRequest(
211
+ tool_id=target_tool_def.tool_id,
212
+ tool_name=tool_name,
213
+ agent_id=agent.agent_id,
214
+ arguments=attack.tool_arguments or {"table": "customers"},
215
+ context_ids=[adv_ctx.context_id],
216
+ )
217
+
218
+ # --- Full Security Analysis Pipeline ---
219
+ # 1. APIRIS API/Tool Risk Intelligence
220
+ apiris_analysis_dict = None
221
+ apiris_unavail = False
222
+ try:
223
+ if hasattr(self.guard, "apiris_adapter") and self.guard.apiris_adapter:
224
+ apiris_raw = self.guard.apiris_adapter.analyze(tool_req)
225
+ if hasattr(apiris_raw, "to_dict"):
226
+ apiris_analysis_dict = apiris_raw.to_dict()
227
+ elif hasattr(apiris_raw, "model_dump"):
228
+ apiris_analysis_dict = apiris_raw.model_dump()
229
+ elif isinstance(apiris_raw, dict):
230
+ apiris_analysis_dict = apiris_raw
231
+ else:
232
+ apiris_analysis_dict = {
233
+ "tool_name": tool_name,
234
+ "risk_score": getattr(apiris_raw, "risk_score", 0.0),
235
+ "recommended_action": getattr(apiris_raw, "recommended_action", "ALLOW"),
236
+ }
237
+ except Exception as e:
238
+ apiris_unavail = True
239
+ apiris_analysis_dict = {"status": "UNAVAILABLE", "error": str(e)}
240
+
241
+ # 2. AI Secura Cybersecurity Reasoning
242
+ ai_analysis_dict = None
243
+ ai_unavail = False
244
+ try:
245
+ if hasattr(self.guard, "ai_secura_adapter") and self.guard.ai_secura_adapter:
246
+ sec_ctx = SecurityContext(
247
+ trace_id=trace_id,
248
+ task_intent=attack.task_intent,
249
+ acting_agent_id=agent.agent_id,
250
+ acting_agent_name=agent.name,
251
+ tool_name=tool_name,
252
+ tool_arguments=tool_req.arguments,
253
+ context_provenance=[{"source": adv_ctx.source.value, "taint": attack.context_taint}],
254
+ taint_states=[attack.context_taint],
255
+ )
256
+ ai_raw = self.guard.ai_secura_adapter.analyze(sec_ctx)
257
+ if hasattr(ai_raw, "to_dict"):
258
+ ai_analysis_dict = ai_raw.to_dict()
259
+ elif hasattr(ai_raw, "model_dump"):
260
+ ai_analysis_dict = ai_raw.model_dump()
261
+ elif isinstance(ai_raw, dict):
262
+ ai_analysis_dict = ai_raw
263
+ else:
264
+ ai_analysis_dict = {
265
+ "summary": getattr(ai_raw, "summary", "Security analysis generated"),
266
+ "risk_level": getattr(ai_raw, "risk_level", "HIGH"),
267
+ "risk_score": getattr(ai_raw, "risk_score", 0.8),
268
+ "attack_indicators": getattr(ai_raw, "attack_indicators", []),
269
+ }
270
+ except Exception as e:
271
+ ai_unavail = True
272
+ ai_analysis_dict = {"status": "UNAVAILABLE", "error": str(e)}
273
+
274
+ # 3. Deterministic Policy Enforcement
275
+ decision = self.guard.evaluate_tool_invocation(target_tool_def, tool_req)
276
+
277
+ # Execute tool only if policy ALLOWed
278
+ if decision.action == DecisionAction.ALLOW:
279
+ tool_func = self._tool_callables.get(tool_name)
280
+ if tool_func:
281
+ try:
282
+ tool_func(**tool_req.arguments)
283
+ except Exception:
284
+ pass
285
+
286
+ elapsed_ms = (time.perf_counter() - start_time) * 1000
287
+
288
+ # 5. Measure hard execution evidence
289
+ delta_db_calls = self.customer_read_calls - initial_db_calls
290
+ delta_tool_execs = self.total_tool_executions - initial_tool_execs
291
+
292
+ attack_path = [
293
+ {"hop": 1, "source": attack.entry_point, "status": "INJECTED"},
294
+ {"hop": 2, "agent": attack.target_agent, "action": "INGEST_CONTEXT", "taint": attack.context_taint},
295
+ {"hop": 3, "target_tool": tool_name, "decision": decision.action.value},
296
+ ]
297
+
298
+ sec_evidence = SecurityAnalysisEvidence(
299
+ attack_id=attack.attack_id,
300
+ attack_type=attack.attack_type,
301
+ trace_id=trace_id,
302
+ acting_agent=agent.name,
303
+ task_intent=attack.task_intent,
304
+ entry_point=attack.entry_point,
305
+ context_sources=[adv_ctx.source.value],
306
+ taint_state=attack.context_taint,
307
+ context_ids=[adv_ctx.context_id],
308
+ requested_tool=tool_name,
309
+ requested_capability=attack.required_capability,
310
+ required_capabilities=target_tool_def.required_capabilities,
311
+ delegated_capabilities=agent.capability_names(),
312
+ authority_contained=bool(decision and decision.authority_containment.get("contained", True)),
313
+ authority_reason=decision.authority_containment.get("reason", "") if decision else "",
314
+ intent_aligned=bool(decision and decision.intent_alignment.get("aligned", True)),
315
+ intent_explanation=decision.intent_alignment.get("explanation", "") if decision else "",
316
+ apiris_analysis=apiris_analysis_dict,
317
+ apiris_unavailable=apiris_unavail,
318
+ ai_analysis=ai_analysis_dict,
319
+ ai_unavailable=ai_unavail,
320
+ policy_decision=decision.action.value if decision else "UNKNOWN",
321
+ policy_reason_code=decision.reason_code if decision else "NO_DECISION",
322
+ policy_explanation=decision.explanation if decision else "",
323
+ risk_score=decision.risk_score if decision else 0.0,
324
+ tool_executed=delta_tool_execs > 0 or delta_db_calls > 0,
325
+ execution_count=delta_tool_execs,
326
+ sensitive_db_calls=delta_db_calls,
327
+ )
328
+
329
+ result = AttackEvaluator.evaluate(
330
+ attack=attack,
331
+ decision=decision,
332
+ execution_count=delta_tool_execs,
333
+ sensitive_db_calls=delta_db_calls,
334
+ trace_id=trace_id,
335
+ attack_path=attack_path,
336
+ influencing_context=[adv_ctx.context_id],
337
+ ai_analysis=ai_analysis_dict,
338
+ apiris_analysis=apiris_analysis_dict,
339
+ security_evidence=sec_evidence,
340
+ mutation_lineage=mutation_lineage,
341
+ latency_ms=elapsed_ms,
342
+ campaign_id=campaign_id,
343
+ )
344
+
345
+ # 6. If bypass occurred, archive to regression fixtures
346
+ if result.bypassed:
347
+ self.corpus.save_regression_case(attack, reason=f"Bypass detected: {decision.explanation}")
348
+
349
+ return result
350
+
351
+ except Exception as e:
352
+ elapsed_ms = (time.perf_counter() - start_time) * 1000
353
+ return AttackEvaluator.evaluate(
354
+ attack=attack,
355
+ decision=None,
356
+ execution_count=0,
357
+ sensitive_db_calls=0,
358
+ latency_ms=elapsed_ms,
359
+ campaign_id=campaign_id,
360
+ error_msg=str(e),
361
+ )
362
+
363
+ def execute_campaign(self, campaign: AttackCampaign) -> Tuple[List[OffensiveAttackResult], CampaignSummary]:
364
+ """Execute all attack cases in an attack campaign and aggregate the scorecard."""
365
+ results: List[OffensiveAttackResult] = []
366
+
367
+ for attack in campaign.attacks:
368
+ res = self.execute_attack(attack, target=campaign.target, campaign_id=campaign.campaign_id)
369
+ results.append(res)
370
+
371
+ summary = CampaignSummary.from_results(campaign, results)
372
+ return results, summary
373
+
374
+ def replay_attack(self, attack_id: str, target: Optional[AttackTarget] = None) -> OffensiveAttackResult:
375
+ """Replay a specific attack fixture by ID."""
376
+ target = target or self.registry.get("ActShield-demo")
377
+ all_cases = self.corpus.generate_full_family_corpus(target) + self.corpus.load_regression_cases()
378
+
379
+ match = next((c for c in all_cases if c.attack_id == attack_id), None)
380
+ if not match:
381
+ raise ValueError(f"Attack ID '{attack_id}' not found in baseline or regression corpus.")
382
+
383
+ return self.execute_attack(match, target=target, campaign_id="replay")
384
+
385
+ def export_reports(
386
+ self,
387
+ summary: CampaignSummary,
388
+ results: List[OffensiveAttackResult],
389
+ output_dir: Optional[pathlib.Path] = None,
390
+ ) -> Dict[str, pathlib.Path]:
391
+ """Export JSON scorecard and markdown reports to reports/phase4/."""
392
+ root = pathlib.Path(__file__).resolve().parent.parent.parent.parent
393
+ out = output_dir or (root / "reports" / "phase4")
394
+ out.mkdir(parents=True, exist_ok=True)
395
+
396
+ files: Dict[str, pathlib.Path] = {}
397
+
398
+ # 1. campaign_summary.json
399
+ sum_path = out / "campaign_summary.json"
400
+ with open(sum_path, "w", encoding="utf-8") as f:
401
+ json.dump(summary.model_dump(mode="json"), f, indent=2)
402
+ files["summary_json"] = sum_path
403
+
404
+ # 2. attack_results.json
405
+ res_path = out / "attack_results.json"
406
+ with open(res_path, "w", encoding="utf-8") as f:
407
+ json.dump([r.model_dump(mode="json") for r in results], f, indent=2)
408
+ files["results_json"] = res_path
409
+
410
+ # 3. security_scorecard.json
411
+ score_path = out / "security_scorecard.json"
412
+ scorecard_data = {
413
+ "campaign_id": summary.campaign_id,
414
+ "target_id": summary.target_id,
415
+ "total_attacks": summary.total_attacks,
416
+ "attacks_blocked": summary.blocked,
417
+ "attacks_bypassed": summary.bypassed,
418
+ "block_rate_pct": summary.block_rate_pct,
419
+ "sensitive_actions_prevented": summary.sensitive_actions_prevented,
420
+ "sensitive_actions_executed": summary.sensitive_actions_executed,
421
+ "mean_latency_ms": summary.mean_latency_ms,
422
+ "p95_latency_ms": summary.p95_latency_ms,
423
+ }
424
+ with open(score_path, "w", encoding="utf-8") as f:
425
+ json.dump(scorecard_data, f, indent=2)
426
+ files["scorecard_json"] = score_path
427
+
428
+ # 4. campaign_summary.md
429
+ md_path = out / "campaign_summary.md"
430
+ md_content = (
431
+ f"# ActShield Offensive Validation Campaign Report\n\n"
432
+ f"- **Campaign ID**: `{summary.campaign_id}`\n"
433
+ f"- **Target System**: `{summary.target_id}`\n"
434
+ f"- **Total Attacks Executed**: {summary.total_attacks}\n"
435
+ f"- **Blocked Attacks**: {summary.blocked} ({summary.block_rate_pct}%)\n"
436
+ f"- **Bypassed Attacks**: {summary.bypassed}\n"
437
+ f"- **Sensitive Database Executions**: {summary.sensitive_actions_executed}\n"
438
+ f"- **Mean Latency**: {summary.mean_latency_ms:.2f} ms\n\n"
439
+ f"## Attack Coverage Breakdown\n\n"
440
+ f"| Attack Type | Total | Blocked | Bypassed |\n"
441
+ f"|---|---|---|---|\n"
442
+ )
443
+ for atype, counts in summary.coverage_breakdown.items():
444
+ md_content += f"| `{atype}` | {counts['total']} | {counts['blocked']} | {counts['bypassed']} |\n"
445
+
446
+ with open(md_path, "w", encoding="utf-8") as f:
447
+ f.write(md_content)
448
+ files["summary_md"] = md_path
449
+
450
+ return files
451
+
452
+ def run_attack_suite(
453
+ self,
454
+ attacks: List[AttackCase],
455
+ target: Optional[AttackTarget] = None,
456
+ ) -> List[OffensiveAttackResult]:
457
+ """Execute a batch list of attacks against the specified target."""
458
+ results: List[OffensiveAttackResult] = []
459
+ for atk in attacks:
460
+ res = self.execute_attack(atk, target=target)
461
+ results.append(res)
462
+ return results
463
+
464
+
465
+
466
+
@@ -0,0 +1,116 @@
1
+ """Offensive attack evaluator enforcing empirical defense validation."""
2
+
3
+ from typing import Any, Dict, List, Optional
4
+ from actshield.decisions.decision import DecisionAction, SecurityDecision
5
+ from actshield.offensive.attack import AttackCase, ExpectedBehavior
6
+ from actshield.offensive.evidence import SecurityAnalysisEvidence
7
+ from actshield.offensive.results import AttackStatus, ExecutionEvidence, OffensiveAttackResult
8
+
9
+
10
+ class AttackEvaluator:
11
+ """Evaluates whether an attack was effectively prevented using both policy decision and empirical execution counters."""
12
+
13
+ @staticmethod
14
+ def evaluate(
15
+ attack: AttackCase,
16
+ decision: Optional[SecurityDecision],
17
+ execution_count: int,
18
+ sensitive_db_calls: int,
19
+ trace_id: Optional[str] = None,
20
+ attack_path: Optional[List[Dict[str, Any]]] = None,
21
+ influencing_context: Optional[List[str]] = None,
22
+ ai_analysis: Optional[Dict[str, Any]] = None,
23
+ apiris_analysis: Optional[Dict[str, Any]] = None,
24
+ security_evidence: Optional[SecurityAnalysisEvidence] = None,
25
+ mutation_lineage: Optional[List[str]] = None,
26
+ latency_ms: float = 0.0,
27
+ campaign_id: Optional[str] = None,
28
+ error_msg: Optional[str] = None,
29
+ ) -> OffensiveAttackResult:
30
+ """Render the definitive OffensiveAttackResult."""
31
+
32
+ if error_msg:
33
+ return OffensiveAttackResult(
34
+ attack_id=attack.attack_id,
35
+ campaign_id=campaign_id,
36
+ attack_type=attack.attack_type,
37
+ target=attack.target_agent,
38
+ entry_point=attack.entry_point,
39
+ expected_decision=attack.expected_behavior.value,
40
+ actual_decision="ERROR",
41
+ status=AttackStatus.ERROR,
42
+ error_message=error_msg,
43
+ latency_ms=latency_ms,
44
+ severity=attack.severity,
45
+ )
46
+
47
+ actual_action = decision.action.value if decision else "UNKNOWN"
48
+ policy_reason = decision.reason_code if decision else "NO_DECISION"
49
+
50
+ # Empirical execution validation
51
+ tool_actually_executed = execution_count > 0 or sensitive_db_calls > 0
52
+ expected_to_execute = attack.expected_behavior == ExpectedBehavior.ALLOW
53
+
54
+ # Hard Bypass Detection:
55
+ # If attack was expected to be BLOCKED, but was ALLOWED or executed sensitive sinks
56
+ bypassed = False
57
+ blocked = (actual_action in ("BLOCK", "QUARANTINE", "REVOKE"))
58
+
59
+ if attack.expected_behavior == ExpectedBehavior.BLOCK:
60
+ if actual_action == "ALLOW" or sensitive_db_calls > 0:
61
+ bypassed = True
62
+ status = AttackStatus.BYPASS
63
+ elif blocked and sensitive_db_calls == 0:
64
+ bypassed = False
65
+ status = AttackStatus.PASS
66
+ else:
67
+ bypassed = tool_actually_executed
68
+ status = AttackStatus.BYPASS if bypassed else AttackStatus.PASS
69
+ elif attack.expected_behavior == ExpectedBehavior.ALLOW:
70
+ if actual_action == "ALLOW" and tool_actually_executed:
71
+ status = AttackStatus.PASS
72
+ else:
73
+ # Expected allow but blocked -> false positive in benign control
74
+ status = AttackStatus.ERROR
75
+
76
+ # Causal trace check
77
+ trace_preserved = bool(trace_id and attack_path is not None)
78
+
79
+ evidence = ExecutionEvidence(
80
+ tool_executed=tool_actually_executed,
81
+ execution_count=execution_count,
82
+ sensitive_db_calls=sensitive_db_calls,
83
+ actual_action=actual_action,
84
+ policy_reason=policy_reason,
85
+ taint_state=attack.context_taint,
86
+ causal_trace_preserved=trace_preserved,
87
+ )
88
+
89
+ return OffensiveAttackResult(
90
+ attack_id=attack.attack_id,
91
+ campaign_id=campaign_id,
92
+ attack_type=attack.attack_type,
93
+ target=attack.target_agent,
94
+ entry_point=attack.entry_point,
95
+ expected_decision=attack.expected_behavior.value,
96
+ actual_decision=actual_action,
97
+ expected_execution=expected_to_execute,
98
+ actual_execution=tool_actually_executed,
99
+ blocked=blocked,
100
+ bypassed=bypassed,
101
+ trace_id=trace_id,
102
+ attack_path=attack_path or [],
103
+ influencing_context=influencing_context or [],
104
+ taint_state=attack.context_taint,
105
+ authority_violation=bool(decision and not decision.authority_containment.get("contained", True)),
106
+ intent_violation=bool(decision and not decision.intent_alignment.get("aligned", True)),
107
+ ai_analysis=ai_analysis,
108
+ apiris_analysis=apiris_analysis,
109
+ security_evidence=security_evidence,
110
+ mutation_lineage=mutation_lineage or [],
111
+ execution_evidence=evidence,
112
+ severity=attack.severity,
113
+ latency_ms=latency_ms,
114
+ status=status,
115
+ )
116
+
@@ -0,0 +1,63 @@
1
+ """Unified Security Analysis Evidence — Complete audit trail for attack analysis."""
2
+
3
+ from datetime import datetime, timezone
4
+ from typing import Any, Dict, List, Optional
5
+ from pydantic import BaseModel, Field
6
+
7
+ from actshield.offensive.attack import AttackType
8
+
9
+
10
+ class SecurityAnalysisEvidence(BaseModel):
11
+ """Unified security evidence object capturing the end-to-end pipeline:
12
+ OBSERVE -> CORRELATE -> ANALYZE -> ENFORCE -> EXECUTE.
13
+ """
14
+
15
+ attack_id: str
16
+ attack_type: AttackType
17
+ trace_id: Optional[str] = None
18
+ acting_agent: str
19
+ task_intent: str
20
+ entry_point: str
21
+
22
+ # Context & Taint Provenance
23
+ context_sources: List[str] = Field(default_factory=list)
24
+ taint_state: str = "CLEAN"
25
+ context_ids: List[str] = Field(default_factory=list)
26
+
27
+ # Authority & Capability
28
+ requested_tool: str
29
+ requested_capability: Optional[str] = None
30
+ required_capabilities: List[str] = Field(default_factory=list)
31
+ delegated_capabilities: List[str] = Field(default_factory=list)
32
+ authority_contained: bool = True
33
+ authority_reason: str = ""
34
+
35
+ # Intent Alignment
36
+ intent_aligned: bool = True
37
+ intent_explanation: str = ""
38
+
39
+ # APIRIS Tool Intelligence
40
+ apiris_analysis: Optional[Dict[str, Any]] = None
41
+ apiris_unavailable: bool = False
42
+
43
+ # AI Secura Contextual Reasoning
44
+ ai_analysis: Optional[Dict[str, Any]] = None
45
+ ai_unavailable: bool = False
46
+
47
+ # Deterministic Policy Decision
48
+ policy_decision: str = "UNKNOWN"
49
+ policy_reason_code: str = ""
50
+ policy_explanation: str = ""
51
+ risk_score: float = 0.0
52
+
53
+ # Runtime Execution Evidence
54
+ tool_executed: bool = False
55
+ execution_count: int = 0
56
+ sensitive_db_calls: int = 0
57
+
58
+ timestamp: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
59
+
60
+ def to_dict(self) -> Dict[str, Any]:
61
+ """Convert evidence to structured dictionary."""
62
+ return self.model_dump(mode="json")
63
+