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,254 @@
1
+ """ActShield Phase 3.5 — 50-Case Evaluation Suite Runner.
2
+
3
+ Evaluates AI Secura (local Ollama qwen2.5:7b or offline simulated engine)
4
+ across 50 standardized security cases across 5 categories:
5
+ 1. Prompt Injection & Jailbreak (10 cases)
6
+ 2. MCP & Multi-Agent Security (10 cases)
7
+ 3. RAG & Knowledge Base Security (10 cases)
8
+ 4. Authority & Intent Drift (10 cases)
9
+ 5. Benign Operations & False Positive Resistance (10 cases)
10
+
11
+ Calculates:
12
+ - JSON Schema Validity Rate (%)
13
+ - Threat Detection Accuracy (%)
14
+ - Intent Alignment Accuracy (%)
15
+ - Recommendation Accuracy (%)
16
+ - False Positive Rate & False Negative Rate
17
+ - Latency metrics (mean, p50, p95)
18
+ """
19
+
20
+ import json
21
+ import os
22
+ import pathlib
23
+ import sys
24
+ import time
25
+ from typing import Dict, List, Any, Optional
26
+
27
+ from actshield.llm_config import LLMConfig
28
+ from actshield.integrations.ai_secura import SecurityContext
29
+ from actshield.integrations.ai_secura_analysis import (
30
+ AISecuraAnalysis,
31
+ ThreatSeverity,
32
+ IntentAlignment,
33
+ AIRecommendation,
34
+ )
35
+ from actshield.integrations.ollama_adapter import OllamaAISecuraAdapter
36
+
37
+
38
+ CASES_DIR = pathlib.Path(__file__).parent / "cases"
39
+
40
+
41
+ class EvaluationCase:
42
+ def __init__(self, data: Dict[str, Any]):
43
+ self.id: str = data["id"]
44
+ self.category: str = data["category"]
45
+ self.name: str = data["name"]
46
+ self.description: str = data["description"]
47
+ self.input: Dict[str, Any] = data["input"]
48
+ self.expected: Dict[str, Any] = data["expected"]
49
+
50
+ def to_security_context(self) -> SecurityContext:
51
+ inp = self.input
52
+ return SecurityContext(
53
+ trace_id=f"eval_{self.id}",
54
+ task_intent=inp.get("task_intent", "Unknown task"),
55
+ acting_agent_name=inp.get("acting_agent", "TestAgent"),
56
+ acting_agent_trust=inp.get("trust_level", "medium"),
57
+ context_provenance=inp.get("context_provenance", []),
58
+ taint_states=inp.get("taint_states", ["CLEAN"]),
59
+ tool_name=inp.get("tool_name"),
60
+ tool_arguments=inp.get("tool_arguments", {}),
61
+ target_resource_sensitivity=inp.get("resource_sensitivity", "LOW"),
62
+ )
63
+
64
+
65
+ class CaseResult:
66
+ def __init__(
67
+ self,
68
+ case: EvaluationCase,
69
+ analysis: AISecuraAnalysis,
70
+ latency_ms: float,
71
+ json_valid: bool = True,
72
+ ):
73
+ self.case = case
74
+ self.analysis = analysis
75
+ self.latency_ms = latency_ms
76
+ self.json_valid = json_valid
77
+
78
+ # Metric evaluation
79
+ self.is_attack = case.category != "benign_false_positive"
80
+ self.rec = analysis.ai_recommendation.value
81
+ self.exp_rec = case.expected.get("expected_recommendation", "BLOCK")
82
+
83
+ # Recommendation match
84
+ self.rec_match = (
85
+ (self.rec == self.exp_rec)
86
+ or (not self.is_attack and self.rec == "ALLOW")
87
+ or (self.is_attack and self.rec in ("BLOCK", "HITL_REQUIRED"))
88
+ )
89
+
90
+ # False positive: benign case flagged as BLOCK/ATTACK
91
+ self.false_positive = (not self.is_attack) and (self.rec == "BLOCK")
92
+
93
+ # False negative: attack case flagged as ALLOW
94
+ self.false_negative = self.is_attack and (self.rec == "ALLOW")
95
+
96
+ # Intent match
97
+ exp_intent = case.expected.get("intent_alignment")
98
+ self.intent_match = (
99
+ analysis.intent_alignment.value == exp_intent
100
+ if exp_intent else True
101
+ )
102
+
103
+
104
+ class EvaluationSuite:
105
+ def __init__(self, adapter: Optional[OllamaAISecuraAdapter] = None):
106
+ if adapter is None:
107
+ config = LLMConfig.from_env()
108
+ self.adapter = OllamaAISecuraAdapter(config)
109
+ else:
110
+ self.adapter = adapter
111
+ self.cases: List[EvaluationCase] = []
112
+ self._load_cases()
113
+
114
+ def _load_cases(self):
115
+ case_files = sorted(CASES_DIR.glob("eval_*.json"))
116
+ for f in case_files:
117
+ try:
118
+ with open(f, "r", encoding="utf-8") as fp:
119
+ data = json.load(fp)
120
+ self.cases.append(EvaluationCase(data))
121
+ except Exception as e:
122
+ print(f"Warning: Failed to load case {f}: {e}")
123
+
124
+ def run(self, verbose: bool = True) -> Dict[str, Any]:
125
+ results: List[CaseResult] = []
126
+ health = self.adapter.health_check()
127
+ live_mode = health.get("ollama_reachable", False)
128
+
129
+ if verbose:
130
+ print("=" * 78)
131
+ print(f" ActShield PHASE 3.5 — EVALUATION SUITE (50 CASES)")
132
+ print(f" Execution Mode : {'LIVE OLLAMA (' + self.adapter.config.model + ')' if live_mode else 'OFFLINE DETERMINISTIC HEURISTIC'}")
133
+ print(f" Loaded Cases : {len(self.cases)}")
134
+ print("=" * 78)
135
+
136
+ for idx, case in enumerate(self.cases, 1):
137
+ ctx = case.to_security_context()
138
+ t0 = time.perf_counter()
139
+ analysis = self.adapter.analyze(ctx)
140
+ elapsed_ms = (time.perf_counter() - t0) * 1000
141
+
142
+ res = CaseResult(case, analysis, elapsed_ms, json_valid=not analysis.ai_unavailable)
143
+ results.append(res)
144
+
145
+ if verbose:
146
+ status_char = "[PASS]" if res.rec_match else "[FAIL]"
147
+ fp_fn_str = " [FP]" if res.false_positive else (" [FN]" if res.false_negative else "")
148
+ print(
149
+ f"[{idx:02d}/50] {status_char:<6} {case.category[:15]:<15} | "
150
+ f"{case.name[:28]:<28} | Rec: {res.rec:<6} (Exp: {res.exp_rec:<6}) | "
151
+ f"{res.latency_ms:6.1f}ms{fp_fn_str}",
152
+ flush=True,
153
+ )
154
+
155
+ summary = self._compute_summary(results, live_mode)
156
+ if verbose:
157
+ self._print_summary(summary)
158
+
159
+ return summary
160
+
161
+ def _compute_summary(self, results: List[CaseResult], live_mode: bool) -> Dict[str, Any]:
162
+ total = len(results)
163
+ if total == 0:
164
+ return {"total": 0}
165
+
166
+ rec_matches = sum(1 for r in results if r.rec_match)
167
+ intent_matches = sum(1 for r in results if r.intent_match)
168
+ false_positives = sum(1 for r in results if r.false_positive)
169
+ false_negatives = sum(1 for r in results if r.false_negative)
170
+ json_valids = sum(1 for r in results if r.json_valid)
171
+ latencies = [r.latency_ms for r in results]
172
+ latencies.sort()
173
+
174
+ mean_lat = sum(latencies) / len(latencies)
175
+ p50_lat = latencies[len(latencies) // 2]
176
+ p95_idx = int(len(latencies) * 0.95)
177
+ p95_lat = latencies[min(p95_idx, len(latencies) - 1)]
178
+
179
+ categories: Dict[str, Dict[str, int]] = {}
180
+ for r in results:
181
+ cat = r.case.category
182
+ if cat not in categories:
183
+ categories[cat] = {"total": 0, "passed": 0, "fp": 0, "fn": 0}
184
+ categories[cat]["total"] += 1
185
+ if r.rec_match:
186
+ categories[cat]["passed"] += 1
187
+ if r.false_positive:
188
+ categories[cat]["fp"] += 1
189
+ if r.false_negative:
190
+ categories[cat]["fn"] += 1
191
+
192
+ return {
193
+ "live_mode": live_mode,
194
+ "total_cases": total,
195
+ "recommendation_accuracy_pct": round((rec_matches / total) * 100, 1),
196
+ "intent_accuracy_pct": round((intent_matches / total) * 100, 1),
197
+ "json_validity_pct": round((json_valids / total) * 100, 1),
198
+ "false_positives": false_positives,
199
+ "false_negatives": false_negatives,
200
+ "mean_latency_ms": round(mean_lat, 2),
201
+ "p50_latency_ms": round(p50_lat, 2),
202
+ "p95_latency_ms": round(p95_lat, 2),
203
+ "categories": categories,
204
+ }
205
+
206
+ def _print_summary(self, s: Dict[str, Any]):
207
+ print("\n" + "=" * 78)
208
+ print(" EVALUATION SUMMARY RESULTS")
209
+ print("=" * 78)
210
+ print(f" Mode : {'LIVE OLLAMA' if s['live_mode'] else 'OFFLINE SIMULATION'}")
211
+ print(f" Total Test Cases : {s['total_cases']}")
212
+ print(f" Recommendation Match : {s['recommendation_accuracy_pct']}%")
213
+ print(f" Intent Match : {s['intent_accuracy_pct']}%")
214
+ print(f" JSON Validity Rate : {s['json_validity_pct']}%")
215
+ print(f" False Positives (FP) : {s['false_positives']}")
216
+ print(f" False Negatives (FN) : {s['false_negatives']}")
217
+ print(f" Latency (Mean / p50 / p95): {s['mean_latency_ms']:.1f}ms / {s['p50_latency_ms']:.1f}ms / {s['p95_latency_ms']:.1f}ms")
218
+ print("-" * 78)
219
+ print(" Breakdown by Category:")
220
+ for cat, stats in s.get("categories", {}).items():
221
+ acc = round((stats["passed"] / stats["total"]) * 100, 1) if stats["total"] > 0 else 0
222
+ print(f" - {cat:<24}: {stats['passed']}/{stats['total']} ({acc}%) | FP: {stats['fp']} | FN: {stats['fn']}")
223
+ print("=" * 78 + "\n")
224
+
225
+
226
+ def main():
227
+ import argparse
228
+ parser = argparse.ArgumentParser(description="ActShield Phase 3.5 Evaluation Suite")
229
+ parser.add_argument("--mode", choices=["auto", "live", "offline"], default="auto", help="Execution mode")
230
+ parser.add_argument("--limit", type=int, default=None, help="Limit number of test cases")
231
+ parser.add_argument("--category", type=str, default=None, help="Filter by category name")
232
+ args = parser.parse_args()
233
+
234
+ cfg = LLMConfig.from_env()
235
+ if args.mode == "offline":
236
+ cfg = cfg.model_copy(update={"provider": "local"})
237
+ elif args.mode == "live":
238
+ cfg = cfg.model_copy(update={"provider": "ollama"})
239
+
240
+ adapter = OllamaAISecuraAdapter(cfg)
241
+ suite = EvaluationSuite(adapter)
242
+
243
+ if args.category:
244
+ suite.cases = [c for c in suite.cases if args.category.lower() in c.category.lower()]
245
+ if args.limit:
246
+ suite.cases = suite.cases[:args.limit]
247
+
248
+ suite.run(verbose=True)
249
+
250
+
251
+ if __name__ == "__main__":
252
+ main()
253
+
254
+
@@ -0,0 +1,53 @@
1
+ """ActShield Phase 6.5 — Forensic Intelligence Layer.
2
+
3
+ Turns existing runtime security events into a coherent forensic intelligence
4
+ layer answering: WHAT happened, WHY, WHO caused it, WHO had authority,
5
+ WHAT was accessed (or attempted), and WHAT could be reached.
6
+
7
+ Read-only with respect to security authority. PolicyEvaluator remains the
8
+ enforcement authority. This layer only observes and answers queries.
9
+ """
10
+
11
+ from actshield.forensics.models import (
12
+ AgentAccessProfile,
13
+ ResourceAccessProfile,
14
+ AuthorityRelationship,
15
+ AccessRelationship,
16
+ DelegationRelationship,
17
+ AccessAttempt,
18
+ ActualAccess,
19
+ AccessSnapshot,
20
+ AccessDiff,
21
+ ForensicExplanation,
22
+ AttackForensicReport,
23
+ AccessDecision,
24
+ ForensicIncident,
25
+ IncidentType,
26
+ IncidentSeverity,
27
+ )
28
+ from actshield.forensics.service import ForensicService
29
+ from actshield.forensics.queries import ForensicQueryEngine
30
+
31
+ __all__ = [
32
+ # Models
33
+ "AgentAccessProfile",
34
+ "ResourceAccessProfile",
35
+ "AuthorityRelationship",
36
+ "AccessRelationship",
37
+ "DelegationRelationship",
38
+ "AccessAttempt",
39
+ "ActualAccess",
40
+ "AccessSnapshot",
41
+ "AccessDiff",
42
+ "ForensicExplanation",
43
+ "AttackForensicReport",
44
+ "AccessDecision",
45
+ "ForensicIncident",
46
+ "IncidentType",
47
+ "IncidentSeverity",
48
+ # Services
49
+ "ForensicService",
50
+ "ForensicQueryEngine",
51
+ ]
52
+
53
+
@@ -0,0 +1,216 @@
1
+ """Access graph — resource-centric view of who accessed (or attempted) what.
2
+
3
+ Tracks:
4
+ - Which agents can access which resources (effective permission)
5
+ - Which agents attempted access
6
+ - Which agents actually accessed (executed)
7
+ - Which attempts were blocked and why
8
+
9
+ Read-only with respect to security authority.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ from typing import Dict, List, Optional, Set
15
+ from actshield.forensics.models import (
16
+ AccessAttempt,
17
+ AccessDecision,
18
+ AccessRelationship,
19
+ ActualAccess,
20
+ ResourceAccessProfile,
21
+ )
22
+ from actshield.tools.tool import ToolDefinition
23
+
24
+
25
+ class ResourceNode:
26
+ """In-memory node representing one protected resource in the access graph."""
27
+
28
+ def __init__(
29
+ self,
30
+ resource_id: str,
31
+ resource_name: str,
32
+ resource_type: str = "api",
33
+ sensitivity: str = "MEDIUM",
34
+ ) -> None:
35
+ self.resource_id = resource_id
36
+ self.resource_name = resource_name
37
+ self.resource_type = resource_type
38
+ self.sensitivity = sensitivity
39
+ self.exposing_tools: List[str] = []
40
+ self.authorized_agents: List[str] = []
41
+ self.attempts: List[AccessAttempt] = []
42
+ self.actual_accesses: List[ActualAccess] = []
43
+
44
+ def add_exposing_tool(self, tool_name: str) -> None:
45
+ if tool_name not in self.exposing_tools:
46
+ self.exposing_tools.append(tool_name)
47
+
48
+ def add_authorized_agent(self, agent_id: str) -> None:
49
+ if agent_id not in self.authorized_agents:
50
+ self.authorized_agents.append(agent_id)
51
+
52
+ def record_attempt(self, attempt: AccessAttempt) -> None:
53
+ self.attempts.append(attempt)
54
+
55
+ def record_actual_access(self, access: ActualAccess) -> None:
56
+ self.actual_accesses.append(access)
57
+
58
+ def to_profile(self) -> ResourceAccessProfile:
59
+ attempted = list({a.agent_id for a in self.attempts})
60
+ actual = list({a.agent_id for a in self.actual_accesses})
61
+ blocked = list({a.agent_id for a in self.attempts if a.was_blocked})
62
+ return ResourceAccessProfile(
63
+ resource_id=self.resource_id,
64
+ resource_name=self.resource_name,
65
+ resource_type=self.resource_type,
66
+ sensitivity=self.sensitivity,
67
+ exposing_tools=list(self.exposing_tools),
68
+ authorized_agents=list(self.authorized_agents),
69
+ attempted_agents=attempted,
70
+ actual_access_agents=actual,
71
+ blocked_agents=blocked,
72
+ access_history=list(self.attempts),
73
+ total_attempts=len(self.attempts),
74
+ total_blocked=sum(1 for a in self.attempts if a.was_blocked),
75
+ total_executions=sum(a.execution_count for a in self.actual_accesses),
76
+ )
77
+
78
+
79
+ class AccessGraph:
80
+ """Resource-centric access graph.
81
+
82
+ Answers:
83
+ - Which agents can access resource X?
84
+ - Which tools expose resource X?
85
+ - Which agents attempted access to X?
86
+ - Which agents actually accessed X?
87
+ - Which attempts were blocked?
88
+ """
89
+
90
+ def __init__(self) -> None:
91
+ # resource_name → ResourceNode
92
+ self._resources: Dict[str, ResourceNode] = {}
93
+ # agent_id → list of AccessRelationship
94
+ self._agent_rels: Dict[str, List[AccessRelationship]] = {}
95
+ # All recorded attempts
96
+ self._attempts: List[AccessAttempt] = []
97
+ # All confirmed actual accesses
98
+ self._actual: List[ActualAccess] = []
99
+
100
+ # ------------------------------------------------------------------
101
+ # Registration
102
+ # ------------------------------------------------------------------
103
+
104
+ def register_resource(
105
+ self,
106
+ resource_id: str,
107
+ resource_name: str,
108
+ resource_type: str = "api",
109
+ sensitivity: str = "MEDIUM",
110
+ ) -> ResourceNode:
111
+ if resource_name not in self._resources:
112
+ self._resources[resource_name] = ResourceNode(
113
+ resource_id=resource_id,
114
+ resource_name=resource_name,
115
+ resource_type=resource_type,
116
+ sensitivity=sensitivity,
117
+ )
118
+ return self._resources[resource_name]
119
+
120
+ def register_tool(self, tool_def: ToolDefinition) -> None:
121
+ """Register all resources exposed by a tool definition."""
122
+ for resource in tool_def.target_resources:
123
+ node = self.register_resource(
124
+ resource_id=resource.resource_id,
125
+ resource_name=resource.name,
126
+ resource_type=resource.resource_type,
127
+ sensitivity=resource.sensitivity.value,
128
+ )
129
+ node.add_exposing_tool(tool_def.name)
130
+
131
+ def declare_authorized_agent(self, resource_name: str, agent_id: str) -> None:
132
+ """Mark an agent as authorized to access a resource (based on capability check)."""
133
+ if resource_name in self._resources:
134
+ self._resources[resource_name].add_authorized_agent(agent_id)
135
+
136
+ # ------------------------------------------------------------------
137
+ # Recording
138
+ # ------------------------------------------------------------------
139
+
140
+ def record_attempt(self, attempt: AccessAttempt) -> None:
141
+ """Record an access attempt (may be blocked or allowed)."""
142
+ self._attempts.append(attempt)
143
+ resource = attempt.resource_name or attempt.tool_name
144
+ if resource in self._resources:
145
+ self._resources[resource].record_attempt(attempt)
146
+
147
+ rel = AccessRelationship(
148
+ agent_id=attempt.agent_id,
149
+ tool_name=attempt.tool_name,
150
+ resource_name=attempt.resource_name,
151
+ decision=attempt.decision,
152
+ executed=attempt.executed,
153
+ capability_required=attempt.capability_requested,
154
+ sensitivity=attempt.resource_sensitivity,
155
+ trace_id=attempt.trace_id,
156
+ task_id=attempt.task_id,
157
+ taint_state=attempt.taint_state,
158
+ reason_code=attempt.policy_reason,
159
+ )
160
+ self._agent_rels.setdefault(attempt.agent_id, []).append(rel)
161
+
162
+ def record_actual_access(self, access: ActualAccess) -> None:
163
+ """Record confirmed execution of a tool/resource access."""
164
+ self._actual.append(access)
165
+ resource = access.resource_name or access.tool_name
166
+ if resource in self._resources:
167
+ self._resources[resource].record_actual_access(access)
168
+
169
+ # ------------------------------------------------------------------
170
+ # Queries
171
+ # ------------------------------------------------------------------
172
+
173
+ def get_resource_profile(
174
+ self, resource_name: str
175
+ ) -> Optional[ResourceAccessProfile]:
176
+ node = self._resources.get(resource_name)
177
+ return node.to_profile() if node else None
178
+
179
+ def get_all_resources(self) -> List[str]:
180
+ return sorted(self._resources.keys())
181
+
182
+ def get_agent_attempts(self, agent_id: str) -> List[AccessAttempt]:
183
+ return [a for a in self._attempts if a.agent_id == agent_id]
184
+
185
+ def get_agent_actual_access(self, agent_id: str) -> List[ActualAccess]:
186
+ return [a for a in self._actual if a.agent_id == agent_id]
187
+
188
+ def get_resource_history(self, resource_name: str) -> List[AccessAttempt]:
189
+ node = self._resources.get(resource_name)
190
+ return list(node.attempts) if node else []
191
+
192
+ def get_reachable_agents(self, resource_name: str) -> List[str]:
193
+ node = self._resources.get(resource_name)
194
+ return list(node.authorized_agents) if node else []
195
+
196
+ def get_all_attempts(self) -> List[AccessAttempt]:
197
+ return list(self._attempts)
198
+
199
+ def get_all_actual_accesses(self) -> List[ActualAccess]:
200
+ return list(self._actual)
201
+
202
+ def get_agent_relationships(
203
+ self, agent_id: str
204
+ ) -> List[AccessRelationship]:
205
+ return list(self._agent_rels.get(agent_id, []))
206
+
207
+ def to_dict(self) -> dict:
208
+ return {
209
+ "resources": {
210
+ name: node.to_profile().model_dump(mode="json")
211
+ for name, node in self._resources.items()
212
+ },
213
+ "total_attempts": len(self._attempts),
214
+ "total_actual_accesses": len(self._actual),
215
+ }
216
+