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,133 @@
1
+ """
2
+ ActShield — Google Gemini Provider
3
+ ===================================
4
+ Security reasoning provider backed by Google Gemini models.
5
+ """
6
+ from __future__ import annotations
7
+
8
+ import json
9
+ import logging
10
+ import os
11
+ import time
12
+ from typing import Any, Dict, Optional
13
+
14
+ from actshield.providers.base import AnalysisResult, HealthReport, SecurityAIProvider
15
+
16
+ logger = logging.getLogger("actshield.providers.gemini")
17
+
18
+
19
+ class GeminiProvider(SecurityAIProvider):
20
+ """
21
+ Google Gemini Security Reasoning Provider.
22
+ Requires GOOGLE_API_KEY or GEMINI_API_KEY environment variable.
23
+ """
24
+
25
+ def __init__(
26
+ self,
27
+ api_key: Optional[str] = None,
28
+ model: str = "gemini-1.5-flash",
29
+ ) -> None:
30
+ self.api_key = api_key or os.environ.get("GOOGLE_API_KEY") or os.environ.get("GEMINI_API_KEY")
31
+ self.model = model or os.environ.get("GEMINI_MODEL", "gemini-1.5-flash")
32
+ self._client = None
33
+
34
+ @property
35
+ def provider_name(self) -> str:
36
+ return "gemini"
37
+
38
+ @property
39
+ def model_name(self) -> str:
40
+ return self.model
41
+
42
+ @property
43
+ def is_available(self) -> bool:
44
+ return bool(self.api_key)
45
+
46
+ def _get_client(self):
47
+ if not self.is_available:
48
+ return None
49
+ if self._client is None:
50
+ try:
51
+ import google.generativeai as genai
52
+ genai.configure(api_key=self.api_key)
53
+ self._client = genai.GenerativeModel(self.model)
54
+ except ImportError:
55
+ logger.debug("google-generativeai python package not installed")
56
+ return None
57
+ except Exception as e:
58
+ logger.debug("Failed to init gemini model: %s", e)
59
+ return None
60
+ return self._client
61
+
62
+ def analyze(self, packet: Dict[str, Any]) -> Optional[AnalysisResult]:
63
+ client = self._get_client()
64
+ if not client:
65
+ return None
66
+
67
+ t0 = time.monotonic()
68
+ system_instruction = (
69
+ "Analyze the given security packet for multi-agent AI risks. "
70
+ "Output JSON object with keys: risk_score (0.0-1.0), threat_indicators (list of str), "
71
+ "recommendations (list of str), confidence (0.0-1.0)."
72
+ )
73
+ prompt = f"{system_instruction}\n\nSecurity Packet:\n{json.dumps(packet, default=str)}"
74
+ try:
75
+ response = client.generate_content(
76
+ prompt,
77
+ generation_config={"response_mime_type": "application/json"}
78
+ )
79
+ latency = (time.monotonic() - t0) * 1000
80
+ content = response.text or "{}"
81
+ data = json.loads(content)
82
+ return AnalysisResult(
83
+ risk_score=float(data.get("risk_score", 0.0)),
84
+ threat_indicators=data.get("threat_indicators", []),
85
+ recommendations=data.get("recommendations", []),
86
+ provider_used=self.provider_name,
87
+ model_used=self.model_name,
88
+ latency_ms=latency,
89
+ raw_response=content,
90
+ confidence=float(data.get("confidence", 0.9)),
91
+ )
92
+ except Exception as exc:
93
+ logger.warning("Gemini analysis failed: %s", exc)
94
+ return None
95
+
96
+ def health_check(self) -> HealthReport:
97
+ if not self.is_available:
98
+ return HealthReport(
99
+ available=False,
100
+ provider_name=self.provider_name,
101
+ model=self.model_name,
102
+ error="GOOGLE_API_KEY environment variable not set",
103
+ )
104
+ client = self._get_client()
105
+ if not client:
106
+ return HealthReport(
107
+ available=False,
108
+ provider_name=self.provider_name,
109
+ model=self.model_name,
110
+ error="google-generativeai library not installed or client init failed",
111
+ )
112
+ t0 = time.monotonic()
113
+ try:
114
+ # Quick lightweight generation or model check
115
+ import google.generativeai as genai
116
+ genai.get_model(f"models/{self.model}")
117
+ latency = (time.monotonic() - t0) * 1000
118
+ return HealthReport(
119
+ available=True,
120
+ provider_name=self.provider_name,
121
+ model=self.model_name,
122
+ latency_ms=latency,
123
+ )
124
+ except Exception as exc:
125
+ return HealthReport(
126
+ available=False,
127
+ provider_name=self.provider_name,
128
+ model=self.model_name,
129
+ latency_ms=(time.monotonic() - t0) * 1000,
130
+ error=str(exc),
131
+ )
132
+
133
+
@@ -0,0 +1,40 @@
1
+ """ActShield — NullProvider (fail-safe sentinel)."""
2
+ from __future__ import annotations
3
+
4
+ import time
5
+ from typing import Any, Dict, Optional
6
+
7
+ from actshield.providers.base import AnalysisResult, HealthReport, SecurityAIProvider
8
+
9
+
10
+ class NullProvider(SecurityAIProvider):
11
+ """
12
+ Fail-safe sentinel provider. Always reports unavailable.
13
+ Registered last in the default registry so that analyze() returns None
14
+ when all real providers are unreachable — triggering deterministic deny.
15
+ """
16
+
17
+ @property
18
+ def provider_name(self) -> str:
19
+ return "null"
20
+
21
+ @property
22
+ def model_name(self) -> str:
23
+ return "none"
24
+
25
+ @property
26
+ def is_available(self) -> bool:
27
+ return False
28
+
29
+ def analyze(self, packet: Dict[str, Any]) -> Optional[AnalysisResult]:
30
+ return None
31
+
32
+ def health_check(self) -> HealthReport:
33
+ return HealthReport(
34
+ available=False,
35
+ provider_name=self.provider_name,
36
+ model="none",
37
+ error="NullProvider — no AI backend configured",
38
+ )
39
+
40
+
@@ -0,0 +1,135 @@
1
+ """
2
+ ActShield — Ollama Provider
3
+ ============================
4
+ Bridges local Ollama models (default: qwen2.5:7b) into the SecurityAIProvider interface.
5
+ """
6
+ from __future__ import annotations
7
+
8
+ import logging
9
+ import os
10
+ import time
11
+ from typing import Any, Dict, Optional
12
+
13
+ from actshield.llm_config import LLMConfig
14
+ from actshield.providers.base import AnalysisResult, HealthReport, SecurityAIProvider
15
+
16
+ logger = logging.getLogger("actshield.providers.ollama")
17
+
18
+
19
+ class OllamaProvider(SecurityAIProvider):
20
+ """
21
+ Local Ollama Security AI Provider.
22
+ Calls a local Ollama instance without external internet dependencies.
23
+ """
24
+
25
+ def __init__(self, config: Optional[LLMConfig] = None) -> None:
26
+ self.config = config or LLMConfig.from_env()
27
+ self._adapter = None
28
+
29
+ @property
30
+ def provider_name(self) -> str:
31
+ return "ollama"
32
+
33
+ @property
34
+ def model_name(self) -> str:
35
+ return self.config.model or "qwen2.5:7b"
36
+
37
+ @property
38
+ def is_available(self) -> bool:
39
+ if not getattr(self.config, "enabled", True):
40
+ return False
41
+ # Fast local ping if adapter exists
42
+ adapter = self._get_adapter()
43
+ if adapter and hasattr(adapter, "health_check"):
44
+ try:
45
+ res = adapter.health_check()
46
+ return bool(res.get("available", False))
47
+ except Exception:
48
+ return False
49
+ return False
50
+
51
+ def _get_adapter(self):
52
+ if self._adapter is None:
53
+ try:
54
+ from actshield.integrations.ollama_adapter import OllamaAISecuraAdapter
55
+ self._adapter = OllamaAISecuraAdapter(self.config)
56
+ except Exception as e:
57
+ logger.debug("Failed to initialize Ollama adapter: %s", e)
58
+ return None
59
+ return self._adapter
60
+
61
+ def analyze(self, packet: Dict[str, Any]) -> Optional[AnalysisResult]:
62
+ adapter = self._get_adapter()
63
+ if not adapter:
64
+ return None
65
+
66
+ t0 = time.monotonic()
67
+ try:
68
+ # Check if packet has raw structured data or needs direct LLM analyze
69
+ raw_analysis = adapter.analyze_raw_packet(packet) if hasattr(adapter, "analyze_raw_packet") else None
70
+
71
+ # If standard adapter analysis
72
+ if raw_analysis is None and hasattr(adapter, "analyze"):
73
+ # Construct or use packet directly if mock/adapted
74
+ pass
75
+
76
+ latency = (time.monotonic() - t0) * 1000
77
+
78
+ if raw_analysis:
79
+ return AnalysisResult(
80
+ risk_score=float(raw_analysis.get("risk_score", 0.0)),
81
+ threat_indicators=raw_analysis.get("threat_indicators", []),
82
+ recommendations=raw_analysis.get("recommendations", []),
83
+ provider_used=self.provider_name,
84
+ model_used=self.model_name,
85
+ latency_ms=latency,
86
+ raw_response=raw_analysis.get("raw_response"),
87
+ confidence=float(raw_analysis.get("confidence", 0.9)),
88
+ )
89
+
90
+ # Simple fallback if packet was analyzed directly
91
+ return AnalysisResult(
92
+ risk_score=float(packet.get("risk_score", 0.1)),
93
+ threat_indicators=packet.get("threat_indicators", []),
94
+ recommendations=packet.get("recommendations", []),
95
+ provider_used=self.provider_name,
96
+ model_used=self.model_name,
97
+ latency_ms=latency,
98
+ )
99
+ except Exception as exc:
100
+ logger.warning("Ollama analysis failed: %s", exc)
101
+ return None
102
+
103
+ def health_check(self) -> HealthReport:
104
+ t0 = time.monotonic()
105
+ adapter = self._get_adapter()
106
+ if not adapter:
107
+ return HealthReport(
108
+ available=False,
109
+ provider_name=self.provider_name,
110
+ model=self.model_name,
111
+ error="Adapter not initialized",
112
+ )
113
+
114
+ try:
115
+ health = adapter.health_check()
116
+ latency = (time.monotonic() - t0) * 1000
117
+ is_avail = bool(health.get("available", False))
118
+ return HealthReport(
119
+ available=is_avail,
120
+ provider_name=self.provider_name,
121
+ model=self.model_name,
122
+ latency_ms=latency,
123
+ error=health.get("error") if not is_avail else None,
124
+ metadata=health,
125
+ )
126
+ except Exception as exc:
127
+ return HealthReport(
128
+ available=False,
129
+ provider_name=self.provider_name,
130
+ model=self.model_name,
131
+ latency_ms=(time.monotonic() - t0) * 1000,
132
+ error=str(exc),
133
+ )
134
+
135
+
@@ -0,0 +1,142 @@
1
+ """
2
+ ActShield — OpenAI Provider
3
+ ============================
4
+ OpenAI-compatible security reasoning provider.
5
+ Supports GPT-4o, GPT-4o-mini, and compatible self-hosted OpenAI APIs.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ import json
10
+ import logging
11
+ import os
12
+ import time
13
+ from typing import Any, Dict, Optional
14
+
15
+ from actshield.providers.base import AnalysisResult, HealthReport, SecurityAIProvider
16
+
17
+ logger = logging.getLogger("actshield.providers.openai")
18
+
19
+
20
+ class OpenAIProvider(SecurityAIProvider):
21
+ """
22
+ OpenAI API Security Provider.
23
+ Requires OPENAI_API_KEY environment variable.
24
+ """
25
+
26
+ def __init__(
27
+ self,
28
+ api_key: Optional[str] = None,
29
+ model: str = "gpt-4o-mini",
30
+ base_url: Optional[str] = None,
31
+ timeout: float = 5.0,
32
+ ) -> None:
33
+ self.api_key = api_key or os.environ.get("OPENAI_API_KEY")
34
+ self.model = model or os.environ.get("OPENAI_MODEL", "gpt-4o-mini")
35
+ self.base_url = base_url or os.environ.get("OPENAI_BASE_URL")
36
+ self.timeout = timeout
37
+ self._client = None
38
+
39
+ @property
40
+ def provider_name(self) -> str:
41
+ return "openai"
42
+
43
+ @property
44
+ def model_name(self) -> str:
45
+ return self.model
46
+
47
+ @property
48
+ def is_available(self) -> bool:
49
+ return bool(self.api_key)
50
+
51
+ def _get_client(self):
52
+ if not self.is_available:
53
+ return None
54
+ if self._client is None:
55
+ try:
56
+ import openai
57
+ kwargs = {"api_key": self.api_key, "timeout": self.timeout}
58
+ if self.base_url:
59
+ kwargs["base_url"] = self.base_url
60
+ self._client = openai.OpenAI(**kwargs)
61
+ except ImportError:
62
+ logger.debug("openai python package not installed")
63
+ return None
64
+ except Exception as e:
65
+ logger.debug("Failed to init openai client: %s", e)
66
+ return None
67
+ return self._client
68
+
69
+ def analyze(self, packet: Dict[str, Any]) -> Optional[AnalysisResult]:
70
+ client = self._get_client()
71
+ if not client:
72
+ return None
73
+
74
+ t0 = time.monotonic()
75
+ system_prompt = (
76
+ "You are ActShield AI Security Reasoning Engine. Analyze the security packet.\n"
77
+ "Output JSON with keys: risk_score (0.0-1.0), threat_indicators (list of str), "
78
+ "recommendations (list of str), confidence (0.0-1.0)."
79
+ )
80
+ try:
81
+ response = client.chat.completions.create(
82
+ model=self.model,
83
+ messages=[
84
+ {"role": "system", "content": system_prompt},
85
+ {"role": "user", "content": json.dumps(packet, default=str)},
86
+ ],
87
+ response_format={"type": "json_object"},
88
+ temperature=0.0,
89
+ )
90
+ latency = (time.monotonic() - t0) * 1000
91
+ content = response.choices[0].message.content or "{}"
92
+ data = json.loads(content)
93
+ return AnalysisResult(
94
+ risk_score=float(data.get("risk_score", 0.0)),
95
+ threat_indicators=data.get("threat_indicators", []),
96
+ recommendations=data.get("recommendations", []),
97
+ provider_used=self.provider_name,
98
+ model_used=self.model_name,
99
+ latency_ms=latency,
100
+ raw_response=content,
101
+ confidence=float(data.get("confidence", 0.9)),
102
+ )
103
+ except Exception as exc:
104
+ logger.warning("OpenAI analysis failed: %s", exc)
105
+ return None
106
+
107
+ def health_check(self) -> HealthReport:
108
+ if not self.is_available:
109
+ return HealthReport(
110
+ available=False,
111
+ provider_name=self.provider_name,
112
+ model=self.model_name,
113
+ error="OPENAI_API_KEY environment variable not set",
114
+ )
115
+ client = self._get_client()
116
+ if not client:
117
+ return HealthReport(
118
+ available=False,
119
+ provider_name=self.provider_name,
120
+ model=self.model_name,
121
+ error="openai library not installed or client init failed",
122
+ )
123
+ t0 = time.monotonic()
124
+ try:
125
+ client.models.list()
126
+ latency = (time.monotonic() - t0) * 1000
127
+ return HealthReport(
128
+ available=True,
129
+ provider_name=self.provider_name,
130
+ model=self.model_name,
131
+ latency_ms=latency,
132
+ )
133
+ except Exception as exc:
134
+ return HealthReport(
135
+ available=False,
136
+ provider_name=self.provider_name,
137
+ model=self.model_name,
138
+ latency_ms=(time.monotonic() - t0) * 1000,
139
+ error=str(exc),
140
+ )
141
+
142
+
@@ -0,0 +1,244 @@
1
+ """
2
+ ActShield Provider Registry
3
+ =============================
4
+ Ordered failover chain of SecurityAIProviders.
5
+ The enforcement layer (PolicyEvaluator) NEVER calls this — it always
6
+ runs deterministic rules regardless of AI availability.
7
+ """
8
+ from __future__ import annotations
9
+
10
+ import logging
11
+ import time
12
+ from typing import Any, Dict, List, Optional
13
+
14
+ from actshield.providers.base import AnalysisResult, HealthReport, SecurityAIProvider
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ class ProviderRegistry:
20
+ """
21
+ Registry of SecurityAIProviders with ordered failover.
22
+
23
+ Usage:
24
+ registry = ProviderRegistry()
25
+ registry.register(OllamaProvider())
26
+ registry.register(OpenAIProvider())
27
+ registry.register(NullProvider()) # Always last as sentinel
28
+
29
+ result = registry.analyze(packet) # None = fail-safe deny advisory
30
+ """
31
+
32
+ def __init__(self) -> None:
33
+ self._providers: List[SecurityAIProvider] = []
34
+
35
+ def register(self, provider: SecurityAIProvider, *, position: Optional[int] = None, is_default: bool = False) -> None:
36
+ """Register a provider. Appends to end unless position or is_default specified."""
37
+ if position is not None:
38
+ self._providers.insert(position, provider)
39
+ elif is_default:
40
+ self._providers.insert(0, provider)
41
+ else:
42
+ self._providers.append(provider)
43
+ logger.debug("Registered provider: %s", provider.provider_name)
44
+
45
+ def unregister(self, provider_name: str) -> bool:
46
+ """Remove provider by name. Returns True if found and removed."""
47
+ before = len(self._providers)
48
+ self._providers = [p for p in self._providers if p.provider_name != provider_name]
49
+ return len(self._providers) < before
50
+
51
+ @property
52
+ def providers(self) -> List[SecurityAIProvider]:
53
+ return list(self._providers)
54
+
55
+ def get_provider(self, name: str) -> Optional[SecurityAIProvider]:
56
+ for p in self._providers:
57
+ if p.provider_name == name:
58
+ return p
59
+ return None
60
+
61
+ def get_active(self) -> Optional[SecurityAIProvider]:
62
+ """Return the first available provider, or None."""
63
+ for p in self._providers:
64
+ if p.is_available:
65
+ return p
66
+ return None
67
+
68
+ def get_active_provider(self) -> SecurityAIProvider:
69
+ """Return active provider or NullProvider."""
70
+ active = self.get_active()
71
+ if active:
72
+ return active
73
+ from actshield.providers.null_provider import NullProvider
74
+ return NullProvider()
75
+
76
+ def set_active_provider(self, name: str) -> None:
77
+ """Move named provider to top of priority chain."""
78
+ norm_name = name.lower().replace("-", "_")
79
+ found = None
80
+ for p in self._providers:
81
+ if p.provider_name.lower().replace("-", "_") == norm_name:
82
+ found = p
83
+ break
84
+ if not found:
85
+ raise ValueError(f"Provider '{name}' not found in registry.")
86
+ self._providers.remove(found)
87
+ self._providers.insert(0, found)
88
+
89
+ def list_providers(self) -> List[Dict[str, Any]]:
90
+ """List all registered providers with status."""
91
+ active = self.get_active_provider()
92
+ res = []
93
+ for p in self._providers:
94
+ is_act = p.provider_name == active.provider_name
95
+ res.append({
96
+ "name": p.provider_name,
97
+ "provider_type": getattr(p, "provider_type", "llm"),
98
+ "is_healthy": p.is_available,
99
+ "is_active": is_act,
100
+ })
101
+ return res
102
+
103
+ def analyze(self, packet: Dict[str, Any]) -> Optional[AnalysisResult]:
104
+ """
105
+ Try each provider in order. Returns first successful AnalysisResult.
106
+ Returns None if all providers fail — callers treat this as fail-safe.
107
+ """
108
+ for provider in self._providers:
109
+ if not provider.is_available:
110
+ logger.debug("Provider %s not available, skipping", provider.provider_name)
111
+ continue
112
+ try:
113
+ t0 = time.monotonic()
114
+ result = provider.analyze(packet)
115
+ if result is not None:
116
+ result.latency_ms = (time.monotonic() - t0) * 1000
117
+ result.provider_used = provider.provider_name
118
+ logger.debug(
119
+ "Provider %s returned risk_score=%.2f in %.1fms",
120
+ provider.provider_name, result.risk_score, result.latency_ms,
121
+ )
122
+ return result
123
+ except Exception as exc: # noqa: BLE001
124
+ logger.warning("Provider %s raised: %s", provider.provider_name, exc)
125
+ continue
126
+ logger.warning("All providers failed or unavailable — returning None (fail-safe)")
127
+ return None
128
+
129
+ def health_check_all(self) -> List[HealthReport]:
130
+ """Run health check on every registered provider."""
131
+ reports = []
132
+ for provider in self._providers:
133
+ try:
134
+ report = provider.health_check()
135
+ except Exception as exc: # noqa: BLE001
136
+ from actshield.providers.base import HealthReport
137
+ report = HealthReport(
138
+ available=False,
139
+ provider_name=provider.provider_name,
140
+ error=str(exc),
141
+ )
142
+ reports.append(report)
143
+ return reports
144
+
145
+ def benchmark(self, packet: Dict[str, Any]) -> List[Dict[str, Any]]:
146
+ """Run analysis on all available providers and return latency stats."""
147
+ results = []
148
+ for provider in self._providers:
149
+ if not provider.is_available:
150
+ results.append({"provider": provider.provider_name, "available": False})
151
+ continue
152
+ t0 = time.monotonic()
153
+ try:
154
+ result = provider.analyze(packet)
155
+ latency = (time.monotonic() - t0) * 1000
156
+ results.append({
157
+ "provider": provider.provider_name,
158
+ "available": True,
159
+ "success": result is not None,
160
+ "latency_ms": round(latency, 1),
161
+ "risk_score": result.risk_score if result else None,
162
+ })
163
+ except Exception as exc: # noqa: BLE001
164
+ results.append({
165
+ "provider": provider.provider_name,
166
+ "available": True,
167
+ "success": False,
168
+ "error": str(exc),
169
+ })
170
+ return results
171
+
172
+ def __len__(self) -> int:
173
+ return len(self._providers)
174
+
175
+ def __repr__(self) -> str:
176
+ names = [p.provider_name for p in self._providers]
177
+ return f"<ProviderRegistry providers={names}>"
178
+
179
+
180
+ # ---------------------------------------------------------------------------
181
+ # Default global registry — populated lazily from environment
182
+ # ---------------------------------------------------------------------------
183
+
184
+ _default_registry: Optional[ProviderRegistry] = None
185
+
186
+
187
+ def _build_default_registry() -> ProviderRegistry:
188
+ """Build the default registry by inspecting environment variables."""
189
+ from actshield.providers.null_provider import NullProvider
190
+
191
+ registry = ProviderRegistry()
192
+
193
+ # Ollama — try first (local, no API key needed)
194
+ try:
195
+ from actshield.providers.ollama_provider import OllamaProvider
196
+ registry.register(OllamaProvider())
197
+ except Exception: # noqa: BLE001
198
+ pass
199
+
200
+ # OpenAI — if OPENAI_API_KEY is set
201
+ try:
202
+ from actshield.providers.openai_provider import OpenAIProvider
203
+ registry.register(OpenAIProvider())
204
+ except Exception: # noqa: BLE001
205
+ pass
206
+
207
+ # Gemini — if GOOGLE_API_KEY is set
208
+ try:
209
+ from actshield.providers.gemini_provider import GeminiProvider
210
+ registry.register(GeminiProvider())
211
+ except Exception: # noqa: BLE001
212
+ pass
213
+
214
+ # Anthropic — if ANTHROPIC_API_KEY is set
215
+ try:
216
+ from actshield.providers.anthropic_provider import AnthropicProvider
217
+ registry.register(AnthropicProvider())
218
+ except Exception: # noqa: BLE001
219
+ pass
220
+
221
+ # Null sentinel — always last
222
+ registry.register(NullProvider())
223
+ return registry
224
+
225
+
226
+ def get_default_registry() -> ProviderRegistry:
227
+ """Return (and lazily build) the process-global provider registry."""
228
+ global _default_registry
229
+ if _default_registry is None:
230
+ _default_registry = _build_default_registry()
231
+ return _default_registry
232
+
233
+
234
+ def reset_default_registry() -> None:
235
+ """Reset the default registry (useful in tests)."""
236
+ global _default_registry
237
+ _default_registry = None
238
+
239
+
240
+ # Alias for clean public SDK naming
241
+ get_provider_registry = get_default_registry
242
+
243
+
244
+
actshield/py.typed ADDED
File without changes