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,316 @@
|
|
|
1
|
+
# Copyright (c) Microsoft Corporation.
|
|
2
|
+
# Licensed under the MIT License.
|
|
3
|
+
"""
|
|
4
|
+
Safe HTTP Client Tool.
|
|
5
|
+
|
|
6
|
+
Provides HTTP request capabilities with security controls:
|
|
7
|
+
- URL whitelisting
|
|
8
|
+
- Rate limiting
|
|
9
|
+
- Request timeout enforcement
|
|
10
|
+
- Response size limits
|
|
11
|
+
- No redirect following to external domains
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import asyncio
|
|
15
|
+
import time
|
|
16
|
+
from collections import deque
|
|
17
|
+
from typing import Any, Dict, List, Optional, Set
|
|
18
|
+
from urllib.parse import urlparse
|
|
19
|
+
|
|
20
|
+
from atr.decorator import tool
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class RateLimiter:
|
|
24
|
+
"""Simple rate limiter using sliding window."""
|
|
25
|
+
|
|
26
|
+
def __init__(self, max_requests: int, window_seconds: float = 60.0):
|
|
27
|
+
self.max_requests = max_requests
|
|
28
|
+
self.window_seconds = window_seconds
|
|
29
|
+
self._requests: deque = deque()
|
|
30
|
+
|
|
31
|
+
def check(self) -> bool:
|
|
32
|
+
"""Check if request is allowed."""
|
|
33
|
+
now = time.monotonic()
|
|
34
|
+
|
|
35
|
+
# Remove old requests
|
|
36
|
+
while self._requests and self._requests[0] < now - self.window_seconds:
|
|
37
|
+
self._requests.popleft()
|
|
38
|
+
|
|
39
|
+
return len(self._requests) < self.max_requests
|
|
40
|
+
|
|
41
|
+
def record(self):
|
|
42
|
+
"""Record a request."""
|
|
43
|
+
self._requests.append(time.monotonic())
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class HttpClientTool:
|
|
47
|
+
"""
|
|
48
|
+
Safe HTTP client with security controls.
|
|
49
|
+
|
|
50
|
+
Features:
|
|
51
|
+
- Domain whitelisting (only specified domains allowed)
|
|
52
|
+
- Rate limiting (prevent abuse)
|
|
53
|
+
- Timeout enforcement (prevent hanging)
|
|
54
|
+
- Response size limits (prevent memory exhaustion)
|
|
55
|
+
- Safe redirect handling (no external redirects)
|
|
56
|
+
|
|
57
|
+
Example:
|
|
58
|
+
```python
|
|
59
|
+
http = HttpClientTool(
|
|
60
|
+
allowed_domains=["api.github.com", "httpbin.org"],
|
|
61
|
+
rate_limit=30, # 30 requests per minute
|
|
62
|
+
timeout=10.0,
|
|
63
|
+
max_response_size=1_000_000 # 1MB
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
# Register with ATR
|
|
67
|
+
registry.register(http.get)
|
|
68
|
+
registry.register(http.post)
|
|
69
|
+
|
|
70
|
+
# Use from agent
|
|
71
|
+
response = await http.get("https://api.github.com/users/octocat")
|
|
72
|
+
```
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
def __init__(
|
|
76
|
+
self,
|
|
77
|
+
allowed_domains: Optional[List[str]] = None,
|
|
78
|
+
blocked_domains: Optional[List[str]] = None,
|
|
79
|
+
rate_limit: int = 60,
|
|
80
|
+
timeout: float = 30.0,
|
|
81
|
+
max_response_size: int = 10_000_000, # 10MB
|
|
82
|
+
allow_redirects: bool = True,
|
|
83
|
+
max_redirects: int = 5
|
|
84
|
+
):
|
|
85
|
+
"""
|
|
86
|
+
Initialize HTTP client tool.
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
allowed_domains: Whitelist of allowed domains (if set, only these allowed)
|
|
90
|
+
blocked_domains: Blacklist of blocked domains
|
|
91
|
+
rate_limit: Maximum requests per minute
|
|
92
|
+
timeout: Request timeout in seconds
|
|
93
|
+
max_response_size: Maximum response size in bytes
|
|
94
|
+
allow_redirects: Whether to follow redirects
|
|
95
|
+
max_redirects: Maximum number of redirects to follow
|
|
96
|
+
"""
|
|
97
|
+
self.allowed_domains: Set[str] = set(allowed_domains or [])
|
|
98
|
+
self.blocked_domains: Set[str] = set(blocked_domains or [
|
|
99
|
+
"localhost",
|
|
100
|
+
"127.0.0.1",
|
|
101
|
+
"0.0.0.0",
|
|
102
|
+
"169.254.169.254", # AWS metadata
|
|
103
|
+
"metadata.google.internal", # GCP metadata
|
|
104
|
+
])
|
|
105
|
+
self.timeout = timeout
|
|
106
|
+
self.max_response_size = max_response_size
|
|
107
|
+
self.allow_redirects = allow_redirects
|
|
108
|
+
self.max_redirects = max_redirects
|
|
109
|
+
self._rate_limiter = RateLimiter(rate_limit)
|
|
110
|
+
|
|
111
|
+
def _validate_url(self, url: str) -> str:
|
|
112
|
+
"""Validate and normalize URL."""
|
|
113
|
+
parsed = urlparse(url)
|
|
114
|
+
|
|
115
|
+
# Must have scheme
|
|
116
|
+
if not parsed.scheme:
|
|
117
|
+
raise ValueError("URL must include scheme (http:// or https://)")
|
|
118
|
+
|
|
119
|
+
# Only allow http/https
|
|
120
|
+
if parsed.scheme not in ("http", "https"):
|
|
121
|
+
raise ValueError(f"Scheme '{parsed.scheme}' not allowed. Use http or https.")
|
|
122
|
+
|
|
123
|
+
# Extract domain
|
|
124
|
+
domain = parsed.netloc.lower()
|
|
125
|
+
if ":" in domain:
|
|
126
|
+
domain = domain.split(":")[0] # Remove port
|
|
127
|
+
|
|
128
|
+
# Check blocked domains
|
|
129
|
+
if domain in self.blocked_domains:
|
|
130
|
+
raise ValueError(f"Domain '{domain}' is blocked")
|
|
131
|
+
|
|
132
|
+
# Check for private IP ranges (basic check)
|
|
133
|
+
if self._is_private_domain(domain):
|
|
134
|
+
raise ValueError(f"Private/internal domains not allowed: {domain}")
|
|
135
|
+
|
|
136
|
+
# Check allowed domains (if whitelist set)
|
|
137
|
+
if self.allowed_domains:
|
|
138
|
+
if not any(domain.endswith(allowed) for allowed in self.allowed_domains):
|
|
139
|
+
raise ValueError(
|
|
140
|
+
f"Domain '{domain}' not in allowed list. "
|
|
141
|
+
f"Allowed: {', '.join(self.allowed_domains)}"
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
return url
|
|
145
|
+
|
|
146
|
+
def _is_private_domain(self, domain: str) -> bool:
|
|
147
|
+
"""Check if domain resolves to private IP (basic check)."""
|
|
148
|
+
# Check common private patterns
|
|
149
|
+
private_patterns = [
|
|
150
|
+
"10.",
|
|
151
|
+
"192.168.",
|
|
152
|
+
"172.16.", "172.17.", "172.18.", "172.19.",
|
|
153
|
+
"172.20.", "172.21.", "172.22.", "172.23.",
|
|
154
|
+
"172.24.", "172.25.", "172.26.", "172.27.",
|
|
155
|
+
"172.28.", "172.29.", "172.30.", "172.31.",
|
|
156
|
+
"internal",
|
|
157
|
+
".local",
|
|
158
|
+
]
|
|
159
|
+
return any(domain.startswith(p) or domain.endswith(p) for p in private_patterns)
|
|
160
|
+
|
|
161
|
+
def _check_rate_limit(self):
|
|
162
|
+
"""Check and enforce rate limit."""
|
|
163
|
+
if not self._rate_limiter.check():
|
|
164
|
+
raise RuntimeError("Rate limit exceeded. Please wait before making more requests.")
|
|
165
|
+
self._rate_limiter.record()
|
|
166
|
+
|
|
167
|
+
@tool(
|
|
168
|
+
name="http_get",
|
|
169
|
+
description="Make a safe HTTP GET request to retrieve data from a URL",
|
|
170
|
+
tags=["http", "network", "safe"]
|
|
171
|
+
)
|
|
172
|
+
async def get(
|
|
173
|
+
self,
|
|
174
|
+
url: str,
|
|
175
|
+
headers: Optional[Dict[str, str]] = None
|
|
176
|
+
) -> Dict[str, Any]:
|
|
177
|
+
"""
|
|
178
|
+
Make a GET request.
|
|
179
|
+
|
|
180
|
+
Args:
|
|
181
|
+
url: URL to request (must be in allowed domains)
|
|
182
|
+
headers: Optional request headers
|
|
183
|
+
|
|
184
|
+
Returns:
|
|
185
|
+
Dict with status_code, headers, and body
|
|
186
|
+
"""
|
|
187
|
+
try:
|
|
188
|
+
import aiohttp
|
|
189
|
+
except ImportError:
|
|
190
|
+
raise ImportError("aiohttp required. Install with: pip install aiohttp")
|
|
191
|
+
|
|
192
|
+
# Validate
|
|
193
|
+
url = self._validate_url(url)
|
|
194
|
+
self._check_rate_limit()
|
|
195
|
+
|
|
196
|
+
# Make request
|
|
197
|
+
timeout = aiohttp.ClientTimeout(total=self.timeout)
|
|
198
|
+
|
|
199
|
+
async with aiohttp.ClientSession(timeout=timeout) as session:
|
|
200
|
+
async with session.get(
|
|
201
|
+
url,
|
|
202
|
+
headers=headers,
|
|
203
|
+
allow_redirects=self.allow_redirects,
|
|
204
|
+
max_redirects=self.max_redirects
|
|
205
|
+
) as response:
|
|
206
|
+
# Check response size
|
|
207
|
+
content_length = response.headers.get("Content-Length")
|
|
208
|
+
if content_length and int(content_length) > self.max_response_size:
|
|
209
|
+
raise ValueError(f"Response too large: {content_length} bytes")
|
|
210
|
+
|
|
211
|
+
# Read response (with size limit)
|
|
212
|
+
body = await response.text()
|
|
213
|
+
if len(body) > self.max_response_size:
|
|
214
|
+
body = body[:self.max_response_size] + "... [truncated]"
|
|
215
|
+
|
|
216
|
+
return {
|
|
217
|
+
"status_code": response.status,
|
|
218
|
+
"headers": dict(response.headers),
|
|
219
|
+
"body": body,
|
|
220
|
+
"url": str(response.url)
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
@tool(
|
|
224
|
+
name="http_post",
|
|
225
|
+
description="Make a safe HTTP POST request to send data to a URL",
|
|
226
|
+
tags=["http", "network", "safe"]
|
|
227
|
+
)
|
|
228
|
+
async def post(
|
|
229
|
+
self,
|
|
230
|
+
url: str,
|
|
231
|
+
data: Optional[Dict[str, Any]] = None,
|
|
232
|
+
json_body: Optional[Dict[str, Any]] = None,
|
|
233
|
+
headers: Optional[Dict[str, str]] = None
|
|
234
|
+
) -> Dict[str, Any]:
|
|
235
|
+
"""
|
|
236
|
+
Make a POST request.
|
|
237
|
+
|
|
238
|
+
Args:
|
|
239
|
+
url: URL to request (must be in allowed domains)
|
|
240
|
+
data: Form data to send
|
|
241
|
+
json_body: JSON body to send
|
|
242
|
+
headers: Optional request headers
|
|
243
|
+
|
|
244
|
+
Returns:
|
|
245
|
+
Dict with status_code, headers, and body
|
|
246
|
+
"""
|
|
247
|
+
try:
|
|
248
|
+
import aiohttp
|
|
249
|
+
except ImportError:
|
|
250
|
+
raise ImportError("aiohttp required. Install with: pip install aiohttp")
|
|
251
|
+
|
|
252
|
+
# Validate
|
|
253
|
+
url = self._validate_url(url)
|
|
254
|
+
self._check_rate_limit()
|
|
255
|
+
|
|
256
|
+
# Make request
|
|
257
|
+
timeout = aiohttp.ClientTimeout(total=self.timeout)
|
|
258
|
+
|
|
259
|
+
async with aiohttp.ClientSession(timeout=timeout) as session:
|
|
260
|
+
async with session.post(
|
|
261
|
+
url,
|
|
262
|
+
data=data,
|
|
263
|
+
json=json_body,
|
|
264
|
+
headers=headers,
|
|
265
|
+
allow_redirects=self.allow_redirects,
|
|
266
|
+
max_redirects=self.max_redirects
|
|
267
|
+
) as response:
|
|
268
|
+
# Check response size
|
|
269
|
+
content_length = response.headers.get("Content-Length")
|
|
270
|
+
if content_length and int(content_length) > self.max_response_size:
|
|
271
|
+
raise ValueError(f"Response too large: {content_length} bytes")
|
|
272
|
+
|
|
273
|
+
# Read response
|
|
274
|
+
body = await response.text()
|
|
275
|
+
if len(body) > self.max_response_size:
|
|
276
|
+
body = body[:self.max_response_size] + "... [truncated]"
|
|
277
|
+
|
|
278
|
+
return {
|
|
279
|
+
"status_code": response.status,
|
|
280
|
+
"headers": dict(response.headers),
|
|
281
|
+
"body": body,
|
|
282
|
+
"url": str(response.url)
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
@tool(
|
|
286
|
+
name="http_head",
|
|
287
|
+
description="Make a safe HTTP HEAD request to check if a URL exists",
|
|
288
|
+
tags=["http", "network", "safe"]
|
|
289
|
+
)
|
|
290
|
+
async def head(self, url: str) -> Dict[str, Any]:
|
|
291
|
+
"""
|
|
292
|
+
Make a HEAD request (useful for checking if resource exists).
|
|
293
|
+
|
|
294
|
+
Args:
|
|
295
|
+
url: URL to check
|
|
296
|
+
|
|
297
|
+
Returns:
|
|
298
|
+
Dict with status_code and headers
|
|
299
|
+
"""
|
|
300
|
+
try:
|
|
301
|
+
import aiohttp
|
|
302
|
+
except ImportError:
|
|
303
|
+
raise ImportError("aiohttp required. Install with: pip install aiohttp")
|
|
304
|
+
|
|
305
|
+
url = self._validate_url(url)
|
|
306
|
+
self._check_rate_limit()
|
|
307
|
+
|
|
308
|
+
timeout = aiohttp.ClientTimeout(total=self.timeout)
|
|
309
|
+
|
|
310
|
+
async with aiohttp.ClientSession(timeout=timeout) as session:
|
|
311
|
+
async with session.head(url, allow_redirects=self.allow_redirects) as response:
|
|
312
|
+
return {
|
|
313
|
+
"status_code": response.status,
|
|
314
|
+
"headers": dict(response.headers),
|
|
315
|
+
"url": str(response.url)
|
|
316
|
+
}
|
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
# Copyright (c) Microsoft Corporation.
|
|
2
|
+
# Licensed under the MIT License.
|
|
3
|
+
"""
|
|
4
|
+
Safe JSON/YAML Parser Tool.
|
|
5
|
+
|
|
6
|
+
Provides safe parsing of JSON and YAML with:
|
|
7
|
+
- Size limits to prevent memory exhaustion
|
|
8
|
+
- Depth limits to prevent stack overflow
|
|
9
|
+
- Safe YAML loading (no arbitrary code execution)
|
|
10
|
+
- Schema validation support
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import json
|
|
14
|
+
from typing import Any, Dict, List, Optional, Union
|
|
15
|
+
|
|
16
|
+
from atr.decorator import tool
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class JsonParserTool:
|
|
20
|
+
"""
|
|
21
|
+
Safe JSON and YAML parser.
|
|
22
|
+
|
|
23
|
+
Features:
|
|
24
|
+
- Input size limits
|
|
25
|
+
- Nesting depth limits
|
|
26
|
+
- Safe YAML loading (yaml.safe_load)
|
|
27
|
+
- Schema validation (optional)
|
|
28
|
+
- Pretty printing
|
|
29
|
+
|
|
30
|
+
Example:
|
|
31
|
+
```python
|
|
32
|
+
parser = JsonParserTool(
|
|
33
|
+
max_size=1_000_000, # 1MB
|
|
34
|
+
max_depth=50
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
# Parse JSON
|
|
38
|
+
data = parser.parse_json('{"key": "value"}')
|
|
39
|
+
|
|
40
|
+
# Parse YAML
|
|
41
|
+
data = parser.parse_yaml("key: value")
|
|
42
|
+
|
|
43
|
+
# Validate schema
|
|
44
|
+
parser.validate(data, schema={"type": "object"})
|
|
45
|
+
```
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
def __init__(
|
|
49
|
+
self,
|
|
50
|
+
max_size: int = 10_000_000, # 10MB
|
|
51
|
+
max_depth: int = 100,
|
|
52
|
+
max_keys: int = 10000
|
|
53
|
+
):
|
|
54
|
+
"""
|
|
55
|
+
Initialize parser tool.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
max_size: Maximum input size in characters
|
|
59
|
+
max_depth: Maximum nesting depth
|
|
60
|
+
max_keys: Maximum number of keys in objects
|
|
61
|
+
"""
|
|
62
|
+
self.max_size = max_size
|
|
63
|
+
self.max_depth = max_depth
|
|
64
|
+
self.max_keys = max_keys
|
|
65
|
+
|
|
66
|
+
def _check_size(self, data: str, name: str = "input"):
|
|
67
|
+
"""Check input size."""
|
|
68
|
+
if len(data) > self.max_size:
|
|
69
|
+
raise ValueError(
|
|
70
|
+
f"{name} too large: {len(data)} chars. Maximum: {self.max_size}"
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
def _check_depth(self, obj: Any, current_depth: int = 0):
|
|
74
|
+
"""Check nesting depth recursively."""
|
|
75
|
+
if current_depth > self.max_depth:
|
|
76
|
+
raise ValueError(f"Nesting too deep. Maximum depth: {self.max_depth}")
|
|
77
|
+
|
|
78
|
+
if isinstance(obj, dict):
|
|
79
|
+
if len(obj) > self.max_keys:
|
|
80
|
+
raise ValueError(f"Too many keys: {len(obj)}. Maximum: {self.max_keys}")
|
|
81
|
+
for value in obj.values():
|
|
82
|
+
self._check_depth(value, current_depth + 1)
|
|
83
|
+
elif isinstance(obj, list):
|
|
84
|
+
for item in obj:
|
|
85
|
+
self._check_depth(item, current_depth + 1)
|
|
86
|
+
|
|
87
|
+
@tool(
|
|
88
|
+
name="parse_json",
|
|
89
|
+
description="Safely parse a JSON string into a Python object",
|
|
90
|
+
tags=["json", "parse", "safe"]
|
|
91
|
+
)
|
|
92
|
+
def parse_json(self, data: str) -> Dict[str, Any]:
|
|
93
|
+
"""
|
|
94
|
+
Parse JSON string.
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
data: JSON string to parse
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
Dict with parsed data and metadata
|
|
101
|
+
"""
|
|
102
|
+
self._check_size(data, "JSON")
|
|
103
|
+
|
|
104
|
+
try:
|
|
105
|
+
parsed = json.loads(data)
|
|
106
|
+
except json.JSONDecodeError as e:
|
|
107
|
+
return {
|
|
108
|
+
"success": False,
|
|
109
|
+
"error": f"Invalid JSON: {e.msg} at line {e.lineno}, column {e.colno}",
|
|
110
|
+
"data": None
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
# Check depth
|
|
114
|
+
try:
|
|
115
|
+
self._check_depth(parsed)
|
|
116
|
+
except ValueError as e:
|
|
117
|
+
return {
|
|
118
|
+
"success": False,
|
|
119
|
+
"error": str(e),
|
|
120
|
+
"data": None
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return {
|
|
124
|
+
"success": True,
|
|
125
|
+
"data": parsed,
|
|
126
|
+
"type": type(parsed).__name__
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
@tool(
|
|
130
|
+
name="parse_yaml",
|
|
131
|
+
description="Safely parse a YAML string into a Python object",
|
|
132
|
+
tags=["yaml", "parse", "safe"]
|
|
133
|
+
)
|
|
134
|
+
def parse_yaml(self, data: str) -> Dict[str, Any]:
|
|
135
|
+
"""
|
|
136
|
+
Parse YAML string safely.
|
|
137
|
+
|
|
138
|
+
Uses yaml.safe_load to prevent arbitrary code execution.
|
|
139
|
+
|
|
140
|
+
Args:
|
|
141
|
+
data: YAML string to parse
|
|
142
|
+
|
|
143
|
+
Returns:
|
|
144
|
+
Dict with parsed data and metadata
|
|
145
|
+
"""
|
|
146
|
+
try:
|
|
147
|
+
import yaml
|
|
148
|
+
except ImportError:
|
|
149
|
+
return {
|
|
150
|
+
"success": False,
|
|
151
|
+
"error": "PyYAML not installed. Install with: pip install pyyaml",
|
|
152
|
+
"data": None
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
self._check_size(data, "YAML")
|
|
156
|
+
|
|
157
|
+
try:
|
|
158
|
+
# Always use safe_load to prevent code execution
|
|
159
|
+
parsed = yaml.safe_load(data)
|
|
160
|
+
except yaml.YAMLError as e:
|
|
161
|
+
return {
|
|
162
|
+
"success": False,
|
|
163
|
+
"error": f"Invalid YAML: {e}",
|
|
164
|
+
"data": None
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
# Check depth
|
|
168
|
+
try:
|
|
169
|
+
self._check_depth(parsed)
|
|
170
|
+
except ValueError as e:
|
|
171
|
+
return {
|
|
172
|
+
"success": False,
|
|
173
|
+
"error": str(e),
|
|
174
|
+
"data": None
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return {
|
|
178
|
+
"success": True,
|
|
179
|
+
"data": parsed,
|
|
180
|
+
"type": type(parsed).__name__ if parsed is not None else "null"
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
@tool(
|
|
184
|
+
name="to_json",
|
|
185
|
+
description="Convert a Python object to a JSON string",
|
|
186
|
+
tags=["json", "serialize", "safe"]
|
|
187
|
+
)
|
|
188
|
+
def to_json(
|
|
189
|
+
self,
|
|
190
|
+
data: Any,
|
|
191
|
+
indent: Optional[int] = 2,
|
|
192
|
+
sort_keys: bool = False
|
|
193
|
+
) -> Dict[str, Any]:
|
|
194
|
+
"""
|
|
195
|
+
Convert object to JSON string.
|
|
196
|
+
|
|
197
|
+
Args:
|
|
198
|
+
data: Object to serialize
|
|
199
|
+
indent: Indentation level (None for compact)
|
|
200
|
+
sort_keys: Whether to sort dictionary keys
|
|
201
|
+
|
|
202
|
+
Returns:
|
|
203
|
+
Dict with JSON string
|
|
204
|
+
"""
|
|
205
|
+
try:
|
|
206
|
+
result = json.dumps(data, indent=indent, sort_keys=sort_keys, ensure_ascii=False)
|
|
207
|
+
|
|
208
|
+
if len(result) > self.max_size:
|
|
209
|
+
return {
|
|
210
|
+
"success": False,
|
|
211
|
+
"error": f"Output too large: {len(result)} chars",
|
|
212
|
+
"json": None
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return {
|
|
216
|
+
"success": True,
|
|
217
|
+
"json": result,
|
|
218
|
+
"size": len(result)
|
|
219
|
+
}
|
|
220
|
+
except (TypeError, ValueError) as e:
|
|
221
|
+
return {
|
|
222
|
+
"success": False,
|
|
223
|
+
"error": f"Cannot serialize: {e}",
|
|
224
|
+
"json": None
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
@tool(
|
|
228
|
+
name="to_yaml",
|
|
229
|
+
description="Convert a Python object to a YAML string",
|
|
230
|
+
tags=["yaml", "serialize", "safe"]
|
|
231
|
+
)
|
|
232
|
+
def to_yaml(
|
|
233
|
+
self,
|
|
234
|
+
data: Any,
|
|
235
|
+
default_flow_style: bool = False
|
|
236
|
+
) -> Dict[str, Any]:
|
|
237
|
+
"""
|
|
238
|
+
Convert object to YAML string.
|
|
239
|
+
|
|
240
|
+
Args:
|
|
241
|
+
data: Object to serialize
|
|
242
|
+
default_flow_style: Use flow style (compact) formatting
|
|
243
|
+
|
|
244
|
+
Returns:
|
|
245
|
+
Dict with YAML string
|
|
246
|
+
"""
|
|
247
|
+
try:
|
|
248
|
+
import yaml
|
|
249
|
+
except ImportError:
|
|
250
|
+
return {
|
|
251
|
+
"success": False,
|
|
252
|
+
"error": "PyYAML not installed. Install with: pip install pyyaml",
|
|
253
|
+
"yaml": None
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
try:
|
|
257
|
+
result = yaml.safe_dump(
|
|
258
|
+
data,
|
|
259
|
+
default_flow_style=default_flow_style,
|
|
260
|
+
allow_unicode=True
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
if len(result) > self.max_size:
|
|
264
|
+
return {
|
|
265
|
+
"success": False,
|
|
266
|
+
"error": f"Output too large: {len(result)} chars",
|
|
267
|
+
"yaml": None
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
return {
|
|
271
|
+
"success": True,
|
|
272
|
+
"yaml": result,
|
|
273
|
+
"size": len(result)
|
|
274
|
+
}
|
|
275
|
+
except yaml.YAMLError as e:
|
|
276
|
+
return {
|
|
277
|
+
"success": False,
|
|
278
|
+
"error": f"Cannot serialize: {e}",
|
|
279
|
+
"yaml": None
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
@tool(
|
|
283
|
+
name="validate_json_schema",
|
|
284
|
+
description="Validate data against a JSON schema",
|
|
285
|
+
tags=["json", "validate", "schema", "safe"]
|
|
286
|
+
)
|
|
287
|
+
def validate_schema(
|
|
288
|
+
self,
|
|
289
|
+
data: Any,
|
|
290
|
+
schema: Dict[str, Any]
|
|
291
|
+
) -> Dict[str, Any]:
|
|
292
|
+
"""
|
|
293
|
+
Validate data against JSON schema.
|
|
294
|
+
|
|
295
|
+
Args:
|
|
296
|
+
data: Data to validate
|
|
297
|
+
schema: JSON schema
|
|
298
|
+
|
|
299
|
+
Returns:
|
|
300
|
+
Dict with validation result
|
|
301
|
+
"""
|
|
302
|
+
try:
|
|
303
|
+
import jsonschema
|
|
304
|
+
except ImportError:
|
|
305
|
+
return {
|
|
306
|
+
"valid": False,
|
|
307
|
+
"error": "jsonschema not installed. Install with: pip install jsonschema"
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
try:
|
|
311
|
+
jsonschema.validate(instance=data, schema=schema)
|
|
312
|
+
return {
|
|
313
|
+
"valid": True,
|
|
314
|
+
"error": None
|
|
315
|
+
}
|
|
316
|
+
except jsonschema.ValidationError as e:
|
|
317
|
+
return {
|
|
318
|
+
"valid": False,
|
|
319
|
+
"error": e.message,
|
|
320
|
+
"path": list(e.absolute_path)
|
|
321
|
+
}
|
|
322
|
+
except jsonschema.SchemaError as e:
|
|
323
|
+
return {
|
|
324
|
+
"valid": False,
|
|
325
|
+
"error": f"Invalid schema: {e.message}"
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
@tool(
|
|
329
|
+
name="json_query",
|
|
330
|
+
description="Query JSON data using JSONPath or simple dot notation",
|
|
331
|
+
tags=["json", "query", "safe"]
|
|
332
|
+
)
|
|
333
|
+
def query(
|
|
334
|
+
self,
|
|
335
|
+
data: Any,
|
|
336
|
+
path: str
|
|
337
|
+
) -> Dict[str, Any]:
|
|
338
|
+
"""
|
|
339
|
+
Query JSON data using dot notation.
|
|
340
|
+
|
|
341
|
+
Args:
|
|
342
|
+
data: JSON data to query
|
|
343
|
+
path: Dot-notation path (e.g., "users.0.name")
|
|
344
|
+
|
|
345
|
+
Returns:
|
|
346
|
+
Dict with query result
|
|
347
|
+
"""
|
|
348
|
+
parts = path.split(".")
|
|
349
|
+
current = data
|
|
350
|
+
|
|
351
|
+
try:
|
|
352
|
+
for part in parts:
|
|
353
|
+
if not part:
|
|
354
|
+
continue
|
|
355
|
+
|
|
356
|
+
if isinstance(current, dict):
|
|
357
|
+
current = current[part]
|
|
358
|
+
elif isinstance(current, list):
|
|
359
|
+
idx = int(part)
|
|
360
|
+
current = current[idx]
|
|
361
|
+
else:
|
|
362
|
+
raise KeyError(f"Cannot access '{part}' on {type(current).__name__}")
|
|
363
|
+
|
|
364
|
+
return {
|
|
365
|
+
"success": True,
|
|
366
|
+
"value": current,
|
|
367
|
+
"path": path
|
|
368
|
+
}
|
|
369
|
+
except (KeyError, IndexError, ValueError) as e:
|
|
370
|
+
return {
|
|
371
|
+
"success": False,
|
|
372
|
+
"error": f"Path not found: {e}",
|
|
373
|
+
"value": None
|
|
374
|
+
}
|