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.
- agent_control_plane/__init__.py +662 -0
- agent_control_plane/a2a_adapter.py +543 -0
- agent_control_plane/adapter.py +417 -0
- agent_control_plane/agent_hibernation.py +394 -0
- agent_control_plane/agent_kernel.py +470 -0
- agent_control_plane/compliance.py +720 -0
- agent_control_plane/constraint_graphs.py +478 -0
- agent_control_plane/control_plane.py +854 -0
- agent_control_plane/example_executors.py +195 -0
- agent_control_plane/execution_engine.py +231 -0
- agent_control_plane/flight_recorder.py +846 -0
- agent_control_plane/governance_layer.py +435 -0
- agent_control_plane/hf_utils.py +563 -0
- agent_control_plane/interfaces/__init__.py +55 -0
- agent_control_plane/interfaces/kernel_interface.py +361 -0
- agent_control_plane/interfaces/plugin_interface.py +497 -0
- agent_control_plane/interfaces/protocol_interfaces.py +387 -0
- agent_control_plane/kernel_space.py +1009 -0
- agent_control_plane/langchain_adapter.py +424 -0
- agent_control_plane/lifecycle.py +3113 -0
- agent_control_plane/mcp_adapter.py +653 -0
- agent_control_plane/ml_safety.py +563 -0
- agent_control_plane/multimodal.py +727 -0
- agent_control_plane/mute_agent.py +422 -0
- agent_control_plane/observability.py +787 -0
- agent_control_plane/orchestrator.py +482 -0
- agent_control_plane/plugin_registry.py +750 -0
- agent_control_plane/policy_engine.py +954 -0
- agent_control_plane/process_isolation.py +777 -0
- agent_control_plane/shadow_mode.py +310 -0
- agent_control_plane/signals.py +493 -0
- agent_control_plane/supervisor_agents.py +430 -0
- agent_control_plane/time_travel_debugger.py +557 -0
- agent_control_plane/tool_registry.py +452 -0
- agent_control_plane/vfs.py +697 -0
- agent_kernel/__init__.py +69 -0
- agent_kernel/analyzer.py +435 -0
- agent_kernel/auditor.py +36 -0
- agent_kernel/completeness_auditor.py +237 -0
- agent_kernel/detector.py +203 -0
- agent_kernel/kernel.py +744 -0
- agent_kernel/memory_manager.py +85 -0
- agent_kernel/models.py +374 -0
- agent_kernel/nudge_mechanism.py +263 -0
- agent_kernel/outcome_analyzer.py +338 -0
- agent_kernel/patcher.py +582 -0
- agent_kernel/semantic_analyzer.py +316 -0
- agent_kernel/semantic_purge.py +349 -0
- agent_kernel/simulator.py +449 -0
- agent_kernel/teacher.py +85 -0
- agent_kernel/triage.py +152 -0
- agent_os/__init__.py +409 -0
- agent_os/_adversarial_impl.py +200 -0
- agent_os/_circuit_breaker_impl.py +232 -0
- agent_os/_mcp_metrics.py +193 -0
- agent_os/adversarial.py +20 -0
- agent_os/agents_compat.py +490 -0
- agent_os/audit_logger.py +135 -0
- agent_os/base_agent.py +651 -0
- agent_os/circuit_breaker.py +34 -0
- agent_os/cli/__init__.py +659 -0
- agent_os/cli/cmd_audit.py +128 -0
- agent_os/cli/cmd_init.py +152 -0
- agent_os/cli/cmd_policy.py +41 -0
- agent_os/cli/cmd_policy_gen.py +180 -0
- agent_os/cli/cmd_validate.py +258 -0
- agent_os/cli/mcp_scan.py +265 -0
- agent_os/cli/output.py +192 -0
- agent_os/cli/policy_checker.py +330 -0
- agent_os/compat.py +74 -0
- agent_os/constraint_graph.py +234 -0
- agent_os/content_governance.py +140 -0
- agent_os/context_budget.py +305 -0
- agent_os/credential_redactor.py +224 -0
- agent_os/diff_policy.py +89 -0
- agent_os/egress_policy.py +159 -0
- agent_os/escalation.py +276 -0
- agent_os/event_bus.py +124 -0
- agent_os/exceptions.py +180 -0
- agent_os/execution_context_policy.py +141 -0
- agent_os/github_enterprise.py +96 -0
- agent_os/health.py +20 -0
- agent_os/integrations/__init__.py +279 -0
- agent_os/integrations/a2a_adapter.py +279 -0
- agent_os/integrations/agent_lightning/__init__.py +30 -0
- agent_os/integrations/anthropic_adapter.py +420 -0
- agent_os/integrations/autogen_adapter.py +620 -0
- agent_os/integrations/base.py +1137 -0
- agent_os/integrations/compat.py +229 -0
- agent_os/integrations/config.py +98 -0
- agent_os/integrations/conversation_guardian.py +957 -0
- agent_os/integrations/crewai_adapter.py +467 -0
- agent_os/integrations/drift_detector.py +425 -0
- agent_os/integrations/dry_run.py +124 -0
- agent_os/integrations/escalation.py +582 -0
- agent_os/integrations/gemini_adapter.py +364 -0
- agent_os/integrations/google_adk_adapter.py +633 -0
- agent_os/integrations/guardrails_adapter.py +394 -0
- agent_os/integrations/health.py +197 -0
- agent_os/integrations/langchain_adapter.py +654 -0
- agent_os/integrations/llamafirewall.py +343 -0
- agent_os/integrations/llamaindex_adapter.py +188 -0
- agent_os/integrations/logging.py +191 -0
- agent_os/integrations/maf_adapter.py +631 -0
- agent_os/integrations/mistral_adapter.py +365 -0
- agent_os/integrations/openai_adapter.py +816 -0
- agent_os/integrations/openai_agents_sdk.py +406 -0
- agent_os/integrations/policy_compose.py +171 -0
- agent_os/integrations/profiling.py +144 -0
- agent_os/integrations/pydantic_ai_adapter.py +420 -0
- agent_os/integrations/rate_limiter.py +130 -0
- agent_os/integrations/rbac.py +143 -0
- agent_os/integrations/registry.py +113 -0
- agent_os/integrations/scope_guard.py +303 -0
- agent_os/integrations/semantic_kernel_adapter.py +769 -0
- agent_os/integrations/smolagents_adapter.py +629 -0
- agent_os/integrations/templates.py +178 -0
- agent_os/integrations/token_budget.py +134 -0
- agent_os/integrations/tool_aliases.py +190 -0
- agent_os/integrations/webhooks.py +177 -0
- agent_os/lite.py +208 -0
- agent_os/mcp_gateway.py +385 -0
- agent_os/mcp_message_signer.py +273 -0
- agent_os/mcp_protocols.py +161 -0
- agent_os/mcp_response_scanner.py +232 -0
- agent_os/mcp_security.py +924 -0
- agent_os/mcp_session_auth.py +231 -0
- agent_os/mcp_sliding_rate_limiter.py +184 -0
- agent_os/memory_guard.py +409 -0
- agent_os/metrics.py +134 -0
- agent_os/mute.py +428 -0
- agent_os/mute_agent.py +209 -0
- agent_os/policies/__init__.py +77 -0
- agent_os/policies/async_evaluator.py +275 -0
- agent_os/policies/backends.py +670 -0
- agent_os/policies/bridge.py +169 -0
- agent_os/policies/budget.py +85 -0
- agent_os/policies/cli.py +294 -0
- agent_os/policies/conflict_resolution.py +270 -0
- agent_os/policies/data_classification.py +252 -0
- agent_os/policies/evaluator.py +239 -0
- agent_os/policies/policy_schema.json +228 -0
- agent_os/policies/rate_limiting.py +145 -0
- agent_os/policies/schema.py +115 -0
- agent_os/policies/shared.py +331 -0
- agent_os/prompt_injection.py +694 -0
- agent_os/providers.py +182 -0
- agent_os/py.typed +0 -0
- agent_os/retry.py +81 -0
- agent_os/reversibility.py +251 -0
- agent_os/sandbox.py +432 -0
- agent_os/sandbox_provider.py +140 -0
- agent_os/secure_codegen.py +525 -0
- agent_os/security_skills.py +538 -0
- agent_os/semantic_policy.py +422 -0
- agent_os/server/__init__.py +15 -0
- agent_os/server/__main__.py +25 -0
- agent_os/server/app.py +277 -0
- agent_os/server/models.py +104 -0
- agent_os/shift_left_metrics.py +130 -0
- agent_os/stateless.py +742 -0
- agent_os/supervisor.py +148 -0
- agent_os/task_outcome.py +148 -0
- agent_os/transparency.py +181 -0
- agent_os/trust_root.py +128 -0
- agent_os_kernel-3.1.0.dist-info/METADATA +1269 -0
- agent_os_kernel-3.1.0.dist-info/RECORD +337 -0
- agent_os_kernel-3.1.0.dist-info/WHEEL +4 -0
- agent_os_kernel-3.1.0.dist-info/entry_points.txt +2 -0
- agent_os_kernel-3.1.0.dist-info/licenses/LICENSE +21 -0
- agent_os_observability/__init__.py +27 -0
- agent_os_observability/dashboards.py +898 -0
- agent_os_observability/metrics.py +398 -0
- agent_os_observability/server.py +223 -0
- agent_os_observability/tracer.py +232 -0
- agent_primitives/__init__.py +24 -0
- agent_primitives/failures.py +84 -0
- agent_primitives/py.typed +0 -0
- amb_core/__init__.py +177 -0
- amb_core/adapters/__init__.py +57 -0
- amb_core/adapters/aws_sqs_broker.py +376 -0
- amb_core/adapters/azure_servicebus_broker.py +340 -0
- amb_core/adapters/kafka_broker.py +260 -0
- amb_core/adapters/nats_broker.py +285 -0
- amb_core/adapters/rabbitmq_broker.py +235 -0
- amb_core/adapters/redis_broker.py +262 -0
- amb_core/broker.py +145 -0
- amb_core/bus.py +481 -0
- amb_core/cloudevents.py +509 -0
- amb_core/dlq.py +345 -0
- amb_core/hf_utils.py +536 -0
- amb_core/memory_broker.py +410 -0
- amb_core/models.py +141 -0
- amb_core/persistence.py +529 -0
- amb_core/schema.py +294 -0
- amb_core/tracing.py +358 -0
- atr/__init__.py +640 -0
- atr/access.py +348 -0
- atr/composition.py +645 -0
- atr/decorator.py +357 -0
- atr/executor.py +384 -0
- atr/health.py +557 -0
- atr/hf_utils.py +449 -0
- atr/injection.py +422 -0
- atr/metrics.py +440 -0
- atr/policies.py +403 -0
- atr/py.typed +2 -0
- atr/registry.py +452 -0
- atr/schema.py +480 -0
- atr/tools/safe/__init__.py +75 -0
- atr/tools/safe/calculator.py +467 -0
- atr/tools/safe/datetime_tool.py +443 -0
- atr/tools/safe/file_reader.py +402 -0
- atr/tools/safe/http_client.py +316 -0
- atr/tools/safe/json_parser.py +374 -0
- atr/tools/safe/text_tool.py +537 -0
- atr/tools/safe/toolkit.py +175 -0
- caas/__init__.py +162 -0
- caas/api/__init__.py +7 -0
- caas/api/server.py +1328 -0
- caas/caching.py +834 -0
- caas/cli.py +210 -0
- caas/conversation.py +223 -0
- caas/decay.py +72 -0
- caas/detection/__init__.py +9 -0
- caas/detection/detector.py +238 -0
- caas/enrichment.py +130 -0
- caas/gateway/__init__.py +27 -0
- caas/gateway/trust_gateway.py +474 -0
- caas/hf_utils.py +479 -0
- caas/ingestion/__init__.py +23 -0
- caas/ingestion/processors.py +253 -0
- caas/ingestion/structure_parser.py +188 -0
- caas/models.py +356 -0
- caas/pragmatic_truth.py +444 -0
- caas/routing/__init__.py +10 -0
- caas/routing/heuristic_router.py +58 -0
- caas/storage/__init__.py +9 -0
- caas/storage/store.py +389 -0
- caas/triad.py +213 -0
- caas/tuning/__init__.py +9 -0
- caas/tuning/tuner.py +329 -0
- caas/vfs/__init__.py +14 -0
- caas/vfs/filesystem.py +452 -0
- cmvk/__init__.py +218 -0
- cmvk/audit.py +402 -0
- cmvk/benchmarks.py +478 -0
- cmvk/constitutional.py +904 -0
- cmvk/hf_utils.py +301 -0
- cmvk/metrics.py +473 -0
- cmvk/profiles.py +300 -0
- cmvk/py.typed +0 -0
- cmvk/types.py +12 -0
- cmvk/verification.py +956 -0
- emk/__init__.py +89 -0
- emk/causal.py +352 -0
- emk/hf_utils.py +421 -0
- emk/indexer.py +83 -0
- emk/py.typed +0 -0
- emk/schema.py +204 -0
- emk/sleep_cycle.py +347 -0
- emk/store.py +281 -0
- iatp/__init__.py +166 -0
- iatp/attestation.py +461 -0
- iatp/cli.py +317 -0
- iatp/hf_utils.py +472 -0
- iatp/ipc_pipes.py +580 -0
- iatp/main.py +412 -0
- iatp/models/__init__.py +447 -0
- iatp/policy_engine.py +337 -0
- iatp/py.typed +2 -0
- iatp/recovery.py +321 -0
- iatp/security/__init__.py +270 -0
- iatp/sidecar/__init__.py +519 -0
- iatp/telemetry/__init__.py +164 -0
- iatp/tests/__init__.py +1 -0
- iatp/tests/test_attestation.py +370 -0
- iatp/tests/test_cli.py +131 -0
- iatp/tests/test_ed25519_attestation.py +211 -0
- iatp/tests/test_models.py +130 -0
- iatp/tests/test_policy_engine.py +347 -0
- iatp/tests/test_recovery.py +281 -0
- iatp/tests/test_security.py +222 -0
- iatp/tests/test_sidecar.py +167 -0
- iatp/tests/test_telemetry.py +175 -0
- mcp_kernel_server/__init__.py +28 -0
- mcp_kernel_server/cli.py +274 -0
- mcp_kernel_server/resources.py +217 -0
- mcp_kernel_server/server.py +564 -0
- mcp_kernel_server/tools.py +1174 -0
- mute_agent/__init__.py +68 -0
- mute_agent/core/__init__.py +1 -0
- mute_agent/core/execution_agent.py +166 -0
- mute_agent/core/handshake_protocol.py +201 -0
- mute_agent/core/reasoning_agent.py +238 -0
- mute_agent/knowledge_graph/__init__.py +1 -0
- mute_agent/knowledge_graph/graph_elements.py +65 -0
- mute_agent/knowledge_graph/multidimensional_graph.py +170 -0
- mute_agent/knowledge_graph/subgraph.py +224 -0
- mute_agent/listener/__init__.py +43 -0
- mute_agent/listener/adapters/__init__.py +31 -0
- mute_agent/listener/adapters/base_adapter.py +189 -0
- mute_agent/listener/adapters/caas_adapter.py +344 -0
- mute_agent/listener/adapters/control_plane_adapter.py +436 -0
- mute_agent/listener/adapters/iatp_adapter.py +332 -0
- mute_agent/listener/adapters/scak_adapter.py +251 -0
- mute_agent/listener/listener.py +610 -0
- mute_agent/listener/state_observer.py +436 -0
- mute_agent/listener/threshold_config.py +313 -0
- mute_agent/super_system/__init__.py +1 -0
- mute_agent/super_system/router.py +204 -0
- mute_agent/visualization/__init__.py +10 -0
- mute_agent/visualization/graph_debugger.py +502 -0
- nexus/README.md +60 -0
- nexus/__init__.py +51 -0
- nexus/arbiter.py +359 -0
- nexus/client.py +466 -0
- nexus/dmz.py +444 -0
- nexus/escrow.py +430 -0
- nexus/exceptions.py +286 -0
- nexus/pyproject.toml +36 -0
- nexus/registry.py +393 -0
- nexus/reputation.py +425 -0
- nexus/schemas/__init__.py +51 -0
- nexus/schemas/compliance.py +276 -0
- nexus/schemas/escrow.py +251 -0
- nexus/schemas/manifest.py +225 -0
- nexus/schemas/receipt.py +208 -0
- nexus/tests/__init__.py +0 -0
- nexus/tests/conftest.py +146 -0
- nexus/tests/test_arbiter.py +192 -0
- nexus/tests/test_dmz.py +194 -0
- nexus/tests/test_escrow.py +276 -0
- nexus/tests/test_exceptions.py +225 -0
- nexus/tests/test_registry.py +232 -0
- nexus/tests/test_reputation.py +328 -0
- 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
|
agent_os/diff_policy.py
ADDED
|
@@ -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)
|