agent_os_kernel 3.1.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 (337) hide show
  1. agent_control_plane/__init__.py +662 -0
  2. agent_control_plane/a2a_adapter.py +543 -0
  3. agent_control_plane/adapter.py +417 -0
  4. agent_control_plane/agent_hibernation.py +394 -0
  5. agent_control_plane/agent_kernel.py +470 -0
  6. agent_control_plane/compliance.py +720 -0
  7. agent_control_plane/constraint_graphs.py +478 -0
  8. agent_control_plane/control_plane.py +854 -0
  9. agent_control_plane/example_executors.py +195 -0
  10. agent_control_plane/execution_engine.py +231 -0
  11. agent_control_plane/flight_recorder.py +846 -0
  12. agent_control_plane/governance_layer.py +435 -0
  13. agent_control_plane/hf_utils.py +563 -0
  14. agent_control_plane/interfaces/__init__.py +55 -0
  15. agent_control_plane/interfaces/kernel_interface.py +361 -0
  16. agent_control_plane/interfaces/plugin_interface.py +497 -0
  17. agent_control_plane/interfaces/protocol_interfaces.py +387 -0
  18. agent_control_plane/kernel_space.py +1009 -0
  19. agent_control_plane/langchain_adapter.py +424 -0
  20. agent_control_plane/lifecycle.py +3113 -0
  21. agent_control_plane/mcp_adapter.py +653 -0
  22. agent_control_plane/ml_safety.py +563 -0
  23. agent_control_plane/multimodal.py +727 -0
  24. agent_control_plane/mute_agent.py +422 -0
  25. agent_control_plane/observability.py +787 -0
  26. agent_control_plane/orchestrator.py +482 -0
  27. agent_control_plane/plugin_registry.py +750 -0
  28. agent_control_plane/policy_engine.py +954 -0
  29. agent_control_plane/process_isolation.py +777 -0
  30. agent_control_plane/shadow_mode.py +310 -0
  31. agent_control_plane/signals.py +493 -0
  32. agent_control_plane/supervisor_agents.py +430 -0
  33. agent_control_plane/time_travel_debugger.py +557 -0
  34. agent_control_plane/tool_registry.py +452 -0
  35. agent_control_plane/vfs.py +697 -0
  36. agent_kernel/__init__.py +69 -0
  37. agent_kernel/analyzer.py +435 -0
  38. agent_kernel/auditor.py +36 -0
  39. agent_kernel/completeness_auditor.py +237 -0
  40. agent_kernel/detector.py +203 -0
  41. agent_kernel/kernel.py +744 -0
  42. agent_kernel/memory_manager.py +85 -0
  43. agent_kernel/models.py +374 -0
  44. agent_kernel/nudge_mechanism.py +263 -0
  45. agent_kernel/outcome_analyzer.py +338 -0
  46. agent_kernel/patcher.py +582 -0
  47. agent_kernel/semantic_analyzer.py +316 -0
  48. agent_kernel/semantic_purge.py +349 -0
  49. agent_kernel/simulator.py +449 -0
  50. agent_kernel/teacher.py +85 -0
  51. agent_kernel/triage.py +152 -0
  52. agent_os/__init__.py +409 -0
  53. agent_os/_adversarial_impl.py +200 -0
  54. agent_os/_circuit_breaker_impl.py +232 -0
  55. agent_os/_mcp_metrics.py +193 -0
  56. agent_os/adversarial.py +20 -0
  57. agent_os/agents_compat.py +490 -0
  58. agent_os/audit_logger.py +135 -0
  59. agent_os/base_agent.py +651 -0
  60. agent_os/circuit_breaker.py +34 -0
  61. agent_os/cli/__init__.py +659 -0
  62. agent_os/cli/cmd_audit.py +128 -0
  63. agent_os/cli/cmd_init.py +152 -0
  64. agent_os/cli/cmd_policy.py +41 -0
  65. agent_os/cli/cmd_policy_gen.py +180 -0
  66. agent_os/cli/cmd_validate.py +258 -0
  67. agent_os/cli/mcp_scan.py +265 -0
  68. agent_os/cli/output.py +192 -0
  69. agent_os/cli/policy_checker.py +330 -0
  70. agent_os/compat.py +74 -0
  71. agent_os/constraint_graph.py +234 -0
  72. agent_os/content_governance.py +140 -0
  73. agent_os/context_budget.py +305 -0
  74. agent_os/credential_redactor.py +224 -0
  75. agent_os/diff_policy.py +89 -0
  76. agent_os/egress_policy.py +159 -0
  77. agent_os/escalation.py +276 -0
  78. agent_os/event_bus.py +124 -0
  79. agent_os/exceptions.py +180 -0
  80. agent_os/execution_context_policy.py +141 -0
  81. agent_os/github_enterprise.py +96 -0
  82. agent_os/health.py +20 -0
  83. agent_os/integrations/__init__.py +279 -0
  84. agent_os/integrations/a2a_adapter.py +279 -0
  85. agent_os/integrations/agent_lightning/__init__.py +30 -0
  86. agent_os/integrations/anthropic_adapter.py +420 -0
  87. agent_os/integrations/autogen_adapter.py +620 -0
  88. agent_os/integrations/base.py +1137 -0
  89. agent_os/integrations/compat.py +229 -0
  90. agent_os/integrations/config.py +98 -0
  91. agent_os/integrations/conversation_guardian.py +957 -0
  92. agent_os/integrations/crewai_adapter.py +467 -0
  93. agent_os/integrations/drift_detector.py +425 -0
  94. agent_os/integrations/dry_run.py +124 -0
  95. agent_os/integrations/escalation.py +582 -0
  96. agent_os/integrations/gemini_adapter.py +364 -0
  97. agent_os/integrations/google_adk_adapter.py +633 -0
  98. agent_os/integrations/guardrails_adapter.py +394 -0
  99. agent_os/integrations/health.py +197 -0
  100. agent_os/integrations/langchain_adapter.py +654 -0
  101. agent_os/integrations/llamafirewall.py +343 -0
  102. agent_os/integrations/llamaindex_adapter.py +188 -0
  103. agent_os/integrations/logging.py +191 -0
  104. agent_os/integrations/maf_adapter.py +631 -0
  105. agent_os/integrations/mistral_adapter.py +365 -0
  106. agent_os/integrations/openai_adapter.py +816 -0
  107. agent_os/integrations/openai_agents_sdk.py +406 -0
  108. agent_os/integrations/policy_compose.py +171 -0
  109. agent_os/integrations/profiling.py +144 -0
  110. agent_os/integrations/pydantic_ai_adapter.py +420 -0
  111. agent_os/integrations/rate_limiter.py +130 -0
  112. agent_os/integrations/rbac.py +143 -0
  113. agent_os/integrations/registry.py +113 -0
  114. agent_os/integrations/scope_guard.py +303 -0
  115. agent_os/integrations/semantic_kernel_adapter.py +769 -0
  116. agent_os/integrations/smolagents_adapter.py +629 -0
  117. agent_os/integrations/templates.py +178 -0
  118. agent_os/integrations/token_budget.py +134 -0
  119. agent_os/integrations/tool_aliases.py +190 -0
  120. agent_os/integrations/webhooks.py +177 -0
  121. agent_os/lite.py +208 -0
  122. agent_os/mcp_gateway.py +385 -0
  123. agent_os/mcp_message_signer.py +273 -0
  124. agent_os/mcp_protocols.py +161 -0
  125. agent_os/mcp_response_scanner.py +232 -0
  126. agent_os/mcp_security.py +924 -0
  127. agent_os/mcp_session_auth.py +231 -0
  128. agent_os/mcp_sliding_rate_limiter.py +184 -0
  129. agent_os/memory_guard.py +409 -0
  130. agent_os/metrics.py +134 -0
  131. agent_os/mute.py +428 -0
  132. agent_os/mute_agent.py +209 -0
  133. agent_os/policies/__init__.py +77 -0
  134. agent_os/policies/async_evaluator.py +275 -0
  135. agent_os/policies/backends.py +670 -0
  136. agent_os/policies/bridge.py +169 -0
  137. agent_os/policies/budget.py +85 -0
  138. agent_os/policies/cli.py +294 -0
  139. agent_os/policies/conflict_resolution.py +270 -0
  140. agent_os/policies/data_classification.py +252 -0
  141. agent_os/policies/evaluator.py +239 -0
  142. agent_os/policies/policy_schema.json +228 -0
  143. agent_os/policies/rate_limiting.py +145 -0
  144. agent_os/policies/schema.py +115 -0
  145. agent_os/policies/shared.py +331 -0
  146. agent_os/prompt_injection.py +694 -0
  147. agent_os/providers.py +182 -0
  148. agent_os/py.typed +0 -0
  149. agent_os/retry.py +81 -0
  150. agent_os/reversibility.py +251 -0
  151. agent_os/sandbox.py +432 -0
  152. agent_os/sandbox_provider.py +140 -0
  153. agent_os/secure_codegen.py +525 -0
  154. agent_os/security_skills.py +538 -0
  155. agent_os/semantic_policy.py +422 -0
  156. agent_os/server/__init__.py +15 -0
  157. agent_os/server/__main__.py +25 -0
  158. agent_os/server/app.py +277 -0
  159. agent_os/server/models.py +104 -0
  160. agent_os/shift_left_metrics.py +130 -0
  161. agent_os/stateless.py +742 -0
  162. agent_os/supervisor.py +148 -0
  163. agent_os/task_outcome.py +148 -0
  164. agent_os/transparency.py +181 -0
  165. agent_os/trust_root.py +128 -0
  166. agent_os_kernel-3.1.0.dist-info/METADATA +1269 -0
  167. agent_os_kernel-3.1.0.dist-info/RECORD +337 -0
  168. agent_os_kernel-3.1.0.dist-info/WHEEL +4 -0
  169. agent_os_kernel-3.1.0.dist-info/entry_points.txt +2 -0
  170. agent_os_kernel-3.1.0.dist-info/licenses/LICENSE +21 -0
  171. agent_os_observability/__init__.py +27 -0
  172. agent_os_observability/dashboards.py +898 -0
  173. agent_os_observability/metrics.py +398 -0
  174. agent_os_observability/server.py +223 -0
  175. agent_os_observability/tracer.py +232 -0
  176. agent_primitives/__init__.py +24 -0
  177. agent_primitives/failures.py +84 -0
  178. agent_primitives/py.typed +0 -0
  179. amb_core/__init__.py +177 -0
  180. amb_core/adapters/__init__.py +57 -0
  181. amb_core/adapters/aws_sqs_broker.py +376 -0
  182. amb_core/adapters/azure_servicebus_broker.py +340 -0
  183. amb_core/adapters/kafka_broker.py +260 -0
  184. amb_core/adapters/nats_broker.py +285 -0
  185. amb_core/adapters/rabbitmq_broker.py +235 -0
  186. amb_core/adapters/redis_broker.py +262 -0
  187. amb_core/broker.py +145 -0
  188. amb_core/bus.py +481 -0
  189. amb_core/cloudevents.py +509 -0
  190. amb_core/dlq.py +345 -0
  191. amb_core/hf_utils.py +536 -0
  192. amb_core/memory_broker.py +410 -0
  193. amb_core/models.py +141 -0
  194. amb_core/persistence.py +529 -0
  195. amb_core/schema.py +294 -0
  196. amb_core/tracing.py +358 -0
  197. atr/__init__.py +640 -0
  198. atr/access.py +348 -0
  199. atr/composition.py +645 -0
  200. atr/decorator.py +357 -0
  201. atr/executor.py +384 -0
  202. atr/health.py +557 -0
  203. atr/hf_utils.py +449 -0
  204. atr/injection.py +422 -0
  205. atr/metrics.py +440 -0
  206. atr/policies.py +403 -0
  207. atr/py.typed +2 -0
  208. atr/registry.py +452 -0
  209. atr/schema.py +480 -0
  210. atr/tools/safe/__init__.py +75 -0
  211. atr/tools/safe/calculator.py +467 -0
  212. atr/tools/safe/datetime_tool.py +443 -0
  213. atr/tools/safe/file_reader.py +402 -0
  214. atr/tools/safe/http_client.py +316 -0
  215. atr/tools/safe/json_parser.py +374 -0
  216. atr/tools/safe/text_tool.py +537 -0
  217. atr/tools/safe/toolkit.py +175 -0
  218. caas/__init__.py +162 -0
  219. caas/api/__init__.py +7 -0
  220. caas/api/server.py +1328 -0
  221. caas/caching.py +834 -0
  222. caas/cli.py +210 -0
  223. caas/conversation.py +223 -0
  224. caas/decay.py +72 -0
  225. caas/detection/__init__.py +9 -0
  226. caas/detection/detector.py +238 -0
  227. caas/enrichment.py +130 -0
  228. caas/gateway/__init__.py +27 -0
  229. caas/gateway/trust_gateway.py +474 -0
  230. caas/hf_utils.py +479 -0
  231. caas/ingestion/__init__.py +23 -0
  232. caas/ingestion/processors.py +253 -0
  233. caas/ingestion/structure_parser.py +188 -0
  234. caas/models.py +356 -0
  235. caas/pragmatic_truth.py +444 -0
  236. caas/routing/__init__.py +10 -0
  237. caas/routing/heuristic_router.py +58 -0
  238. caas/storage/__init__.py +9 -0
  239. caas/storage/store.py +389 -0
  240. caas/triad.py +213 -0
  241. caas/tuning/__init__.py +9 -0
  242. caas/tuning/tuner.py +329 -0
  243. caas/vfs/__init__.py +14 -0
  244. caas/vfs/filesystem.py +452 -0
  245. cmvk/__init__.py +218 -0
  246. cmvk/audit.py +402 -0
  247. cmvk/benchmarks.py +478 -0
  248. cmvk/constitutional.py +904 -0
  249. cmvk/hf_utils.py +301 -0
  250. cmvk/metrics.py +473 -0
  251. cmvk/profiles.py +300 -0
  252. cmvk/py.typed +0 -0
  253. cmvk/types.py +12 -0
  254. cmvk/verification.py +956 -0
  255. emk/__init__.py +89 -0
  256. emk/causal.py +352 -0
  257. emk/hf_utils.py +421 -0
  258. emk/indexer.py +83 -0
  259. emk/py.typed +0 -0
  260. emk/schema.py +204 -0
  261. emk/sleep_cycle.py +347 -0
  262. emk/store.py +281 -0
  263. iatp/__init__.py +166 -0
  264. iatp/attestation.py +461 -0
  265. iatp/cli.py +317 -0
  266. iatp/hf_utils.py +472 -0
  267. iatp/ipc_pipes.py +580 -0
  268. iatp/main.py +412 -0
  269. iatp/models/__init__.py +447 -0
  270. iatp/policy_engine.py +337 -0
  271. iatp/py.typed +2 -0
  272. iatp/recovery.py +321 -0
  273. iatp/security/__init__.py +270 -0
  274. iatp/sidecar/__init__.py +519 -0
  275. iatp/telemetry/__init__.py +164 -0
  276. iatp/tests/__init__.py +1 -0
  277. iatp/tests/test_attestation.py +370 -0
  278. iatp/tests/test_cli.py +131 -0
  279. iatp/tests/test_ed25519_attestation.py +211 -0
  280. iatp/tests/test_models.py +130 -0
  281. iatp/tests/test_policy_engine.py +347 -0
  282. iatp/tests/test_recovery.py +281 -0
  283. iatp/tests/test_security.py +222 -0
  284. iatp/tests/test_sidecar.py +167 -0
  285. iatp/tests/test_telemetry.py +175 -0
  286. mcp_kernel_server/__init__.py +28 -0
  287. mcp_kernel_server/cli.py +274 -0
  288. mcp_kernel_server/resources.py +217 -0
  289. mcp_kernel_server/server.py +564 -0
  290. mcp_kernel_server/tools.py +1174 -0
  291. mute_agent/__init__.py +68 -0
  292. mute_agent/core/__init__.py +1 -0
  293. mute_agent/core/execution_agent.py +166 -0
  294. mute_agent/core/handshake_protocol.py +201 -0
  295. mute_agent/core/reasoning_agent.py +238 -0
  296. mute_agent/knowledge_graph/__init__.py +1 -0
  297. mute_agent/knowledge_graph/graph_elements.py +65 -0
  298. mute_agent/knowledge_graph/multidimensional_graph.py +170 -0
  299. mute_agent/knowledge_graph/subgraph.py +224 -0
  300. mute_agent/listener/__init__.py +43 -0
  301. mute_agent/listener/adapters/__init__.py +31 -0
  302. mute_agent/listener/adapters/base_adapter.py +189 -0
  303. mute_agent/listener/adapters/caas_adapter.py +344 -0
  304. mute_agent/listener/adapters/control_plane_adapter.py +436 -0
  305. mute_agent/listener/adapters/iatp_adapter.py +332 -0
  306. mute_agent/listener/adapters/scak_adapter.py +251 -0
  307. mute_agent/listener/listener.py +610 -0
  308. mute_agent/listener/state_observer.py +436 -0
  309. mute_agent/listener/threshold_config.py +313 -0
  310. mute_agent/super_system/__init__.py +1 -0
  311. mute_agent/super_system/router.py +204 -0
  312. mute_agent/visualization/__init__.py +10 -0
  313. mute_agent/visualization/graph_debugger.py +502 -0
  314. nexus/README.md +60 -0
  315. nexus/__init__.py +51 -0
  316. nexus/arbiter.py +359 -0
  317. nexus/client.py +466 -0
  318. nexus/dmz.py +444 -0
  319. nexus/escrow.py +430 -0
  320. nexus/exceptions.py +286 -0
  321. nexus/pyproject.toml +36 -0
  322. nexus/registry.py +393 -0
  323. nexus/reputation.py +425 -0
  324. nexus/schemas/__init__.py +51 -0
  325. nexus/schemas/compliance.py +276 -0
  326. nexus/schemas/escrow.py +251 -0
  327. nexus/schemas/manifest.py +225 -0
  328. nexus/schemas/receipt.py +208 -0
  329. nexus/tests/__init__.py +0 -0
  330. nexus/tests/conftest.py +146 -0
  331. nexus/tests/test_arbiter.py +192 -0
  332. nexus/tests/test_dmz.py +194 -0
  333. nexus/tests/test_escrow.py +276 -0
  334. nexus/tests/test_exceptions.py +225 -0
  335. nexus/tests/test_registry.py +232 -0
  336. nexus/tests/test_reputation.py +328 -0
  337. nexus/tests/test_schemas.py +295 -0
agent_os/lite.py ADDED
@@ -0,0 +1,208 @@
1
+ # Copyright (c) Microsoft Corporation.
2
+ # Licensed under the MIT License.
3
+ """AGT Lite — Zero-config governance in 3 lines.
4
+
5
+ The full Agent OS is powerful but heavy (530 files, 42s import).
6
+ AGT Lite is the lightweight alternative: single import, inline rules,
7
+ no YAML, no external deps beyond pydantic. Designed for the developer
8
+ who just wants to add basic governance without learning the full stack.
9
+
10
+ Usage:
11
+ from agent_os.lite import govern
12
+
13
+ # One line to create a governance function
14
+ check = govern(allow=["read_file", "web_search"], deny=["execute_code", "delete_file"])
15
+
16
+ # One line to check any action
17
+ check("read_file") # returns True
18
+ check("execute_code") # raises GovernanceViolation
19
+
20
+ # Or use the non-raising version
21
+ check.is_allowed("execute_code") # returns False
22
+
23
+ That's it. No YAML, no PolicyEvaluator, no 42-second import.
24
+ Upgrade to the full stack when you need trust mesh, SRE, or compliance.
25
+ """
26
+
27
+ from __future__ import annotations
28
+
29
+ import re
30
+ import time
31
+ from datetime import datetime, timezone
32
+ from typing import Any
33
+
34
+
35
+ class GovernanceViolation(Exception):
36
+ """Raised when an action is blocked by governance policy."""
37
+
38
+ def __init__(self, action: str, reason: str) -> None:
39
+ self.action = action
40
+ self.reason = reason
41
+ super().__init__(f"Governance violation: '{action}' — {reason}")
42
+
43
+
44
+ class GovernanceDecision:
45
+ """Result of a governance check."""
46
+
47
+ __slots__ = ("action", "allowed", "reason", "timestamp", "latency_ms")
48
+
49
+ def __init__(self, action: str, allowed: bool, reason: str, latency_ms: float) -> None:
50
+ self.action = action
51
+ self.allowed = allowed
52
+ self.reason = reason
53
+ self.timestamp = datetime.now(timezone.utc)
54
+ self.latency_ms = latency_ms
55
+
56
+
57
+ class LiteGovernor:
58
+ """Lightweight, zero-config governance gate.
59
+
60
+ Rules:
61
+ 1. If action is in `deny` list → BLOCKED
62
+ 2. If action matches a `deny_patterns` regex → BLOCKED
63
+ 3. If `allow` list is set and action is NOT in it → BLOCKED
64
+ 4. If content matches `blocked_content` patterns → BLOCKED
65
+ 5. Otherwise → ALLOWED
66
+
67
+ Deny takes priority over allow (fail-secure).
68
+ """
69
+
70
+ def __init__(
71
+ self,
72
+ allow: list[str] | None = None,
73
+ deny: list[str] | None = None,
74
+ deny_patterns: list[str] | None = None,
75
+ blocked_content: list[str] | None = None,
76
+ escalate: list[str] | None = None,
77
+ max_calls: int = 0,
78
+ log: bool = True,
79
+ ) -> None:
80
+ self._allow = set(allow) if allow else None
81
+ self._deny = set(deny or [])
82
+ self._deny_patterns = [re.compile(p) for p in (deny_patterns or [])]
83
+ self._blocked_content = [re.compile(p) for p in (blocked_content or [])]
84
+ self._escalate = set(escalate or [])
85
+ self._max_calls = max_calls
86
+ self._log = log
87
+ self._call_count = 0
88
+ self._audit: list[GovernanceDecision] = []
89
+
90
+ def __call__(self, action: str, content: str = "", **context: Any) -> bool:
91
+ """Check if action is allowed. Raises GovernanceViolation if not."""
92
+ decision = self.evaluate(action, content, **context)
93
+ if not decision.allowed:
94
+ raise GovernanceViolation(action, decision.reason)
95
+ return True
96
+
97
+ def is_allowed(self, action: str, content: str = "", **context: Any) -> bool:
98
+ """Check if action is allowed. Returns bool (non-raising)."""
99
+ return self.evaluate(action, content, **context).allowed
100
+
101
+ def evaluate(self, action: str, content: str = "", **context: Any) -> GovernanceDecision:
102
+ """Evaluate an action against policy. Returns GovernanceDecision."""
103
+ start = time.perf_counter()
104
+
105
+ # Rate limit check
106
+ if self._max_calls > 0:
107
+ self._call_count += 1
108
+ if self._call_count > self._max_calls:
109
+ return self._decide(action, False, f"Rate limit exceeded ({self._max_calls} max)", start)
110
+
111
+ # Deny list (highest priority)
112
+ if action in self._deny:
113
+ return self._decide(action, False, f"Action '{action}' is explicitly denied", start)
114
+
115
+ # Deny patterns
116
+ for pattern in self._deny_patterns:
117
+ if pattern.search(action):
118
+ return self._decide(action, False, f"Action '{action}' matches deny pattern", start)
119
+
120
+ # Content check
121
+ if content:
122
+ for pattern in self._blocked_content:
123
+ if pattern.search(content):
124
+ return self._decide(action, False, "Content matches blocked pattern", start)
125
+
126
+ # Allow list (if set, only listed actions are allowed)
127
+ if self._allow is not None and action not in self._allow:
128
+ return self._decide(action, False, f"Action '{action}' not in allow list", start)
129
+
130
+ return self._decide(action, True, "Allowed by policy", start)
131
+
132
+ @property
133
+ def audit_trail(self) -> list[GovernanceDecision]:
134
+ """Get all governance decisions made."""
135
+ return list(self._audit)
136
+
137
+ @property
138
+ def stats(self) -> dict[str, Any]:
139
+ """Get governance statistics."""
140
+ total = len(self._audit)
141
+ allowed = sum(1 for d in self._audit if d.allowed)
142
+ denied = total - allowed
143
+ avg_latency = (
144
+ sum(d.latency_ms for d in self._audit) / total if total else 0
145
+ )
146
+ return {
147
+ "total": total,
148
+ "allowed": allowed,
149
+ "denied": denied,
150
+ "violation_rate": f"{denied/total*100:.1f}%" if total else "0%",
151
+ "avg_latency_ms": f"{avg_latency:.3f}",
152
+ }
153
+
154
+ def _decide(
155
+ self, action: str, allowed: bool, reason: str, start: float
156
+ ) -> GovernanceDecision:
157
+ latency_ms = (time.perf_counter() - start) * 1000
158
+ decision = GovernanceDecision(action, allowed, reason, latency_ms)
159
+ if self._log:
160
+ self._audit.append(decision)
161
+ return decision
162
+
163
+
164
+ def govern(
165
+ allow: list[str] | None = None,
166
+ deny: list[str] | None = None,
167
+ deny_patterns: list[str] | None = None,
168
+ blocked_content: list[str] | None = None,
169
+ escalate: list[str] | None = None,
170
+ max_calls: int = 0,
171
+ log: bool = True,
172
+ ) -> LiteGovernor:
173
+ """Create a lightweight governance gate.
174
+
175
+ Args:
176
+ allow: Actions to allow (allowlist). If set, only these actions pass.
177
+ deny: Actions to explicitly deny (takes priority over allow).
178
+ deny_patterns: Regex patterns to deny.
179
+ blocked_content: Regex patterns to block in content.
180
+ escalate: Actions that require human approval (logged as denied).
181
+ max_calls: Max total calls before rate limiting (0 = unlimited).
182
+ log: Whether to keep audit trail.
183
+
184
+ Returns:
185
+ A LiteGovernor callable. Use as: `check("action_name")`
186
+
187
+ Examples:
188
+ # Minimal — block dangerous, allow everything else
189
+ check = govern(deny=["execute_code", "delete_file", "ssh_connect"])
190
+
191
+ # Allowlist — only permit specific actions
192
+ check = govern(allow=["read_file", "web_search", "api_call"])
193
+
194
+ # With content filtering
195
+ check = govern(
196
+ allow=["read_file", "web_search"],
197
+ blocked_content=[r'\\b\\d{3}-\\d{2}-\\d{4}\\b'], # SSN
198
+ )
199
+ """
200
+ return LiteGovernor(
201
+ allow=allow,
202
+ deny=deny,
203
+ deny_patterns=deny_patterns,
204
+ blocked_content=blocked_content,
205
+ escalate=escalate,
206
+ max_calls=max_calls,
207
+ log=log,
208
+ )
@@ -0,0 +1,385 @@
1
+ # Copyright (c) Microsoft Corporation.
2
+ # Licensed under the MIT License.
3
+ """
4
+ MCP Security Gateway — Public Preview
5
+
6
+ A governance layer that sits between MCP clients and MCP servers,
7
+ enforcing policy-based controls on all tool calls passing through.
8
+
9
+ Addresses OWASP ASI02 (Tool Misuse & Exploitation) by providing:
10
+ - Tool allow/deny list filtering
11
+ - Parameter sanitization against dangerous patterns
12
+ - Per-agent rate limiting / call budget enforcement
13
+ - Structured audit logging of every tool invocation
14
+ - Human-in-the-loop approval for sensitive tools
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ import json
20
+ import logging
21
+ import re
22
+ import threading
23
+ import time
24
+ from dataclasses import dataclass
25
+ from enum import Enum
26
+ from typing import Any, Callable
27
+
28
+ from agent_os._mcp_metrics import MCPMetrics, MCPMetricsRecorder
29
+ from agent_os.credential_redactor import CredentialRedactor
30
+ from agent_os.integrations.base import GovernancePolicy, PatternType
31
+ from agent_os.mcp_protocols import (
32
+ InMemoryAuditSink,
33
+ InMemoryRateLimitStore,
34
+ MCPAuditSink,
35
+ MCPRateLimitStore,
36
+ )
37
+
38
+ logger = logging.getLogger(__name__)
39
+
40
+
41
+ # ── Built-in dangerous parameter patterns (CE defaults) ─────────────────────
42
+
43
+ _BUILTIN_DANGEROUS_PATTERNS: list[tuple[str, PatternType]] = [
44
+ # PII / sensitive data
45
+ (r"\b\d{3}-\d{2}-\d{4}\b", PatternType.REGEX), # SSN
46
+ (r"\b\d{4}[- ]?\d{4}[- ]?\d{4}[- ]?\d{4}\b", PatternType.REGEX), # credit card
47
+ # Shell injection
48
+ (r";\s*(rm|del|format|mkfs)\b", PatternType.REGEX), # destructive cmds
49
+ (r"\$\(.*\)", PatternType.REGEX), # command substitution
50
+ (r"`[^`]+`", PatternType.REGEX), # backtick execution
51
+ ]
52
+
53
+
54
+ class ApprovalStatus(Enum):
55
+ """Result of a human-approval check."""
56
+
57
+ PENDING = "pending"
58
+ APPROVED = "approved"
59
+ DENIED = "denied"
60
+
61
+
62
+ @dataclass
63
+ class AuditEntry:
64
+ """A single audit-log record for a tool call."""
65
+
66
+ timestamp: float
67
+ agent_id: str
68
+ tool_name: str
69
+ parameters: dict[str, Any]
70
+ allowed: bool
71
+ reason: str
72
+ approval_status: ApprovalStatus | None = None
73
+
74
+ def to_dict(self) -> dict[str, Any]:
75
+ return {
76
+ "timestamp": self.timestamp,
77
+ "agent_id": self.agent_id,
78
+ "tool_name": self.tool_name,
79
+ "parameters": self.parameters,
80
+ "allowed": self.allowed,
81
+ "reason": self.reason,
82
+ "approval_status": self.approval_status.value if self.approval_status else None,
83
+ }
84
+
85
+
86
+ @dataclass
87
+ class GatewayConfig:
88
+ """Configuration returned by ``wrap_mcp_server``."""
89
+
90
+ server_config: dict[str, Any]
91
+ policy_name: str
92
+ allowed_tools: list[str]
93
+ denied_tools: list[str]
94
+ sensitive_tools: list[str]
95
+ rate_limit: int
96
+ builtin_sanitization: bool
97
+
98
+
99
+ class MCPGateway:
100
+ """Security gateway that sits between MCP clients and servers.
101
+
102
+ Enforces governance policies on all tool calls passing through,
103
+ providing defense against tool misuse, data exfiltration, and
104
+ unauthorized access (OWASP ASI02). The gateway redacts persisted audit
105
+ payloads, enforces a per-agent call budget, and fails closed whenever
106
+ policy evaluation or approval hooks raise unexpected errors.
107
+ """
108
+
109
+ def __init__(
110
+ self,
111
+ policy: GovernancePolicy,
112
+ *,
113
+ denied_tools: list[str] | None = None,
114
+ sensitive_tools: list[str] | None = None,
115
+ approval_callback: Callable[[str, str, dict[str, Any]], ApprovalStatus] | None = None,
116
+ enable_builtin_sanitization: bool = True,
117
+ metrics: MCPMetricsRecorder | None = None,
118
+ rate_limit_store: MCPRateLimitStore | None = None,
119
+ audit_sink: MCPAuditSink | None = None,
120
+ clock: Callable[[], float] = time.time,
121
+ ) -> None:
122
+ """
123
+ Args:
124
+ policy: Governance policy defining constraints and thresholds.
125
+ denied_tools: Explicit deny-list — these tools are never exposed.
126
+ sensitive_tools: Tools that require human approval before execution.
127
+ approval_callback: Sync callback invoked for sensitive-tool approval.
128
+ Signature: ``(agent_id, tool_name, params) -> ApprovalStatus``.
129
+ enable_builtin_sanitization: When True, apply built-in dangerous-
130
+ parameter patterns in addition to the policy's blocked_patterns.
131
+ metrics: Optional metrics recorder for gateway events.
132
+ rate_limit_store: Optional persistence backend for per-agent call
133
+ counts.
134
+ audit_sink: Optional sink for persisted audit records.
135
+ clock: Clock used when recording audit timestamps.
136
+ """
137
+ self.policy = policy
138
+ self.denied_tools: list[str] = denied_tools or []
139
+ self.sensitive_tools: list[str] = sensitive_tools or []
140
+ self.approval_callback = approval_callback
141
+ self.enable_builtin_sanitization = enable_builtin_sanitization
142
+ self._metrics = metrics or MCPMetrics()
143
+ self._rate_limit_store = rate_limit_store or InMemoryRateLimitStore()
144
+ self._audit_sink = audit_sink or InMemoryAuditSink()
145
+ self._clock = clock
146
+
147
+ # Per-agent call counters for rate limiting
148
+ self._tracked_agents: set[str] = set()
149
+ self._rate_limit_lock = threading.Lock()
150
+ # Audit log
151
+ self._audit_log: list[AuditEntry] = []
152
+ # Pre-compile built-in patterns
153
+ self._builtin_compiled: list[tuple[str, re.Pattern]] = []
154
+ if enable_builtin_sanitization:
155
+ for pat_str, _ in _BUILTIN_DANGEROUS_PATTERNS:
156
+ self._builtin_compiled.append((pat_str, re.compile(pat_str, re.IGNORECASE)))
157
+
158
+ # ── Core interception ────────────────────────────────────────────────
159
+
160
+ def intercept_tool_call(
161
+ self,
162
+ agent_id: str,
163
+ tool_name: str,
164
+ params: dict[str, Any],
165
+ ) -> tuple[bool, str]:
166
+ """Evaluate a tool call against the gateway's policy stack.
167
+
168
+ Args:
169
+ agent_id: Agent identity attempting the tool invocation.
170
+ tool_name: Tool being requested.
171
+ params: Structured tool parameters to evaluate.
172
+
173
+ Returns:
174
+ ``(allowed, reason)`` — *allowed* is True when the call may
175
+ proceed; *reason* explains the decision.
176
+ """
177
+ stage = "error"
178
+ try:
179
+ allowed, reason, approval, stage = self._evaluate(agent_id, tool_name, params)
180
+ except Exception:
181
+ # Fail closed: deny access on unexpected evaluation errors
182
+ logger.error(
183
+ "MCP Gateway evaluation error — failing closed | agent=%s tool=%s",
184
+ agent_id,
185
+ tool_name,
186
+ exc_info=True,
187
+ )
188
+ allowed, reason, approval = (
189
+ False,
190
+ "Internal gateway error — access denied (fail closed)",
191
+ None,
192
+ )
193
+
194
+ # Record audit entry
195
+ redacted_parameters = CredentialRedactor.redact_data_structure(params)
196
+ entry = AuditEntry(
197
+ timestamp=self._clock(),
198
+ agent_id=agent_id,
199
+ tool_name=tool_name,
200
+ parameters=redacted_parameters,
201
+ allowed=allowed,
202
+ reason=reason,
203
+ approval_status=approval,
204
+ )
205
+ self._audit_log.append(entry)
206
+ self._audit_sink.record(entry.to_dict())
207
+
208
+ if self.policy.log_all_calls:
209
+ logger.info(
210
+ "MCP Gateway audit | agent=%s tool=%s allowed=%s reason=%s",
211
+ agent_id,
212
+ tool_name,
213
+ allowed,
214
+ reason,
215
+ )
216
+
217
+ self._metrics.record_decision(
218
+ allowed=allowed,
219
+ agent_id=agent_id,
220
+ tool_name=tool_name,
221
+ stage=stage,
222
+ )
223
+ if stage == "rate_limit":
224
+ self._metrics.record_rate_limit_hit(agent_id=agent_id, tool_name=tool_name)
225
+
226
+ return allowed, reason
227
+
228
+ # ── Policy evaluation pipeline ───────────────────────────────────────
229
+
230
+ def _evaluate(
231
+ self,
232
+ agent_id: str,
233
+ tool_name: str,
234
+ params: dict[str, Any],
235
+ ) -> tuple[bool, str, ApprovalStatus | None, str]:
236
+ # 1. Deny-list check
237
+ if tool_name in self.denied_tools:
238
+ return False, f"Tool '{tool_name}' is on the deny list", None, "deny_list"
239
+
240
+ # 2. Allow-list check (empty list means all tools allowed)
241
+ if self.policy.allowed_tools and tool_name not in self.policy.allowed_tools:
242
+ return False, f"Tool '{tool_name}' is not on the allow list", None, "allow_list"
243
+
244
+ # 3. Parameter sanitization
245
+ param_text = json.dumps(params, default=str)
246
+
247
+ # 3a. Policy blocked patterns
248
+ matches = self.policy.matches_pattern(param_text)
249
+ if matches:
250
+ return (
251
+ False,
252
+ f"Parameters matched blocked pattern(s): {matches}",
253
+ None,
254
+ "policy_pattern",
255
+ )
256
+
257
+ # 3b. Built-in dangerous patterns
258
+ if self.enable_builtin_sanitization:
259
+ for pat_str, compiled in self._builtin_compiled:
260
+ if compiled.search(param_text):
261
+ return (
262
+ False,
263
+ f"Parameters matched dangerous pattern: {pat_str}",
264
+ None,
265
+ "builtin_pattern",
266
+ )
267
+
268
+ # 4. Rate limiting
269
+ with self._rate_limit_lock:
270
+ count = int(self._rate_limit_store.get_bucket(agent_id) or 0)
271
+ if count >= self.policy.max_tool_calls:
272
+ return (
273
+ False,
274
+ f"Agent '{agent_id}' exceeded call budget ({self.policy.max_tool_calls})",
275
+ None,
276
+ "rate_limit",
277
+ )
278
+
279
+ # Increment call counter (only on successful evaluation past this point)
280
+ self._tracked_agents.add(agent_id)
281
+ self._rate_limit_store.set_bucket(agent_id, count + 1)
282
+
283
+ # 5. Human approval
284
+ if self.policy.require_human_approval or tool_name in self.sensitive_tools:
285
+ if self.approval_callback is not None:
286
+ try:
287
+ status = self.approval_callback(agent_id, tool_name, params)
288
+ except Exception:
289
+ logger.error(
290
+ "Approval callback error — denying access | agent=%s tool=%s",
291
+ agent_id,
292
+ tool_name,
293
+ exc_info=True,
294
+ )
295
+ return (
296
+ False,
297
+ "Approval callback error — access denied (fail closed)",
298
+ None,
299
+ "approval_error",
300
+ )
301
+ else:
302
+ status = ApprovalStatus.PENDING
303
+
304
+ if status == ApprovalStatus.DENIED:
305
+ return False, "Human approval denied", status, "approval_denied"
306
+ if status == ApprovalStatus.PENDING:
307
+ return False, "Awaiting human approval", status, "approval_pending"
308
+ # APPROVED — fall through
309
+ return True, "Approved by human reviewer", status, "approval_granted"
310
+
311
+ return True, "Allowed by policy", None, "allowed"
312
+
313
+ # ── Server wrapping ──────────────────────────────────────────────────
314
+
315
+ @staticmethod
316
+ def wrap_mcp_server(
317
+ server_config: dict[str, Any],
318
+ policy: GovernancePolicy,
319
+ *,
320
+ denied_tools: list[str] | None = None,
321
+ sensitive_tools: list[str] | None = None,
322
+ ) -> GatewayConfig:
323
+ """Produce a ``GatewayConfig`` that layers governance on a server.
324
+
325
+ Args:
326
+ server_config: Raw MCP server configuration to wrap.
327
+ policy: Governance policy to apply to the wrapped server.
328
+ denied_tools: Optional explicit deny-list.
329
+ sensitive_tools: Optional list of tools that require approval.
330
+
331
+ This does not mutate the original *server_config*; it returns a
332
+ new ``GatewayConfig`` object that downstream code can use to
333
+ instantiate a governed MCP proxy.
334
+
335
+ Returns:
336
+ A ``GatewayConfig`` describing the governed server surface.
337
+ """
338
+ return GatewayConfig(
339
+ server_config=dict(server_config),
340
+ policy_name=policy.name,
341
+ allowed_tools=list(policy.allowed_tools),
342
+ denied_tools=list(denied_tools or []),
343
+ sensitive_tools=list(sensitive_tools or []),
344
+ rate_limit=policy.max_tool_calls,
345
+ builtin_sanitization=True,
346
+ )
347
+
348
+ # ── Audit helpers ────────────────────────────────────────────────────
349
+
350
+ @property
351
+ def audit_log(self) -> list[AuditEntry]:
352
+ """Return a copy of the audit log.
353
+
354
+ Returns:
355
+ A shallow copy of the in-memory ``AuditEntry`` list.
356
+ """
357
+ return list(self._audit_log)
358
+
359
+ def get_agent_call_count(self, agent_id: str) -> int:
360
+ """Return the number of calls made by *agent_id*.
361
+
362
+ Args:
363
+ agent_id: Agent identifier to inspect.
364
+
365
+ Returns:
366
+ The number of accepted tool calls recorded for the agent.
367
+ """
368
+ return int(self._rate_limit_store.get_bucket(agent_id) or 0)
369
+
370
+ def reset_agent_budget(self, agent_id: str) -> None:
371
+ """Reset the call counter for *agent_id*.
372
+
373
+ Args:
374
+ agent_id: Agent identifier whose budget should be reset.
375
+ """
376
+ self._tracked_agents.add(agent_id)
377
+ self._rate_limit_store.set_bucket(agent_id, 0)
378
+
379
+ def reset_all_budgets(self) -> None:
380
+ """Reset call counters for every agent.
381
+
382
+ This clears the recorded call count for each tracked agent.
383
+ """
384
+ for agent_id in self._tracked_agents:
385
+ self._rate_limit_store.set_bucket(agent_id, 0)