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
@@ -0,0 +1,224 @@
1
+ # Copyright (c) Microsoft Corporation.
2
+ # Licensed under the MIT License.
3
+ """Credential redaction helpers for MCP audit and response safety."""
4
+
5
+ from __future__ import annotations
6
+
7
+ import logging
8
+ import re
9
+ from dataclasses import dataclass
10
+ from typing import Any
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+ REDACTED_PLACEHOLDER = "[REDACTED]"
15
+
16
+
17
+ @dataclass(frozen=True)
18
+ class CredentialPattern:
19
+ """A named credential detection pattern."""
20
+
21
+ name: str
22
+ pattern: re.Pattern[str]
23
+
24
+
25
+ @dataclass(frozen=True)
26
+ class CredentialMatch:
27
+ """A credential-like value detected in text."""
28
+
29
+ name: str
30
+ matched_text: str
31
+
32
+
33
+ class CredentialRedactor:
34
+ """Detect and redact credential-like material in strings and nested objects.
35
+
36
+ Use this helper before persisting audit payloads or returning tool output to
37
+ callers. The class operates on plain strings as well as nested dictionaries,
38
+ lists, and tuples, replacing detected secret values with a stable
39
+ placeholder.
40
+ """
41
+
42
+ # Python's stdlib ``re`` does not support per-pattern timeouts. These
43
+ # patterns are kept simple and anchored to avoid pathological backtracking.
44
+ PATTERNS: tuple[CredentialPattern, ...] = (
45
+ CredentialPattern(
46
+ name="OpenAI API key",
47
+ pattern=re.compile(r"\bsk-[A-Za-z0-9][A-Za-z0-9_-]{18,}\b"),
48
+ ),
49
+ CredentialPattern(
50
+ name="GitHub token",
51
+ pattern=re.compile(r"\b(?:ghp|ghs)_[A-Za-z0-9]{20,}\b"),
52
+ ),
53
+ CredentialPattern(
54
+ name="AWS access key",
55
+ pattern=re.compile(r"\bAKIA[A-Z0-9]{16}\b"),
56
+ ),
57
+ CredentialPattern(
58
+ name="Azure key",
59
+ pattern=re.compile(
60
+ r"(?i)(?:accountkey|sharedaccesskey|azure[_-]?key)\s*[:=]\s*[A-Za-z0-9+/=]{20,}"
61
+ ),
62
+ ),
63
+ CredentialPattern(
64
+ name="Bearer token",
65
+ pattern=re.compile(r"\bBearer\s+[A-Za-z0-9._\-+/=]{16,}\b"),
66
+ ),
67
+ CredentialPattern(
68
+ name="PEM private key",
69
+ pattern=re.compile(
70
+ r"-----BEGIN\s+(?P<label>(?:RSA\s+|EC\s+|OPENSSH\s+)?PRIVATE\s+KEY)-----"
71
+ r"[\s\S]*?"
72
+ r"-----END\s+(?P=label)-----",
73
+ re.DOTALL,
74
+ ),
75
+ ),
76
+ CredentialPattern(
77
+ name="Connection string secret",
78
+ pattern=re.compile(
79
+ r"(?i)\b(?:password|pwd|accountkey|sharedaccesssignature)\s*=\s*[^;\s]{4,}"
80
+ ),
81
+ ),
82
+ CredentialPattern(
83
+ name="Basic auth secret",
84
+ pattern=re.compile(
85
+ r"(?i)(?:\bBasic\s+[A-Za-z0-9+/=]{8,}\b|\b[a-z][a-z0-9+.-]*://[^/\s:@]+:[^@\s/]+@)"
86
+ ),
87
+ ),
88
+ CredentialPattern(
89
+ name="JWT",
90
+ pattern=re.compile(r"\beyJ[A-Za-z0-9_-]{6,}\.[A-Za-z0-9._-]{6,}\.[A-Za-z0-9._-]{6,}\b"),
91
+ ),
92
+ CredentialPattern(
93
+ name="Generic API secret",
94
+ pattern=re.compile(
95
+ r"(?i)\b(?:api[_-]?key|client[_-]?secret|secret|token)\b\s*[:=]\s*['\"]?[^\s'\";]{6,}"
96
+ ),
97
+ ),
98
+ )
99
+
100
+ @classmethod
101
+ def redact(cls, value: str | None) -> str:
102
+ """Redact credential-like values from a string.
103
+
104
+ Args:
105
+ value: String content that may contain credential-like material.
106
+
107
+ Returns:
108
+ A string with each detected credential replaced by
109
+ ``REDACTED_PLACEHOLDER``. Empty input returns an empty string.
110
+ """
111
+ if not value:
112
+ return ""
113
+
114
+ result = value
115
+ redaction_count = 0
116
+ for credential_pattern in cls.PATTERNS:
117
+ updated, count = credential_pattern.pattern.subn(REDACTED_PLACEHOLDER, result)
118
+ if count:
119
+ redaction_count += count
120
+ result = updated
121
+
122
+ if redaction_count:
123
+ logger.info("Credential redaction applied to %s value(s)", redaction_count)
124
+
125
+ return result
126
+
127
+ @classmethod
128
+ def redact_mapping(cls, mapping: dict[str, Any] | None) -> dict[str, Any]:
129
+ """Redact all nested values in a mapping.
130
+
131
+ Args:
132
+ mapping: A possibly nested mapping containing strings, lists,
133
+ tuples, or dictionaries.
134
+
135
+ Returns:
136
+ A new mapping with nested strings redacted recursively. Empty input
137
+ returns an empty dictionary.
138
+ """
139
+ if not mapping:
140
+ return {}
141
+ return {key: cls.redact_data_structure(value) for key, value in mapping.items()}
142
+
143
+ @classmethod
144
+ def redact_dictionary(cls, mapping: dict[str, Any] | None) -> dict[str, Any]:
145
+ """Compatibility alias for dictionary redaction.
146
+
147
+ Args:
148
+ mapping: Dictionary-like content to redact.
149
+
150
+ Returns:
151
+ The redacted mapping produced by :meth:`redact_mapping`.
152
+ """
153
+ return cls.redact_mapping(mapping)
154
+
155
+ @classmethod
156
+ def redact_data_structure(cls, value: Any) -> Any:
157
+ """Recursively redact nested strings in dicts, lists, and tuples.
158
+
159
+ Args:
160
+ value: Any Python value that may contain nested strings.
161
+
162
+ Returns:
163
+ A value of the same general shape with strings redacted in place of
164
+ their original secret-bearing content.
165
+ """
166
+ if isinstance(value, str):
167
+ return cls.redact(value)
168
+ if isinstance(value, dict):
169
+ return {key: cls.redact_data_structure(item) for key, item in value.items()}
170
+ if isinstance(value, list):
171
+ return [cls.redact_data_structure(item) for item in value]
172
+ if isinstance(value, tuple):
173
+ return tuple(cls.redact_data_structure(item) for item in value)
174
+ return value
175
+
176
+ @classmethod
177
+ def contains_credentials(cls, value: str | None) -> bool:
178
+ """Return whether a string contains any known credential pattern.
179
+
180
+ Args:
181
+ value: String content to inspect.
182
+
183
+ Returns:
184
+ ``True`` when at least one credential pattern matches, otherwise
185
+ ``False``.
186
+ """
187
+ return bool(cls.find_matches(value))
188
+
189
+ @classmethod
190
+ def detect_credential_types(cls, value: str | None) -> list[str]:
191
+ """Return the names of detected credential patterns.
192
+
193
+ Args:
194
+ value: String content to inspect.
195
+
196
+ Returns:
197
+ A de-duplicated list of credential type labels in detection order.
198
+ """
199
+ return list(dict.fromkeys(match.name for match in cls.find_matches(value)))
200
+
201
+ @classmethod
202
+ def find_matches(cls, value: str | None) -> list[CredentialMatch]:
203
+ """Return all credential-like matches found in a string.
204
+
205
+ Args:
206
+ value: String content to inspect.
207
+
208
+ Returns:
209
+ A list of ``CredentialMatch`` records describing each detected
210
+ credential-like span. Empty input returns an empty list.
211
+ """
212
+ if not value:
213
+ return []
214
+
215
+ matches: list[CredentialMatch] = []
216
+ for credential_pattern in cls.PATTERNS:
217
+ for match in credential_pattern.pattern.finditer(value):
218
+ matches.append(
219
+ CredentialMatch(
220
+ name=credential_pattern.name,
221
+ matched_text=match.group(0),
222
+ )
223
+ )
224
+ return matches
@@ -0,0 +1,89 @@
1
+ # Copyright (c) Microsoft Corporation.
2
+ # Licensed under the MIT License.
3
+ """DiffPolicy rule type for git change scope enforcement.
4
+
5
+ Enforces constraints on agent-authored code changes:
6
+ file count limits, line count limits, and path restrictions.
7
+
8
+ Example::
9
+
10
+ policy = DiffPolicy(max_files=20, max_lines=400, blocked_paths=["*.env", "secrets/**"])
11
+ result = policy.evaluate(changed_files)
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import fnmatch
17
+ from dataclasses import dataclass, field
18
+
19
+
20
+ @dataclass
21
+ class DiffFile:
22
+ """A file in a diff."""
23
+
24
+ path: str
25
+ additions: int = 0
26
+ deletions: int = 0
27
+
28
+
29
+ @dataclass
30
+ class DiffPolicyResult:
31
+ """Result of evaluating a diff against policy."""
32
+
33
+ allowed: bool
34
+ violations: list[str] = field(default_factory=list)
35
+
36
+
37
+ @dataclass
38
+ class DiffPolicy:
39
+ """Policy rules for git change scope enforcement.
40
+
41
+ Attributes:
42
+ max_files: Maximum number of files changed.
43
+ max_lines: Maximum total lines changed (additions + deletions).
44
+ allowed_paths: Glob patterns for allowed file paths. Empty = all allowed.
45
+ blocked_paths: Glob patterns for blocked file paths.
46
+ """
47
+
48
+ max_files: int | None = None
49
+ max_lines: int | None = None
50
+ allowed_paths: list[str] = field(default_factory=list)
51
+ blocked_paths: list[str] = field(default_factory=list)
52
+
53
+ def evaluate(self, files: list[DiffFile]) -> DiffPolicyResult:
54
+ """Evaluate a set of changed files against this policy.
55
+
56
+ Args:
57
+ files: List of DiffFile objects representing the changes.
58
+
59
+ Returns:
60
+ DiffPolicyResult with allowed status and any violations.
61
+ """
62
+ violations = []
63
+
64
+ # Check file count
65
+ if self.max_files is not None and len(files) > self.max_files:
66
+ violations.append(f"files: {len(files)}/{self.max_files}")
67
+
68
+ # Check total lines
69
+ if self.max_lines is not None:
70
+ total_lines = sum(f.additions + f.deletions for f in files)
71
+ if total_lines > self.max_lines:
72
+ violations.append(f"lines: {total_lines}/{self.max_lines}")
73
+
74
+ # Check path restrictions
75
+ for f in files:
76
+ # Blocked paths
77
+ for pattern in self.blocked_paths:
78
+ if fnmatch.fnmatch(f.path, pattern):
79
+ violations.append(f"blocked: {f.path} matches {pattern}")
80
+
81
+ # Allowed paths (if set, file must match at least one)
82
+ if self.allowed_paths:
83
+ if not any(fnmatch.fnmatch(f.path, p) for p in self.allowed_paths):
84
+ violations.append(f"not_allowed: {f.path}")
85
+
86
+ return DiffPolicyResult(
87
+ allowed=len(violations) == 0,
88
+ violations=violations,
89
+ )
@@ -0,0 +1,159 @@
1
+ # Copyright (c) Microsoft Corporation.
2
+ # Licensed under the MIT License.
3
+ """Network egress policy enforcement for MCP gateway.
4
+
5
+ Provides domain-level egress control so that agent tool calls can only
6
+ reach pre-approved external endpoints. Wildcard domains are supported
7
+ (e.g. ``*.openai.com`` matches ``api.openai.com``).
8
+ """
9
+ from __future__ import annotations
10
+
11
+ import fnmatch
12
+ from dataclasses import dataclass, field
13
+ from typing import Optional
14
+ from urllib.parse import urlparse
15
+
16
+
17
+ @dataclass
18
+ class EgressRule:
19
+ """A single egress control rule."""
20
+
21
+ domain: str
22
+ ports: list[int] = field(default_factory=lambda: [443])
23
+ protocol: str = "tcp"
24
+ action: str = "allow"
25
+
26
+ def __post_init__(self) -> None:
27
+ if self.action not in ("allow", "deny"):
28
+ raise ValueError(f"action must be 'allow' or 'deny', got {self.action!r}")
29
+ if self.protocol not in ("tcp", "udp"):
30
+ raise ValueError(f"protocol must be 'tcp' or 'udp', got {self.protocol!r}")
31
+
32
+ def matches(self, hostname: str, port: int, protocol: str) -> bool:
33
+ """Return *True* if this rule matches the given endpoint."""
34
+ if protocol != self.protocol:
35
+ return False
36
+ if port not in self.ports:
37
+ return False
38
+ return fnmatch.fnmatch(hostname.lower(), self.domain.lower())
39
+
40
+
41
+ @dataclass
42
+ class EgressDecision:
43
+ """Result of an egress policy check."""
44
+
45
+ allowed: bool
46
+ matched_rule: Optional[EgressRule]
47
+ reason: str
48
+
49
+
50
+ class EgressPolicy:
51
+ """Network egress enforcement for agent tool calls.
52
+
53
+ Rules are evaluated in insertion order; the first match wins.
54
+ If no rule matches, ``default_action`` is applied.
55
+ """
56
+
57
+ def __init__(self, default_action: str = "deny") -> None:
58
+ if default_action not in ("allow", "deny"):
59
+ raise ValueError(
60
+ f"default_action must be 'allow' or 'deny', got {default_action!r}"
61
+ )
62
+ self.rules: list[EgressRule] = []
63
+ self.default_action = default_action
64
+
65
+ def add_rule(
66
+ self,
67
+ domain: str,
68
+ ports: list[int],
69
+ protocol: str = "tcp",
70
+ action: str = "allow",
71
+ ) -> EgressRule:
72
+ """Create and append an :class:`EgressRule`."""
73
+ rule = EgressRule(
74
+ domain=domain, ports=ports, protocol=protocol, action=action
75
+ )
76
+ self.rules.append(rule)
77
+ return rule
78
+
79
+ def load_from_yaml(self, yaml_str: str) -> None:
80
+ """Parse a simple YAML-like egress policy and add rules.
81
+
82
+ Supported format (stdlib-only, no PyYAML dependency)::
83
+
84
+ rules:
85
+ - domain: "*.openai.com"
86
+ ports: [443]
87
+ protocol: tcp
88
+ action: allow
89
+ """
90
+ current: dict[str, str] = {}
91
+ for raw_line in yaml_str.splitlines():
92
+ line = raw_line.strip()
93
+ if not line or line.startswith("#"):
94
+ continue
95
+ if line == "rules:" or line == "rules":
96
+ continue
97
+ if line.startswith("- domain:"):
98
+ if current:
99
+ self._add_from_dict(current)
100
+ current = {}
101
+ current["domain"] = self._unquote(line.split(":", 1)[1])
102
+ elif line.startswith("domain:"):
103
+ current["domain"] = self._unquote(line.split(":", 1)[1])
104
+ elif line.startswith("ports:"):
105
+ current["ports"] = line.split(":", 1)[1].strip()
106
+ elif line.startswith("protocol:"):
107
+ current["protocol"] = self._unquote(line.split(":", 1)[1])
108
+ elif line.startswith("action:"):
109
+ current["action"] = self._unquote(line.split(":", 1)[1])
110
+ if current:
111
+ self._add_from_dict(current)
112
+
113
+ def check(
114
+ self, hostname: str, port: int, protocol: str = "tcp"
115
+ ) -> EgressDecision:
116
+ """Check whether an outbound connection is permitted."""
117
+ for rule in self.rules:
118
+ if rule.matches(hostname, port, protocol):
119
+ allowed = rule.action == "allow"
120
+ return EgressDecision(
121
+ allowed=allowed,
122
+ matched_rule=rule,
123
+ reason=f"matched rule for {rule.domain} -> {rule.action}",
124
+ )
125
+ allowed = self.default_action == "allow"
126
+ return EgressDecision(
127
+ allowed=allowed,
128
+ matched_rule=None,
129
+ reason=f"no matching rule; default action is {self.default_action}",
130
+ )
131
+
132
+ def check_url(self, url: str) -> EgressDecision:
133
+ """Convenience wrapper that extracts host/port from a URL."""
134
+ parsed = urlparse(url)
135
+ hostname = parsed.hostname or ""
136
+ port = parsed.port
137
+ if port is None:
138
+ port = 443 if parsed.scheme == "https" else 80
139
+ return self.check(hostname, port)
140
+
141
+ # ------------------------------------------------------------------
142
+ # Internal helpers
143
+ # ------------------------------------------------------------------
144
+
145
+ @staticmethod
146
+ def _unquote(value: str) -> str:
147
+ return value.strip().strip("\"'")
148
+
149
+ @staticmethod
150
+ def _parse_ports(raw: str) -> list[int]:
151
+ raw = raw.strip().strip("[]")
152
+ return [int(p.strip()) for p in raw.split(",") if p.strip()]
153
+
154
+ def _add_from_dict(self, d: dict[str, str]) -> None:
155
+ domain = d.get("domain", "")
156
+ ports = self._parse_ports(d.get("ports", "[443]"))
157
+ protocol = d.get("protocol", "tcp")
158
+ action = d.get("action", "allow")
159
+ self.add_rule(domain, ports, protocol, action)