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,294 @@
1
+ """Deterministic policy evaluator enforcing security boundaries."""
2
+
3
+ from datetime import datetime, timezone
4
+ from typing import TYPE_CHECKING, Any, Dict, List, Optional
5
+ from actshield.agents.identity import AgentTrustLevel
6
+ from actshield.context.context import Context
7
+ from actshield.context.taint import TaintState
8
+ from actshield.decisions.decision import DecisionAction, SecurityDecision
9
+ from actshield.integrations.ai_secura import SecurityAnalysis, SecurityContext
10
+ from actshield.integrations.apiris import APIAnalysis
11
+ from actshield.policy.intent import DeterministicIntentAnalyzer, IntentAlignmentResult, IntentAnalyzer
12
+ from actshield.policy.policy import Policy
13
+ from actshield.risk.models import RiskFactorBreakdown, RiskLevel, RiskSignal
14
+ from actshield.tools.tool import SensitivityLevel, ToolDefinition, ToolRequest
15
+ from actshield.tracing.correlation import CorrelationContext, generate_id
16
+ from actshield.tracing.events import EventType
17
+
18
+ if TYPE_CHECKING:
19
+ from actshield.client import ActShield
20
+
21
+
22
+ class PolicyEvaluator:
23
+ """Deterministic security policy engine for actshield.
24
+
25
+ Adheres strictly to the architectural principles:
26
+ - AI reasons (AI Secura / Intent Analysis).
27
+ - Deterministic policy evaluates signals, state, authority containment, intent alignment,
28
+ and data taint to render decisions:
29
+ ALLOW / MONITOR / HUMAN_APPROVAL / QUARANTINE / BLOCK / REVOKE.
30
+ """
31
+
32
+ def __init__(self, guard: "ActShield") -> None:
33
+ self.guard = guard
34
+ self.intent_analyzer: IntentAnalyzer = DeterministicIntentAnalyzer()
35
+ self.default_policies: List[Policy] = []
36
+
37
+ def evaluate(
38
+ self,
39
+ tool_def: ToolDefinition,
40
+ request: ToolRequest,
41
+ ctx: Optional[CorrelationContext],
42
+ active_contexts: List[Context],
43
+ ) -> SecurityDecision:
44
+ trace_id = ctx.trace_id if ctx else generate_id("trc")
45
+ evidence_refs: List[str] = []
46
+ signals: List[RiskSignal] = []
47
+
48
+ # 1. Resolve Acting Agent
49
+ agent_id = request.agent_id or (ctx.agent_id if ctx else None)
50
+ acting_agent = self.guard.agent_registry.get(agent_id) if agent_id else None
51
+
52
+ # 2. Resolve Active Delegation Chain
53
+ delegation_id = ctx.delegation_id if ctx else None
54
+ active_delegation = self.guard.delegation_registry.get(delegation_id) if delegation_id else None
55
+
56
+ if active_delegation:
57
+ evidence_refs.append(active_delegation.delegation_id)
58
+
59
+ # 3. Influencing Contexts & Taint Assessment
60
+ influencing_ctx_ids = [c.context_id for c in active_contexts if c is not None]
61
+ for c in active_contexts:
62
+ if c.context_id and c.context_id not in evidence_refs:
63
+ evidence_refs.append(c.context_id)
64
+
65
+ has_tainted_context = any(c.taint_state in (TaintState.TAINTED, TaintState.UNTRUSTED) for c in active_contexts)
66
+ has_untrusted_origin = any(c.source.value in ("external_mcp", "external_api") or c.trust_level == "untrusted" or c.taint_state == TaintState.UNTRUSTED for c in active_contexts)
67
+ is_critical_sink = tool_def.sensitivity in (SensitivityLevel.HIGH, SensitivityLevel.CRITICAL)
68
+
69
+
70
+ # 4. Authority Containment Evaluation
71
+ authority_violation = False
72
+ authority_reason = ""
73
+ delegated_caps: List[str] = []
74
+
75
+ if active_delegation is not None:
76
+ delegated_caps = list(active_delegation.authority_grant.granted_capabilities)
77
+ for req_cap in tool_def.required_capabilities:
78
+ if not active_delegation.authority_grant.contains_capability(req_cap):
79
+ authority_violation = True
80
+ authority_reason = f"Delegated authority {delegated_caps} lacks required capability '{req_cap}'"
81
+ break
82
+ elif acting_agent is not None:
83
+ delegated_caps = acting_agent.capability_names()
84
+ for req_cap in tool_def.required_capabilities:
85
+ if not acting_agent.has_capability(req_cap):
86
+ authority_violation = True
87
+ authority_reason = f"Agent '{acting_agent.name}' does not possess declared capability '{req_cap}'"
88
+ break
89
+
90
+ authority_containment_report = {
91
+ "requesting_agent": acting_agent.name if acting_agent else agent_id,
92
+ "required_capabilities": tool_def.required_capabilities,
93
+ "delegated_capabilities": delegated_caps,
94
+ "delegation_id": delegation_id,
95
+ "contained": not authority_violation,
96
+ "reason": authority_reason if authority_violation else "Authority containment verified.",
97
+ }
98
+
99
+ # 5. Intent Alignment Evaluation
100
+ original_intent = ctx.metadata.get("intent", "Unspecified task intent") if ctx else "Unspecified task intent"
101
+ target_res_name = tool_def.target_resources[0].name if tool_def.target_resources else None
102
+
103
+ intent_res: IntentAlignmentResult = self.intent_analyzer.analyze(
104
+ original_intent=original_intent,
105
+ tool_name=tool_def.name,
106
+ required_capabilities=tool_def.required_capabilities,
107
+ target_resource_sensitivity=tool_def.sensitivity.value,
108
+ target_resource_name=target_res_name,
109
+ arguments=request.arguments,
110
+ )
111
+ intent_violation = intent_res.is_violation
112
+
113
+ intent_alignment_report = {
114
+ "original_intent": original_intent,
115
+ "action_name": tool_def.name,
116
+ "status": intent_res.status.value,
117
+ "aligned": not intent_violation,
118
+ "explanation": intent_res.explanation,
119
+ "indicators": intent_res.matched_indicators,
120
+ }
121
+
122
+ # 6. Compute Deterministic Risk Breakdown
123
+ risk_breakdown = RiskFactorBreakdown.compute(
124
+ has_tainted_context=has_tainted_context,
125
+ is_critical_sink=is_critical_sink,
126
+ has_authority_violation=authority_violation,
127
+ has_intent_violation=intent_violation,
128
+ is_untrusted_origin=has_untrusted_origin,
129
+ )
130
+
131
+ # 7. Build Structured Explanation
132
+ structured_explanation = {
133
+ "agent": acting_agent.name if acting_agent else agent_id,
134
+ "requested_tool": tool_def.name,
135
+ "required_capabilities": tool_def.required_capabilities,
136
+ "delegated_capabilities": delegated_caps,
137
+ "authority_containment": "PASSED" if not authority_violation else "FAILED",
138
+ "original_intent": original_intent,
139
+ "intent_alignment": "PASSED" if not intent_violation else "FAILED",
140
+ "context_sources": [c.source.value for c in active_contexts],
141
+ "context_taint_states": [c.taint_state.value for c in active_contexts],
142
+ "sensitive_sink": target_res_name or tool_def.name,
143
+ "sink_sensitivity": tool_def.sensitivity.value,
144
+ "risk_factors": risk_breakdown.risk_factors,
145
+ "total_risk_score": risk_breakdown.total_score,
146
+ }
147
+
148
+ # 8. Deterministic Decision Rule Evaluations
149
+ # Rule 8a: Authority Escalation Violation -> BLOCK
150
+ if authority_violation:
151
+ reason_code = "DELEGATION_AUTHORITY_EXCEEDED" if active_delegation else "MISSING_AGENT_CAPABILITY"
152
+ explanation = (
153
+ f"Agent '{acting_agent.name if acting_agent else agent_id}' was delegated authority "
154
+ f"lacking required capability '{tool_def.required_capabilities}' for tool '{tool_def.name}'."
155
+ if active_delegation else
156
+ f"Agent '{acting_agent.name if acting_agent else agent_id}' does not possess declared capability "
157
+ f"required to invoke tool '{tool_def.name}'."
158
+ )
159
+ decision = SecurityDecision(
160
+ action=DecisionAction.BLOCK,
161
+ risk_level=RiskLevel.HIGH if risk_breakdown.total_score < 0.7 else RiskLevel.CRITICAL,
162
+ risk_score=risk_breakdown.total_score,
163
+ reason_code=reason_code,
164
+ explanation=explanation,
165
+ trace_id=trace_id,
166
+ evidence_references=evidence_refs,
167
+ influencing_context_ids=influencing_ctx_ids,
168
+ authority_containment=authority_containment_report,
169
+ intent_alignment=intent_alignment_report,
170
+ risk_breakdown=risk_breakdown,
171
+ structured_explanation=structured_explanation,
172
+ )
173
+ self._emit_decision_events(tool_def, request, decision)
174
+ return decision
175
+
176
+ # Rule 8b: Tainted Context flowing into Sensitive Sink -> BLOCK
177
+ if has_tainted_context and is_critical_sink:
178
+ if self.guard.config.block_tainted_sink_access:
179
+ decision = SecurityDecision(
180
+ action=DecisionAction.BLOCK,
181
+ risk_level=RiskLevel.CRITICAL,
182
+ risk_score=risk_breakdown.total_score,
183
+ reason_code="TAINTED_CONTEXT_INTO_SENSITIVE_SINK",
184
+ explanation=(
185
+ f"Tainted or injected context attempted to flow into {tool_def.sensitivity.value} "
186
+ f"sensitive tool/resource '{tool_def.name}' without explicit verification/sanitization."
187
+ ),
188
+ trace_id=trace_id,
189
+ evidence_references=evidence_refs,
190
+ influencing_context_ids=influencing_ctx_ids,
191
+ authority_containment=authority_containment_report,
192
+ intent_alignment=intent_alignment_report,
193
+ risk_breakdown=risk_breakdown,
194
+ structured_explanation=structured_explanation,
195
+ )
196
+ self._emit_decision_events(tool_def, request, decision)
197
+ return decision
198
+
199
+ # Rule 8c: Untrusted Agent Sensitive Access -> BLOCK
200
+ if acting_agent is not None and acting_agent.trust_level == AgentTrustLevel.UNTRUSTED and tool_def.sensitivity != SensitivityLevel.LOW:
201
+ decision = SecurityDecision(
202
+ action=DecisionAction.BLOCK,
203
+ risk_level=RiskLevel.HIGH,
204
+ risk_score=risk_breakdown.total_score,
205
+ reason_code="UNTRUSTED_AGENT_SENSITIVE_ACCESS",
206
+ explanation=f"Untrusted agent '{acting_agent.name}' is prohibited from invoking {tool_def.sensitivity.value} tool '{tool_def.name}'.",
207
+ trace_id=trace_id,
208
+ evidence_references=evidence_refs,
209
+ influencing_context_ids=influencing_ctx_ids,
210
+ authority_containment=authority_containment_report,
211
+ intent_alignment=intent_alignment_report,
212
+ risk_breakdown=risk_breakdown,
213
+ structured_explanation=structured_explanation,
214
+ )
215
+ self._emit_decision_events(tool_def, request, decision)
216
+ return decision
217
+
218
+ # Rule 8d: User Intent Alignment Violation -> BLOCK
219
+ if intent_violation and is_critical_sink:
220
+ decision = SecurityDecision(
221
+ action=DecisionAction.BLOCK,
222
+ risk_level=RiskLevel.HIGH,
223
+ risk_score=risk_breakdown.total_score,
224
+ reason_code="INTENT_ALIGNMENT_VIOLATION",
225
+ explanation=intent_res.explanation,
226
+ trace_id=trace_id,
227
+ evidence_references=evidence_refs,
228
+ influencing_context_ids=influencing_ctx_ids,
229
+ authority_containment=authority_containment_report,
230
+ intent_alignment=intent_alignment_report,
231
+ risk_breakdown=risk_breakdown,
232
+ structured_explanation=structured_explanation,
233
+ )
234
+ self._emit_decision_events(tool_def, request, decision)
235
+ return decision
236
+
237
+ # Rule 8e: Default Allowed / Monitored
238
+ action = DecisionAction.ALLOW if risk_breakdown.total_score < 0.4 else DecisionAction.MONITOR
239
+ decision = SecurityDecision(
240
+ action=action,
241
+ risk_level=RiskLevel.LOW if action == DecisionAction.ALLOW else RiskLevel.MEDIUM,
242
+ risk_score=risk_breakdown.total_score,
243
+ reason_code="POLICY_ALLOW_STANDARD" if action == DecisionAction.ALLOW else "RISK_MONITORED",
244
+ explanation="Action permitted under standard security policy." if action == DecisionAction.ALLOW else "Action permitted under continuous security monitoring.",
245
+ trace_id=trace_id,
246
+ evidence_references=evidence_refs,
247
+ influencing_context_ids=influencing_ctx_ids,
248
+ authority_containment=authority_containment_report,
249
+ intent_alignment=intent_alignment_report,
250
+ risk_breakdown=risk_breakdown,
251
+ structured_explanation=structured_explanation,
252
+ )
253
+
254
+ self._emit_decision_events(tool_def, request, decision)
255
+ return decision
256
+
257
+ def _emit_decision_events(
258
+ self,
259
+ tool_def: ToolDefinition,
260
+ request: ToolRequest,
261
+ decision: SecurityDecision,
262
+ ) -> None:
263
+ """Emit security.evaluated and security.decision events to the tracer."""
264
+ self.guard.emit_event(
265
+ event_type=EventType.SECURITY_EVALUATED,
266
+ trace_id=decision.trace_id,
267
+ agent_id=request.agent_id,
268
+ payload={
269
+ "tool_id": tool_def.tool_id,
270
+ "tool_name": tool_def.name,
271
+ "risk_score": decision.risk_score,
272
+ "risk_level": decision.risk_level.value,
273
+ "authority_contained": decision.authority_containment.get("contained", True),
274
+ "intent_aligned": decision.intent_alignment.get("aligned", True),
275
+ }
276
+ )
277
+
278
+ self.guard.emit_event(
279
+ event_type=EventType.SECURITY_DECISION,
280
+ trace_id=decision.trace_id,
281
+ agent_id=request.agent_id,
282
+ payload={
283
+ "decision_id": decision.decision_id,
284
+ "action": decision.action.value,
285
+ "reason_code": decision.reason_code,
286
+ "explanation": decision.explanation,
287
+ "risk_score": decision.risk_score,
288
+ "evidence_references": decision.evidence_references,
289
+ "influencing_context_ids": decision.influencing_context_ids,
290
+ "structured_explanation": decision.structured_explanation,
291
+ }
292
+ )
293
+
294
+
@@ -0,0 +1,113 @@
1
+ """Intent tracking and deterministic intent alignment analysis."""
2
+
3
+ from enum import Enum
4
+ from typing import Any, Dict, List, Optional, Protocol, runtime_checkable
5
+ from pydantic import BaseModel, Field
6
+
7
+
8
+ class IntentAlignmentStatus(str, Enum):
9
+ """Alignment status between original user intent and requested agent action."""
10
+ ALIGNED = "ALIGNED"
11
+ MISALIGNED = "MISALIGNED"
12
+ VIOLATION = "VIOLATION"
13
+ UNKNOWN = "UNKNOWN"
14
+
15
+
16
+ class IntentAlignmentResult(BaseModel):
17
+ """Result of intent alignment verification."""
18
+ status: IntentAlignmentStatus = IntentAlignmentStatus.ALIGNED
19
+ score: float = Field(default=0.0, ge=0.0, le=1.0, description="Risk contribution (0.0 = aligned, 1.0 = direct violation)")
20
+ explanation: str = Field(default="Action aligns with original user intent.")
21
+ matched_indicators: List[str] = Field(default_factory=list)
22
+ metadata: Dict[str, Any] = Field(default_factory=dict)
23
+
24
+ def __init__(self, **data: Any) -> None:
25
+ if "reason" in data and "explanation" not in data:
26
+ data["explanation"] = data["reason"]
27
+ super().__init__(**data)
28
+
29
+ @property
30
+ def is_violation(self) -> bool:
31
+ return self.status in (IntentAlignmentStatus.VIOLATION, IntentAlignmentStatus.MISALIGNED)
32
+
33
+ @property
34
+ def reason(self) -> str:
35
+ return self.explanation
36
+
37
+
38
+
39
+
40
+ @runtime_checkable
41
+ class IntentAnalyzer(Protocol):
42
+ """Protocol for intent alignment analysis (extensible for AI Secura in Phase 5)."""
43
+
44
+ def analyze(
45
+ self,
46
+ original_intent: str,
47
+ tool_name: str,
48
+ required_capabilities: List[str],
49
+ target_resource_sensitivity: str,
50
+ target_resource_name: Optional[str] = None,
51
+ arguments: Optional[Dict[str, Any]] = None,
52
+ ) -> IntentAlignmentResult:
53
+ """Evaluate if the requested action and required capability remain within the original user intent."""
54
+ ...
55
+
56
+
57
+ class DeterministicIntentAnalyzer(IntentAnalyzer):
58
+ """Deterministic, explainable baseline intent analyzer for Phase 2."""
59
+
60
+ def analyze(
61
+ self,
62
+ original_intent: str,
63
+ tool_name: str,
64
+ required_capabilities: List[str],
65
+ target_resource_sensitivity: str,
66
+ target_resource_name: Optional[str] = None,
67
+ arguments: Optional[Dict[str, Any]] = None,
68
+ ) -> IntentAlignmentResult:
69
+ intent_lower = (original_intent or "").lower()
70
+ tool_lower = (tool_name or "").lower()
71
+ res_lower = (target_resource_name or "").lower()
72
+ caps = [c.lower() for c in required_capabilities]
73
+
74
+ indicators: List[str] = []
75
+
76
+ # Check for public/financial scope vs customer PII / sensitive data
77
+ is_public_financial_intent = any(k in intent_lower for k in ("public", "financial", "sec", "annual report", "10-k", "market"))
78
+ is_customer_pii_target = any("customer" in c for c in caps) or "customer" in tool_lower or "customer" in res_lower or "pii" in res_lower
79
+
80
+ if is_public_financial_intent and is_customer_pii_target:
81
+ indicators.append("PUBLIC_INTENT_VS_CUSTOMER_PII_SINK")
82
+ return IntentAlignmentResult(
83
+ status=IntentAlignmentStatus.VIOLATION,
84
+ score=0.85,
85
+ explanation=(
86
+ f"Intent Violation: Original user intent was scoped to public financial information ('{original_intent}'), "
87
+ f"but requested tool '{tool_name}' targets sensitive customer records/PII."
88
+ ),
89
+ matched_indicators=indicators,
90
+ )
91
+
92
+ # Check for administrative / destructive actions in audit intents
93
+ is_read_intent = any(k in intent_lower for k in ("analyze", "audit", "find", "read", "extract", "search"))
94
+ is_destructive_action = any(k in tool_lower for k in ("delete", "drop", "write", "modify", "admin", "truncate"))
95
+
96
+ if is_read_intent and is_destructive_action:
97
+ indicators.append("READ_AUDIT_INTENT_VS_DESTRUCTIVE_ACTION")
98
+ return IntentAlignmentResult(
99
+ status=IntentAlignmentStatus.VIOLATION,
100
+ score=0.95,
101
+ explanation=(
102
+ f"Intent Violation: Original intent is read/audit ('{original_intent}'), "
103
+ f"but requested tool '{tool_name}' performs destructive modification."
104
+ ),
105
+ matched_indicators=indicators,
106
+ )
107
+
108
+ return IntentAlignmentResult(
109
+ status=IntentAlignmentStatus.ALIGNED,
110
+ score=0.0,
111
+ explanation=f"Action '{tool_name}' aligns with declared user intent.",
112
+ matched_indicators=[],
113
+ )
@@ -0,0 +1,48 @@
1
+ """Deterministic security policy definitions."""
2
+
3
+ from enum import Enum
4
+ from typing import Any, Callable, Dict, List, Optional
5
+ from pydantic import BaseModel, Field
6
+
7
+ from actshield.decisions.decision import DecisionAction
8
+ from actshield.risk.models import RiskLevel
9
+ from actshield.tracing.correlation import generate_id
10
+
11
+
12
+ class RuleCondition(str, Enum):
13
+ """Common condition types evaluated by policy rules."""
14
+ CAPABILITY_REQUIRED = "CAPABILITY_REQUIRED"
15
+ TAINT_RESTRICTION = "TAINT_RESTRICTION"
16
+ TRUST_LEVEL_MINIMUM = "TRUST_LEVEL_MINIMUM"
17
+ SENSITIVITY_THRESHOLD = "SENSITIVITY_THRESHOLD"
18
+ APIRIS_RISK_THRESHOLD = "APIRIS_RISK_THRESHOLD"
19
+ AI_SECURA_INTENT_DRIFT = "AI_SECURA_INTENT_DRIFT"
20
+ CUSTOM = "CUSTOM"
21
+
22
+
23
+ class PolicyRule(BaseModel):
24
+ """An individual deterministic rule within a Policy."""
25
+
26
+ rule_id: str = Field(default_factory=lambda: generate_id("rule"))
27
+ name: str = Field(..., description="Human-readable rule name")
28
+ description: str = Field(default="")
29
+ condition_type: RuleCondition = Field(default=RuleCondition.CUSTOM)
30
+ target_action: DecisionAction = Field(default=DecisionAction.BLOCK, description="Enforcement action if rule matches")
31
+ risk_level: RiskLevel = Field(default=RiskLevel.HIGH)
32
+ reason_code: str = Field(default="POLICY_VIOLATION")
33
+ explanation_template: str = Field(default="Policy rule violation detected.")
34
+ enabled: bool = Field(default=True)
35
+ metadata: Dict[str, Any] = Field(default_factory=dict)
36
+
37
+
38
+ class Policy(BaseModel):
39
+ """A collection of security rules governing agent executions, delegations, and tool access."""
40
+
41
+ policy_id: str = Field(default_factory=lambda: generate_id("pol"))
42
+ name: str = Field(..., description="Policy name")
43
+ description: str = Field(default="")
44
+ version: str = Field(default="1.0.0")
45
+ rules: List[PolicyRule] = Field(default_factory=list)
46
+ enabled: bool = Field(default=True)
47
+ metadata: Dict[str, Any] = Field(default_factory=dict)
48
+
@@ -0,0 +1,26 @@
1
+ """ActShield Posture Engine Package (Phase 9)."""
2
+
3
+ from actshield.posture.posture_models import (
4
+ FindingSeverity,
5
+ PostureDiff,
6
+ PostureDimensionMetrics,
7
+ PostureFinding,
8
+ PostureRating,
9
+ SecurityPostureSnapshot,
10
+ )
11
+ from actshield.posture.posture_rules import PostureRuleEvaluator
12
+ from actshield.posture.posture_engine import PostureDiffEngine, PostureEngine
13
+
14
+ __all__ = [
15
+ "FindingSeverity",
16
+ "PostureDiff",
17
+ "PostureDimensionMetrics",
18
+ "PostureFinding",
19
+ "PostureRating",
20
+ "SecurityPostureSnapshot",
21
+ "PostureRuleEvaluator",
22
+ "PostureDiffEngine",
23
+ "PostureEngine",
24
+ ]
25
+
26
+
@@ -0,0 +1,186 @@
1
+ """Posture diffing, metrics calculation, and engine implementation."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import statistics
6
+ import time
7
+ from datetime import datetime, timezone
8
+ from typing import Any, Dict, List, Optional
9
+
10
+ from actshield.posture.posture_models import (
11
+ FindingSeverity,
12
+ PostureDiff,
13
+ PostureDimensionMetrics,
14
+ PostureFinding,
15
+ PostureRating,
16
+ SecurityPostureSnapshot,
17
+ )
18
+ from actshield.posture.posture_rules import PostureRuleEvaluator
19
+
20
+
21
+ class PostureDiffEngine:
22
+ """Computes explainable diffs between two security posture snapshots."""
23
+
24
+ @staticmethod
25
+ def diff(before: SecurityPostureSnapshot, after: SecurityPostureSnapshot) -> PostureDiff:
26
+ score_change = round(after.security_score - before.security_score, 1)
27
+ rating_transition = f"{before.rating.value} -> {after.rating.value}"
28
+
29
+ before_fnd_titles = {f.title for f in before.findings}
30
+ new_findings = [f for f in after.findings if f.title not in before_fnd_titles]
31
+ after_fnd_titles = {f.title for f in after.findings}
32
+ resolved_findings = [f.title for f in before.findings if f.title not in after_fnd_titles]
33
+
34
+ # Calculate metric deltas
35
+ metric_deltas: Dict[str, Any] = {}
36
+ before_dict = before.metrics.model_dump()
37
+ after_dict = after.metrics.model_dump()
38
+ for k in before_dict:
39
+ b_val = before_dict[k]
40
+ a_val = after_dict[k]
41
+ if isinstance(b_val, (int, float)) and isinstance(a_val, (int, float)):
42
+ metric_deltas[k] = {
43
+ "before": b_val,
44
+ "after": a_val,
45
+ "delta": round(a_val - b_val, 2),
46
+ }
47
+
48
+ if score_change > 0:
49
+ summary = f"Security posture improved by +{score_change} pts ({rating_transition}). Resolved {len(resolved_findings)} finding(s)."
50
+ elif score_change < 0:
51
+ summary = f"Security posture degraded by {score_change} pts ({rating_transition}). Discovered {len(new_findings)} new finding(s)."
52
+ else:
53
+ summary = f"Security posture remained stable at {after.security_score} pts ({rating_transition})."
54
+
55
+ return PostureDiff(
56
+ before_snapshot_id=before.snapshot_id,
57
+ after_snapshot_id=after.snapshot_id,
58
+ score_change=score_change,
59
+ rating_transition=rating_transition,
60
+ new_findings=new_findings,
61
+ resolved_findings=resolved_findings,
62
+ metric_deltas=metric_deltas,
63
+ summary=summary,
64
+ )
65
+
66
+
67
+ class PostureEngine:
68
+ """Derives measurable posture snapshots from live runtime state."""
69
+
70
+ def __init__(self, guard: Optional[Any] = None) -> None:
71
+ self.guard = guard
72
+ self.snapshots: List[SecurityPostureSnapshot] = []
73
+ self._latencies: List[float] = []
74
+
75
+ def evaluate_current_posture(
76
+ self,
77
+ events: Optional[List[Dict[str, Any]]] = None,
78
+ regressions: Optional[List[Dict[str, Any]]] = None,
79
+ incidents: Optional[List[Dict[str, Any]]] = None,
80
+ agents: Optional[List[Dict[str, Any]]] = None,
81
+ environment: str = "production",
82
+ ) -> SecurityPostureSnapshot:
83
+ """Derive and return the current Security Posture Snapshot."""
84
+ return self.generate_snapshot(
85
+ events=events or [],
86
+ regressions=regressions,
87
+ incidents=incidents,
88
+ agents=agents,
89
+ environment=environment,
90
+ )
91
+
92
+ def record_latency(self, latency_ms: float) -> None:
93
+ """Record enforcement latency measurement."""
94
+
95
+ self._latencies.append(latency_ms)
96
+ if len(self._latencies) > 5000:
97
+ self._latencies.pop(0)
98
+
99
+ def compute_metrics(
100
+ self,
101
+ events: List[Dict[str, Any]],
102
+ regressions: Optional[List[Dict[str, Any]]] = None,
103
+ incidents: Optional[List[Dict[str, Any]]] = None,
104
+ agents: Optional[List[Dict[str, Any]]] = None,
105
+ ) -> PostureDimensionMetrics:
106
+ """Derive dimension metrics from real event logs and state."""
107
+ regressions = regressions or []
108
+ incidents = incidents or []
109
+ agents = agents or []
110
+
111
+ unauthorized_attempts = sum(1 for e in events if e.get("decision") in ("BLOCK", "REVOKE"))
112
+ actual_unauthorized = sum(1 for e in events if e.get("decision") == "ALLOW" and e.get("taint") == "TAINTED" and e.get("resource_sensitivity") in ("HIGH", "CRITICAL"))
113
+ tainted_reqs = sum(1 for e in events if e.get("taint") in ("TAINTED", "UNTRUSTED") and e.get("tool"))
114
+ auth_violations = sum(1 for e in events if e.get("authority_violation") or "authority" in str(e.get("reason", "")).lower())
115
+ intent_violations = sum(1 for e in events if e.get("intent_alignment") in ("VIOLATED", "DRIFTING"))
116
+ blocked_actions = sum(1 for e in events if e.get("decision") == "BLOCK")
117
+ hitl_actions = sum(1 for e in events if e.get("decision") in ("HITL", "HUMAN_APPROVAL"))
118
+ allowed_actions = sum(1 for e in events if e.get("decision") == "ALLOW")
119
+
120
+ untrusted_crossings = sum(1 for e in events if e.get("context_source") == "external_mcp" or e.get("taint") == "UNTRUSTED")
121
+ sensitive_exposures = sum(1 for e in events if e.get("resource_sensitivity") in ("HIGH", "CRITICAL"))
122
+
123
+ open_regs = sum(1 for r in regressions if r.get("status", "OPEN") == "OPEN")
124
+ failed_replays = sum(1 for r in regressions if r.get("replay_result") == "FAIL" or r.get("secured_replay_passed") is False)
125
+ off_failures = sum(1 for r in regressions if r.get("bypass_detected") is True)
126
+
127
+ high_risk_agents = sum(1 for a in agents if str(a.get("trust_level", "")).lower() in ("low", "untrusted"))
128
+
129
+ # Latencies
130
+ mean_lat = round(statistics.mean(self._latencies), 2) if self._latencies else 0.45
131
+ p95_lat = round(statistics.quantiles(self._latencies, n=20)[18], 2) if len(self._latencies) >= 20 else (mean_lat * 1.4)
132
+
133
+ return PostureDimensionMetrics(
134
+ unauthorized_access_attempts=unauthorized_attempts,
135
+ actual_unauthorized_executions=actual_unauthorized,
136
+ tainted_sensitive_requests=tainted_reqs,
137
+ authority_violations=auth_violations,
138
+ intent_violations=intent_violations,
139
+ blocked_actions=blocked_actions,
140
+ hitl_actions=hitl_actions,
141
+ allowed_actions=allowed_actions,
142
+ high_risk_agents_count=high_risk_agents,
143
+ untrusted_context_crossings=untrusted_crossings,
144
+ sensitive_resource_exposures=sensitive_exposures,
145
+ open_regressions=open_regs,
146
+ failed_secured_replays=failed_replays,
147
+ offensive_validation_failures=off_failures,
148
+ ai_secura_availability_pct=100.0,
149
+ apiris_availability_pct=100.0,
150
+ policy_engine_availability_pct=100.0,
151
+ mean_enforcement_latency_ms=mean_lat,
152
+ p95_enforcement_latency_ms=p95_lat,
153
+ )
154
+
155
+ def generate_snapshot(
156
+ self,
157
+ events: List[Dict[str, Any]],
158
+ regressions: Optional[List[Dict[str, Any]]] = None,
159
+ incidents: Optional[List[Dict[str, Any]]] = None,
160
+ agents: Optional[List[Dict[str, Any]]] = None,
161
+ environment: str = "production",
162
+ ) -> SecurityPostureSnapshot:
163
+ """Create and store a full security posture snapshot."""
164
+ metrics = self.compute_metrics(events, regressions, incidents, agents)
165
+ score, rating, findings = PostureRuleEvaluator.evaluate(metrics)
166
+
167
+ snapshot = SecurityPostureSnapshot(
168
+ rating=rating,
169
+ security_score=score,
170
+ metrics=metrics,
171
+ findings=findings,
172
+ active_incidents_count=len(incidents or []),
173
+ total_events_evaluated=len(events),
174
+ environment=environment,
175
+ )
176
+ self.snapshots.append(snapshot)
177
+ return snapshot
178
+
179
+ def get_latest_snapshot(self) -> Optional[SecurityPostureSnapshot]:
180
+ return self.snapshots[-1] if self.snapshots else None
181
+
182
+ def diff_latest(self) -> Optional[PostureDiff]:
183
+ if len(self.snapshots) < 2:
184
+ return None
185
+ return PostureDiffEngine.diff(self.snapshots[-2], self.snapshots[-1])
186
+