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,191 @@
1
+ """Security Quality Gate Evaluator for CI/CD Integration (Phase 9).
2
+
3
+ Enforces automated, deterministic security quality gates for ActShield pipelines.
4
+ Evaluates validation runs, campaign results, regression replays, and security posture
5
+ to return machine-readable exit codes (0 for PASS, 1 for FAIL).
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import logging
11
+ from typing import Any, Dict, List, Optional
12
+
13
+ from actshield.gates.gate_models import GateCheckRule, GateStatus, SecurityGateResult
14
+ from actshield.posture.posture_models import SecurityPostureSnapshot
15
+
16
+ logger = logging.getLogger("actshield.gates")
17
+
18
+
19
+ class SecurityGateEvaluator:
20
+ """Evaluates CI/CD quality gate rules deterministically."""
21
+
22
+ def __init__(
23
+ self,
24
+ require_zero_bypasses: bool = True,
25
+ require_zero_unauthorized_db: bool = True,
26
+ require_zero_open_regressions: bool = True,
27
+ require_zero_failed_replays: bool = True,
28
+ min_block_rate: float = 1.0, # 100% block rate expected in secured runs
29
+ min_posture_score: float = 75.0,
30
+ ) -> None:
31
+ self.require_zero_bypasses = require_zero_bypasses
32
+ self.require_zero_unauthorized_db = require_zero_unauthorized_db
33
+ self.require_zero_open_regressions = require_zero_open_regressions
34
+ self.require_zero_failed_replays = require_zero_failed_replays
35
+ self.min_block_rate = min_block_rate
36
+ self.min_posture_score = min_posture_score
37
+
38
+ def evaluate(
39
+ self,
40
+ campaign_name: Optional[str] = None,
41
+ total_attacks: int = 0,
42
+ blocked_attacks: int = 0,
43
+ bypassed_attacks: int = 0,
44
+ unauthorized_db_calls: int = 0,
45
+ open_regressions: int = 0,
46
+ failed_replays: int = 0,
47
+ posture_snapshot: Optional[SecurityPostureSnapshot] = None,
48
+ extra_checks: Optional[List[GateCheckRule]] = None,
49
+ ) -> SecurityGateResult:
50
+ """Run all deterministic gate checks and return a SecurityGateResult."""
51
+ checks: List[GateCheckRule] = []
52
+ failures: List[str] = []
53
+
54
+ # 1. Zero unauthorized DB calls
55
+ if self.require_zero_unauthorized_db:
56
+ passed = (unauthorized_db_calls == 0)
57
+ msg = None if passed else f"Detected {unauthorized_db_calls} unauthorized DB/sink operations."
58
+ if not passed:
59
+ failures.append(msg or "Unauthorized DB calls detected")
60
+ checks.append(
61
+ GateCheckRule(
62
+ rule_name="zero_unauthorized_db_calls",
63
+ description="Ensures zero unauthorized sensitive sink or database calls occurred",
64
+ passed=passed,
65
+ observed_value=unauthorized_db_calls,
66
+ threshold=0,
67
+ failure_message=msg,
68
+ )
69
+ )
70
+
71
+ # 2. Zero bypasses
72
+ if self.require_zero_bypasses:
73
+ passed = (bypassed_attacks == 0)
74
+ msg = None if passed else f"Detected {bypassed_attacks} bypassed offensive attacks."
75
+ if not passed:
76
+ failures.append(msg or "Bypassed attacks detected")
77
+ checks.append(
78
+ GateCheckRule(
79
+ rule_name="zero_bypasses",
80
+ description="Ensures zero offensive attack variants bypassed runtime defenses",
81
+ passed=passed,
82
+ observed_value=bypassed_attacks,
83
+ threshold=0,
84
+ failure_message=msg,
85
+ )
86
+ )
87
+
88
+ # 3. Zero open regressions
89
+ if self.require_zero_open_regressions:
90
+ passed = (open_regressions == 0)
91
+ msg = None if passed else f"Found {open_regressions} unverified/open regression test cases."
92
+ if not passed:
93
+ failures.append(msg or "Open regressions present")
94
+ checks.append(
95
+ GateCheckRule(
96
+ rule_name="zero_open_regressions",
97
+ description="Ensures all regression artifacts have passed validation",
98
+ passed=passed,
99
+ observed_value=open_regressions,
100
+ threshold=0,
101
+ failure_message=msg,
102
+ )
103
+ )
104
+
105
+ # 4. Zero failed replays
106
+ if self.require_zero_failed_replays:
107
+ passed = (failed_replays == 0)
108
+ msg = None if passed else f"Encountered {failed_replays} failed regression replay executions."
109
+ if not passed:
110
+ failures.append(msg or "Failed replays detected")
111
+ checks.append(
112
+ GateCheckRule(
113
+ rule_name="zero_failed_replays",
114
+ description="Ensures all historical attack replays were successfully defended",
115
+ passed=passed,
116
+ observed_value=failed_replays,
117
+ threshold=0,
118
+ failure_message=msg,
119
+ )
120
+ )
121
+
122
+ # 5. Minimum block rate
123
+ if total_attacks > 0:
124
+ block_rate = blocked_attacks / total_attacks
125
+ passed = (block_rate >= self.min_block_rate)
126
+ msg = None if passed else f"Block rate {block_rate:.1%} is below required {self.min_block_rate:.1%}."
127
+ if not passed:
128
+ failures.append(msg or "Insufficient block rate")
129
+ checks.append(
130
+ GateCheckRule(
131
+ rule_name="minimum_block_rate",
132
+ description=f"Ensures block rate meets or exceeds {self.min_block_rate:.1%}",
133
+ passed=passed,
134
+ observed_value=round(block_rate, 4),
135
+ threshold=self.min_block_rate,
136
+ failure_message=msg,
137
+ )
138
+ )
139
+
140
+ # 6. Posture score check (if snapshot provided)
141
+ if posture_snapshot is not None:
142
+ passed = (posture_snapshot.overall_score >= self.min_posture_score)
143
+ msg = None if passed else f"Posture score {posture_snapshot.overall_score:.1f} is below minimum {self.min_posture_score:.1f}."
144
+ if not passed:
145
+ failures.append(msg or "Posture score too low")
146
+ checks.append(
147
+ GateCheckRule(
148
+ rule_name="minimum_posture_score",
149
+ description=f"Ensures security posture rating is at least {self.min_posture_score:.1f}",
150
+ passed=passed,
151
+ observed_value=posture_snapshot.overall_score,
152
+ threshold=self.min_posture_score,
153
+ failure_message=msg,
154
+ )
155
+ )
156
+
157
+ # Extra checks
158
+ if extra_checks:
159
+ for chk in extra_checks:
160
+ checks.append(chk)
161
+ if not chk.passed:
162
+ failures.append(chk.failure_message or f"Custom rule {chk.rule_name} failed")
163
+
164
+ overall_passed = (len(failures) == 0)
165
+ status = GateStatus.PASSED if overall_passed else GateStatus.FAILED
166
+ exit_code = 0 if overall_passed else 1
167
+
168
+ if overall_passed:
169
+ summary = f"CI/CD Security Gate PASSED: All {len(checks)} checks satisfied."
170
+ else:
171
+ summary = f"CI/CD Security Gate FAILED: {len(failures)} check(s) failed: " + "; ".join(failures)
172
+
173
+ return SecurityGateResult(
174
+ status=status,
175
+ exit_code=exit_code,
176
+ campaign_name=campaign_name,
177
+ total_attacks_tested=total_attacks,
178
+ blocked_attacks_count=blocked_attacks,
179
+ bypassed_attacks_count=bypassed_attacks,
180
+ unauthorized_db_calls=unauthorized_db_calls,
181
+ open_regressions_count=open_regressions,
182
+ failed_replays_count=failed_replays,
183
+ checks=checks,
184
+ summary=summary,
185
+ )
186
+
187
+
188
+ # Global singleton instance for easy import
189
+ security_gate = SecurityGateEvaluator()
190
+
191
+
@@ -0,0 +1,13 @@
1
+ """ActShield Security Gateway for live MCP and HTTP interception."""
2
+
3
+ from actshield.gateway.http import HTTPGateway, HTTPInterceptedResponse
4
+ from actshield.gateway.mcp import MCPGateway, MCPMessage
5
+
6
+ __all__ = [
7
+ "MCPGateway",
8
+ "MCPMessage",
9
+ "HTTPGateway",
10
+ "HTTPInterceptedResponse",
11
+ ]
12
+
13
+
@@ -0,0 +1,191 @@
1
+ """Live HTTP Gateway and Reverse-Proxy Interceptor.
2
+
3
+ Intercepts, inspects, and governs outbound HTTP traffic initiated by AI agents:
4
+ - Evaluates endpoint reputation, parameter injection, and APIRIS intelligence signals
5
+ - Automatically captures responses as Context with ContextSource.EXTERNAL_API
6
+ - Blocks malicious command injection / exfiltration payloads before network dispatch.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from dataclasses import dataclass, field
12
+ import json
13
+ from typing import TYPE_CHECKING, Any, Callable, Dict, Optional, Union
14
+ import urllib.parse
15
+ import uuid
16
+
17
+
18
+ if TYPE_CHECKING:
19
+ from actshield.client import ActShield
20
+
21
+ from actshield.context.provenance import ContextSource, ContextTrustLevel
22
+ from actshield.context.taint import TaintState
23
+ from actshield.tools.tool import SensitivityLevel, ToolDefinition, ToolRequest
24
+ from actshield.tracing.events import EventType
25
+
26
+
27
+
28
+ @dataclass
29
+ class HTTPInterceptedResponse:
30
+ """Standardized HTTP response wrapped with ActShield security provenance."""
31
+ status_code: int
32
+ headers: Dict[str, str] = field(default_factory=dict)
33
+ body: Any = None
34
+ context_id: Optional[str] = None
35
+ taint_state: str = "CLEAN"
36
+ trust_level: str = "UNTRUSTED"
37
+ allowed: bool = True
38
+ blocked_reason: Optional[str] = None
39
+
40
+ def json(self) -> Any:
41
+ """Parse body as JSON if string."""
42
+ if isinstance(self.body, (dict, list)):
43
+ return self.body
44
+ if isinstance(self.body, str):
45
+ return json.loads(self.body)
46
+ return self.body
47
+
48
+ @property
49
+ def text(self) -> str:
50
+ """Return body as string."""
51
+ if isinstance(self.body, str):
52
+ return self.body
53
+ return json.dumps(self.body)
54
+
55
+
56
+ class HTTPGateway:
57
+ """Security Gateway interceptor for agent HTTP/REST API communications."""
58
+
59
+ def __init__(
60
+ self,
61
+ guard: ActShield,
62
+ mock_transport: Optional[Callable[[str, str, Dict[str, Any], Any], Dict[str, Any]]] = None,
63
+ ) -> None:
64
+ self.guard = guard
65
+ self._mock_transport = mock_transport
66
+
67
+ def request(
68
+ self,
69
+ method: str,
70
+ url: str,
71
+ headers: Optional[Dict[str, str]] = None,
72
+ json_data: Optional[Any] = None,
73
+ data: Optional[Any] = None,
74
+ params: Optional[Dict[str, Any]] = None,
75
+ required_capability: str = "external_api",
76
+ sensitivity: SensitivityLevel = SensitivityLevel.HIGH,
77
+ ) -> HTTPInterceptedResponse:
78
+ """Intercept and evaluate an outbound HTTP request before network dispatch."""
79
+ method_upper = method.upper()
80
+ parsed_url = urllib.parse.urlparse(url)
81
+ endpoint_name = f"http_{method_upper}_{parsed_url.netloc or 'external'}"
82
+ headers_dict = headers or {}
83
+ payload = json_data if json_data is not None else data
84
+
85
+ # Define tool definition for this HTTP endpoint
86
+ tool_def = ToolDefinition(
87
+ tool_id=f"http_tool_{uuid.uuid4().hex[:8]}",
88
+ name=f"{method_upper} {url}",
89
+ description=f"HTTP {method_upper} request to {url}",
90
+ sensitivity=sensitivity,
91
+ required_capabilities=[required_capability],
92
+ classification="external_api",
93
+ metadata={"url": url, "method": method_upper, "domain": parsed_url.netloc},
94
+ )
95
+
96
+ tool_request = ToolRequest(
97
+ request_id=f"req_http_{uuid.uuid4().hex[:8]}",
98
+ tool_id=tool_def.tool_id,
99
+ tool_name=tool_def.name,
100
+ arguments={"url": url, "method": method_upper, "headers": headers_dict, "payload": payload, "params": params},
101
+ )
102
+
103
+ # 1. Emit tool requested event
104
+ self.guard.emit_event(
105
+ event_type=EventType.TOOL_REQUESTED,
106
+ payload={
107
+ "tool_id": tool_def.tool_id,
108
+ "tool_name": tool_def.name,
109
+ "protocol": "HTTP",
110
+ "method": method_upper,
111
+ "url": url,
112
+ "sensitivity": sensitivity.value,
113
+ },
114
+ )
115
+
116
+ # 2. Evaluate APIRIS API decision intelligence
117
+ apiris_analysis = self.guard.apiris_adapter.analyze(tool_request)
118
+
119
+ # 3. Evaluate deterministic policy
120
+ decision = self.guard.evaluate_tool_invocation(tool_def, tool_request)
121
+
122
+ # If APIRIS flags critical risk (e.g. command injection pattern) or policy blocks
123
+ if decision.is_blocked or apiris_analysis.recommended_action == "BLOCK":
124
+ reason = decision.explanation if decision.is_blocked else "Blocked by APIRIS decision intelligence"
125
+ return HTTPInterceptedResponse(
126
+ status_code=403,
127
+ body={"error": "ActShield HTTP Interception: Access Blocked", "reason": reason},
128
+ allowed=False,
129
+ blocked_reason=reason,
130
+ )
131
+
132
+ # 4. Dispatch HTTP request (using custom mock transport or realistic fallback)
133
+ if self._mock_transport:
134
+ raw_res = self._mock_transport(method_upper, url, headers_dict, payload)
135
+ status_code = raw_res.get("status_code", 200)
136
+ res_headers = raw_res.get("headers", {"Content-Type": "application/json"})
137
+ res_body = raw_res.get("body", {})
138
+ else:
139
+ # Simulated high-fidelity HTTP response
140
+ status_code = 200
141
+ res_headers = {"Content-Type": "application/json", "X-ActShield-Gateway": "Verified"}
142
+ res_body = {
143
+ "status": "SUCCESS",
144
+ "method": method_upper,
145
+ "url": url,
146
+ "data": {"result": f"External API payload response from {url}"},
147
+ }
148
+
149
+ # 5. Automatically tag ingested HTTP response into Context with provenance
150
+ body_str = json.dumps(res_body) if not isinstance(res_body, str) else res_body
151
+ is_tainted = any(indicator in body_str.lower() for indicator in ("override", "system directive", "exfiltrate"))
152
+ taint_state = TaintState.TAINTED if is_tainted else TaintState.CLEAN
153
+
154
+ ctx = self.guard.context(
155
+ data=body_str,
156
+ source=ContextSource.EXTERNAL_API,
157
+ source_uri=url,
158
+ trust_level=ContextTrustLevel.UNTRUSTED,
159
+ taint_state=taint_state,
160
+ metadata={"http_method": method_upper, "status_code": status_code},
161
+ )
162
+
163
+ self.guard.emit_event(
164
+ event_type=EventType.TOOL_COMPLETED,
165
+ context_id=ctx.context_id,
166
+ payload={"url": url, "method": method_upper, "status_code": status_code, "taint_state": taint_state.value},
167
+ )
168
+
169
+ return HTTPInterceptedResponse(
170
+ status_code=status_code,
171
+ headers=res_headers,
172
+ body=res_body,
173
+ context_id=ctx.context_id,
174
+ taint_state=taint_state.value,
175
+ trust_level=ContextTrustLevel.UNTRUSTED.value,
176
+ allowed=True,
177
+ )
178
+
179
+ def get(self, url: str, **kwargs: Any) -> HTTPInterceptedResponse:
180
+ return self.request("GET", url, **kwargs)
181
+
182
+ def post(self, url: str, **kwargs: Any) -> HTTPInterceptedResponse:
183
+ return self.request("POST", url, **kwargs)
184
+
185
+ def put(self, url: str, **kwargs: Any) -> HTTPInterceptedResponse:
186
+ return self.request("PUT", url, **kwargs)
187
+
188
+ def delete(self, url: str, **kwargs: Any) -> HTTPInterceptedResponse:
189
+ return self.request("DELETE", url, **kwargs)
190
+
191
+
@@ -0,0 +1,292 @@
1
+ """Live Model Context Protocol (MCP) Security Gateway.
2
+
3
+ Intercepts and governs JSON-RPC messages between AI Agents and MCP servers:
4
+ - Intercepts 'tools/list', 'tools/call', 'resources/read', 'prompts/get'
5
+ - Enforces capability checks and monotonic authority bounding on MCP tool invocations
6
+ - Automatically tags ingested tool results with ContextSource.EXTERNAL_MCP and trust levels
7
+ - Blocks unauthorized or tainted invocations before reaching the upstream MCP server.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from dataclasses import dataclass, field
13
+ import json
14
+ from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Union
15
+ import uuid
16
+
17
+
18
+ if TYPE_CHECKING:
19
+ from actshield.client import ActShield
20
+
21
+ from actshield.context.provenance import ContextSource, ContextTrustLevel
22
+ from actshield.context.taint import TaintState
23
+ from actshield.tools.tool import SensitivityLevel, ToolDefinition, ToolRequest
24
+ from actshield.tracing.events import EventType
25
+
26
+
27
+
28
+ @dataclass
29
+ class MCPMessage:
30
+ """Represents a standardized Model Context Protocol JSON-RPC message."""
31
+ jsonrpc: str = "2.0"
32
+ id: Optional[Union[str, int]] = None
33
+ method: str = ""
34
+ params: Dict[str, Any] = field(default_factory=dict)
35
+ result: Optional[Any] = None
36
+ error: Optional[Dict[str, Any]] = None
37
+
38
+ def to_dict(self) -> Dict[str, Any]:
39
+ d: Dict[str, Any] = {"jsonrpc": self.jsonrpc}
40
+ if self.id is not None:
41
+ d["id"] = self.id
42
+ if self.method:
43
+ d["method"] = self.method
44
+ if self.params:
45
+ d["params"] = self.params
46
+ if self.result is not None:
47
+ d["result"] = self.result
48
+ if self.error is not None:
49
+ d["error"] = self.error
50
+ return d
51
+
52
+ def to_json(self) -> str:
53
+ return json.dumps(self.to_dict())
54
+
55
+ @classmethod
56
+ def from_dict(cls, data: Dict[str, Any]) -> "MCPMessage":
57
+ return cls(
58
+ jsonrpc=data.get("jsonrpc", "2.0"),
59
+ id=data.get("id"),
60
+ method=data.get("method", ""),
61
+ params=data.get("params", {}),
62
+ result=data.get("result"),
63
+ error=data.get("error"),
64
+ )
65
+
66
+ @classmethod
67
+ def from_json(cls, json_str: str) -> "MCPMessage":
68
+ return cls.from_dict(json.loads(json_str))
69
+
70
+
71
+ class MCPGateway:
72
+ """Security Gateway interceptor for Model Context Protocol (MCP) traffic."""
73
+
74
+ def __init__(
75
+ self,
76
+ guard: ActShield,
77
+ server_name: str = "upstream-mcp-server",
78
+ server_uri: str = "mcp://gateway.internal/v1",
79
+ default_trust_level: ContextTrustLevel = ContextTrustLevel.UNTRUSTED,
80
+ ) -> None:
81
+ self.guard = guard
82
+ self.server_name = server_name
83
+ self.server_uri = server_uri
84
+ self.default_trust_level = default_trust_level
85
+ self._registered_tools: Dict[str, Dict[str, Any]] = {}
86
+ self._upstream_handlers: Dict[str, Callable[[Dict[str, Any]], Any]] = {}
87
+
88
+ def register_mcp_tool(
89
+ self,
90
+ name: str,
91
+ description: str,
92
+ handler: Callable[[Dict[str, Any]], Any],
93
+ required_capabilities: Optional[List[str]] = None,
94
+ sensitivity: SensitivityLevel = SensitivityLevel.MEDIUM,
95
+ input_schema: Optional[Dict[str, Any]] = None,
96
+ ) -> None:
97
+ """Register an upstream MCP tool with ActShield security governance."""
98
+ caps = required_capabilities or [name]
99
+ self._registered_tools[name] = {
100
+ "name": name,
101
+ "description": description,
102
+ "inputSchema": input_schema or {"type": "object", "properties": {}},
103
+ "sensitivity": sensitivity,
104
+ "required_capabilities": caps,
105
+ }
106
+ self._upstream_handlers[name] = handler
107
+
108
+ # Register corresponding protected tool definition in ActShield
109
+ tool_def = ToolDefinition(
110
+ tool_id=f"mcp_tool_{name}",
111
+ name=name,
112
+ description=description,
113
+ sensitivity=sensitivity,
114
+ required_capabilities=caps,
115
+ classification="mcp",
116
+ metadata={"mcp_server": self.server_name, "server_uri": self.server_uri},
117
+ )
118
+ self.guard.register_tool(tool_def)
119
+
120
+ def handle_message(self, message: Union[str, Dict[str, Any], MCPMessage]) -> MCPMessage:
121
+ """Process incoming MCP JSON-RPC message, enforcing ActShield security policies."""
122
+ if isinstance(message, str):
123
+ msg = MCPMessage.from_json(message)
124
+ elif isinstance(message, dict):
125
+ msg = MCPMessage.from_dict(message)
126
+ else:
127
+ msg = message
128
+
129
+ msg_id = msg.id or str(uuid.uuid4())
130
+ method = msg.method
131
+
132
+ if method == "tools/list":
133
+ return self._handle_tools_list(msg_id)
134
+ elif method == "tools/call":
135
+ return self._handle_tools_call(msg_id, msg.params)
136
+ elif method == "resources/read":
137
+ return self._handle_resources_read(msg_id, msg.params)
138
+ elif method == "ping":
139
+ return MCPMessage(id=msg_id, result={"status": "pong", "gateway": "ActShield MCP Gateway"})
140
+ else:
141
+ return MCPMessage(
142
+ id=msg_id,
143
+ error={
144
+ "code": -32601,
145
+ "message": f"Method '{method}' not supported by ActShield MCP Gateway",
146
+ },
147
+ )
148
+
149
+ def _handle_tools_list(self, msg_id: Union[str, int]) -> MCPMessage:
150
+ """Return list of available governed MCP tools."""
151
+ tools_list = []
152
+ for tname, tinfo in self._registered_tools.items():
153
+ tools_list.append({
154
+ "name": tname,
155
+ "description": tinfo["description"],
156
+ "inputSchema": tinfo["inputSchema"],
157
+ })
158
+ return MCPMessage(id=msg_id, result={"tools": tools_list})
159
+
160
+ def _handle_tools_call(self, msg_id: Union[str, int], params: Dict[str, Any]) -> MCPMessage:
161
+ """Intercept and evaluate MCP tool call before execution."""
162
+ tool_name = params.get("name", "")
163
+ tool_args = params.get("arguments", {})
164
+
165
+ if tool_name not in self._registered_tools:
166
+ return MCPMessage(
167
+ id=msg_id,
168
+ error={
169
+ "code": -32602,
170
+ "message": f"Tool '{tool_name}' not found in MCP registry",
171
+ },
172
+ )
173
+
174
+ tinfo = self._registered_tools[tool_name]
175
+ tool_def = self.guard.tool_registry.get(f"mcp_tool_{tool_name}") or self.guard.tool_registry.get(tool_name)
176
+ if not tool_def:
177
+ tool_def = ToolDefinition(
178
+ tool_id=f"mcp_tool_{tool_name}",
179
+ name=tool_name,
180
+ description=tinfo["description"],
181
+ sensitivity=tinfo["sensitivity"],
182
+ required_capabilities=tinfo["required_capabilities"],
183
+ )
184
+
185
+ acting_agent = params.get("_agent_id") or params.get("agent_id")
186
+ request = ToolRequest(
187
+ request_id=f"req_mcp_{uuid.uuid4().hex[:8]}",
188
+ tool_id=tool_def.tool_id,
189
+ tool_name=tool_name,
190
+ arguments=tool_args,
191
+ agent_id=acting_agent,
192
+ )
193
+
194
+ # Emit gateway interception event
195
+ self.guard.emit_event(
196
+ event_type=EventType.TOOL_REQUESTED,
197
+ payload={
198
+ "tool_id": tool_def.tool_id,
199
+ "tool_name": tool_name,
200
+ "protocol": "MCP",
201
+ "arguments": tool_args,
202
+ "sensitivity": tool_def.sensitivity.value,
203
+ },
204
+ )
205
+
206
+ # Evaluate deterministic security policy
207
+ decision = self.guard.evaluate_tool_invocation(tool_def, request)
208
+
209
+ if decision.is_blocked:
210
+ return MCPMessage(
211
+ id=msg_id,
212
+ error={
213
+ "code": -32000,
214
+ "message": f"ActShield Security Block: [{decision.reason_code}] {decision.explanation}",
215
+ "data": {
216
+ "decision": decision.action.value,
217
+ "risk_score": decision.risk_score,
218
+ "reason_code": decision.reason_code,
219
+ "explanation": decision.explanation,
220
+ },
221
+ },
222
+ )
223
+
224
+ # Execute upstream tool safely
225
+ handler = self._upstream_handlers[tool_name]
226
+ try:
227
+ raw_output = handler(tool_args)
228
+ except Exception as e:
229
+ return MCPMessage(
230
+ id=msg_id,
231
+ error={"code": -32603, "message": f"Upstream execution error: {str(e)}"},
232
+ )
233
+
234
+ # Automatically ingest result into Context with provenance and trust tracking
235
+ content_str = json.dumps(raw_output) if not isinstance(raw_output, str) else raw_output
236
+ is_tainted = any(indicator in content_str.lower() for indicator in ("override", "system directive", "exfiltrate", "ignore previous"))
237
+ taint_level = TaintState.TAINTED if is_tainted else TaintState.CLEAN
238
+
239
+ ctx = self.guard.context(
240
+ data=content_str,
241
+ source=ContextSource.EXTERNAL_MCP,
242
+ source_uri=f"{self.server_uri}/tools/{tool_name}",
243
+ trust_level=self.default_trust_level,
244
+ taint_state=taint_level,
245
+ metadata={"mcp_tool": tool_name, "mcp_server": self.server_name},
246
+ )
247
+
248
+ # Emit tool completed event
249
+ self.guard.emit_event(
250
+ event_type=EventType.TOOL_COMPLETED,
251
+ context_id=ctx.context_id,
252
+ payload={
253
+ "tool_name": tool_name,
254
+ "status": "SUCCESS",
255
+ "taint_state": taint_level.value,
256
+ "trust_level": self.default_trust_level.value,
257
+ },
258
+ )
259
+
260
+ return MCPMessage(
261
+ id=msg_id,
262
+ result={
263
+ "content": [{"type": "text", "text": content_str}],
264
+ "_actshield": {
265
+ "context_id": ctx.context_id,
266
+ "taint_state": taint_level.value,
267
+ "trust_level": self.default_trust_level.value,
268
+ "decision": decision.action.value,
269
+ },
270
+ },
271
+ )
272
+
273
+ def _handle_resources_read(self, msg_id: Union[str, int], params: Dict[str, Any]) -> MCPMessage:
274
+ """Handle MCP resource reading with provenance tagging."""
275
+ uri = params.get("uri", "")
276
+ ctx = self.guard.context(
277
+ data=f"MCP Resource content from {uri}",
278
+ source=ContextSource.EXTERNAL_MCP,
279
+ source_uri=uri,
280
+ trust_level=self.default_trust_level,
281
+ taint_state=TaintState.UNTRUSTED,
282
+ )
283
+ return MCPMessage(
284
+ id=msg_id,
285
+ result={
286
+ "contents": [{"uri": uri, "text": ctx.data}],
287
+ "_actshield": {"context_id": ctx.context_id, "taint_state": ctx.taint_state.value},
288
+ },
289
+ )
290
+
291
+
292
+