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
agent_os/cli/output.py
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
# Copyright (c) Microsoft Corporation.
|
|
2
|
+
# Licensed under the MIT License.
|
|
3
|
+
"""Terminal output helpers for the Agent OS CLI.
|
|
4
|
+
|
|
5
|
+
Provides ANSI colour formatting, error/warning formatters, and the
|
|
6
|
+
module-level ``Colors`` singleton used across all CLI sub-commands.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import argparse
|
|
12
|
+
import json
|
|
13
|
+
import logging
|
|
14
|
+
import os
|
|
15
|
+
import pathlib
|
|
16
|
+
import sys
|
|
17
|
+
import traceback
|
|
18
|
+
|
|
19
|
+
logger = logging.getLogger(__name__)
|
|
20
|
+
|
|
21
|
+
# ============================================================================
|
|
22
|
+
# Terminal Colour Support
|
|
23
|
+
# ============================================================================
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def supports_color() -> bool:
|
|
27
|
+
"""Check if terminal supports colors."""
|
|
28
|
+
if os.environ.get('NO_COLOR') or os.environ.get('CI'):
|
|
29
|
+
return False
|
|
30
|
+
return sys.stdout.isatty()
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class Colors:
|
|
34
|
+
"""ANSI color codes for terminal output.
|
|
35
|
+
|
|
36
|
+
Uses instance attributes so that ``disable()`` does not mutate shared
|
|
37
|
+
class state. A module-level singleton is created below; import and use
|
|
38
|
+
that instead of the class directly.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
_DEFAULTS: dict[str, str] = {
|
|
42
|
+
'RED': '\033[91m',
|
|
43
|
+
'GREEN': '\033[92m',
|
|
44
|
+
'YELLOW': '\033[93m',
|
|
45
|
+
'BLUE': '\033[94m',
|
|
46
|
+
'MAGENTA': '\033[95m',
|
|
47
|
+
'CYAN': '\033[96m',
|
|
48
|
+
'WHITE': '\033[97m',
|
|
49
|
+
'BOLD': '\033[1m',
|
|
50
|
+
'DIM': '\033[2m',
|
|
51
|
+
'RESET': '\033[0m',
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
def __init__(self, enabled: bool | None = None) -> None:
|
|
55
|
+
if enabled is None:
|
|
56
|
+
enabled = supports_color()
|
|
57
|
+
self._enabled = enabled
|
|
58
|
+
self._apply(enabled)
|
|
59
|
+
|
|
60
|
+
def _apply(self, enabled: bool) -> None:
|
|
61
|
+
for name, code in self._DEFAULTS.items():
|
|
62
|
+
setattr(self, name, code if enabled else '')
|
|
63
|
+
|
|
64
|
+
def disable(self) -> None:
|
|
65
|
+
"""Disable colors on *this* instance."""
|
|
66
|
+
self._enabled = False
|
|
67
|
+
self._apply(False)
|
|
68
|
+
|
|
69
|
+
def enable(self) -> None:
|
|
70
|
+
"""Enable colors on *this* instance."""
|
|
71
|
+
self._enabled = True
|
|
72
|
+
self._apply(True)
|
|
73
|
+
|
|
74
|
+
@property
|
|
75
|
+
def enabled(self) -> bool:
|
|
76
|
+
return self._enabled
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
# Module-level singleton – every import shares this instance.
|
|
80
|
+
Colors = Colors() # type: ignore[misc]
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
# ============================================================================
|
|
84
|
+
# Output Format
|
|
85
|
+
# ============================================================================
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def get_output_format(args: argparse.Namespace) -> str:
|
|
89
|
+
"""Determine the output format from CLI arguments."""
|
|
90
|
+
if getattr(args, "json", False):
|
|
91
|
+
return "json"
|
|
92
|
+
return getattr(args, "format", "text")
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def get_config_path(args_path: str | None = None) -> "pathlib.Path":
|
|
96
|
+
"""Resolve the config path from args or AGENTOS_CONFIG env var."""
|
|
97
|
+
from pathlib import Path as _Path
|
|
98
|
+
|
|
99
|
+
if args_path:
|
|
100
|
+
return _Path(args_path)
|
|
101
|
+
env_config = os.environ.get("AGENTOS_CONFIG")
|
|
102
|
+
if env_config:
|
|
103
|
+
return _Path(env_config)
|
|
104
|
+
return _Path(".")
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
# ============================================================================
|
|
108
|
+
# CLI Error Formatting
|
|
109
|
+
# ============================================================================
|
|
110
|
+
|
|
111
|
+
DOCS_URL = "https://github.com/microsoft/agent-governance-toolkit/blob/main/docs"
|
|
112
|
+
|
|
113
|
+
AVAILABLE_POLICIES = ("strict", "permissive", "audit")
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _difflib_best_match(word: str, candidates: list[str]) -> str | None:
|
|
117
|
+
"""Return the closest match from *candidates*, or ``None``."""
|
|
118
|
+
import difflib
|
|
119
|
+
|
|
120
|
+
matches = difflib.get_close_matches(word, candidates, n=1, cutoff=0.5)
|
|
121
|
+
return matches[0] if matches else None
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def format_error(message: str, suggestion: str | None = None,
|
|
125
|
+
docs_path: str | None = None) -> str:
|
|
126
|
+
"""Return a colorized error string with an optional suggestion and docs link."""
|
|
127
|
+
parts = [f"{Colors.RED}{Colors.BOLD}Error:{Colors.RESET} {message}"]
|
|
128
|
+
if suggestion:
|
|
129
|
+
parts.append(f" {Colors.GREEN}💡 Suggestion:{Colors.RESET} {suggestion}")
|
|
130
|
+
if docs_path:
|
|
131
|
+
parts.append(f" {Colors.DIM}📖 Docs: {DOCS_URL}/{docs_path}{Colors.RESET}")
|
|
132
|
+
return "\n".join(parts)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def handle_cli_error(e: Exception, args: argparse.Namespace) -> int:
|
|
136
|
+
"""Centralized error handler for Agent OS CLI."""
|
|
137
|
+
# Sanitize exception message to avoid leaking internal details
|
|
138
|
+
is_known_error = isinstance(e, (FileNotFoundError, ValueError, PermissionError))
|
|
139
|
+
error_msg = "A file, value, or permission error occurred." if is_known_error else "An internal error occurred."
|
|
140
|
+
|
|
141
|
+
if getattr(args, "json", False) or (hasattr(args, "format") and args.format == "json"):
|
|
142
|
+
print(json.dumps({
|
|
143
|
+
"status": "error",
|
|
144
|
+
"message": error_msg,
|
|
145
|
+
"error_type": "ValidationError" if is_known_error else "InternalError"
|
|
146
|
+
}, indent=2))
|
|
147
|
+
else:
|
|
148
|
+
print(format_error(error_msg))
|
|
149
|
+
if os.environ.get("AGENTOS_DEBUG"):
|
|
150
|
+
traceback.print_exc()
|
|
151
|
+
return 1
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def handle_missing_config(path: str = ".") -> str:
|
|
155
|
+
"""Error message for a missing ``.agents/`` config directory."""
|
|
156
|
+
return format_error(
|
|
157
|
+
f"Config directory not found: {path}/.agents/",
|
|
158
|
+
suggestion="Did you mean to create one? Run: agentos init",
|
|
159
|
+
docs_path="getting-started.md",
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def handle_invalid_policy(name: str) -> str:
|
|
164
|
+
"""Error message for an unrecognised policy template name."""
|
|
165
|
+
available = ", ".join(AVAILABLE_POLICIES)
|
|
166
|
+
suggestion = f"Available policies: {available}"
|
|
167
|
+
match = _difflib_best_match(name, list(AVAILABLE_POLICIES))
|
|
168
|
+
if match:
|
|
169
|
+
suggestion += f". Did you mean '{match}'?"
|
|
170
|
+
return format_error(
|
|
171
|
+
f"Unknown policy template: '{name}'",
|
|
172
|
+
suggestion=suggestion,
|
|
173
|
+
docs_path="security-spec.md",
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def handle_missing_dependency(package: str, extra: str = "") -> str:
|
|
178
|
+
"""Error message when an optional dependency is missing."""
|
|
179
|
+
install_cmd = f"pip install agent-os-kernel[{extra}]" if extra else f"pip install {package}"
|
|
180
|
+
return format_error(
|
|
181
|
+
f"Required package not installed: {package}",
|
|
182
|
+
suggestion=f"Install with: {install_cmd}",
|
|
183
|
+
docs_path="installation.md",
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def handle_connection_error(host: str, port: int) -> str:
|
|
188
|
+
"""Error message for a connection failure."""
|
|
189
|
+
return format_error(
|
|
190
|
+
f"Could not connect to {host}:{port}",
|
|
191
|
+
suggestion=f"Check that the service is running on {host}:{port}",
|
|
192
|
+
)
|
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
# Copyright (c) Microsoft Corporation.
|
|
2
|
+
# Licensed under the MIT License.
|
|
3
|
+
"""Local-first code policy checker for the Agent OS CLI.
|
|
4
|
+
|
|
5
|
+
Defines ``PolicyViolation``, ``PolicyChecker``, and the helper
|
|
6
|
+
``load_cli_policy_rules()`` used by the ``agentos check`` and
|
|
7
|
+
``agentos review`` commands.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import os
|
|
13
|
+
import re
|
|
14
|
+
import subprocess
|
|
15
|
+
import warnings
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
from typing import Any
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
# ============================================================================
|
|
21
|
+
# PolicyViolation
|
|
22
|
+
# ============================================================================
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class PolicyViolation:
|
|
26
|
+
"""Represents a policy violation found in code."""
|
|
27
|
+
|
|
28
|
+
def __init__(self, line: int, code: str, violation: str, policy: str,
|
|
29
|
+
severity: str = 'high', suggestion: str | None = None) -> None:
|
|
30
|
+
self.line = line
|
|
31
|
+
self.code = code
|
|
32
|
+
self.violation = violation
|
|
33
|
+
self.policy = policy
|
|
34
|
+
self.severity = severity
|
|
35
|
+
self.suggestion = suggestion
|
|
36
|
+
|
|
37
|
+
def to_dict(self) -> dict[str, Any]:
|
|
38
|
+
"""Convert violation to dictionary for JSON output."""
|
|
39
|
+
return {
|
|
40
|
+
"line": self.line,
|
|
41
|
+
"code": self.code,
|
|
42
|
+
"violation": self.violation,
|
|
43
|
+
"policy": self.policy,
|
|
44
|
+
"severity": self.severity,
|
|
45
|
+
"suggestion": self.suggestion
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
# ============================================================================
|
|
50
|
+
# Rule Loading
|
|
51
|
+
# ============================================================================
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def load_cli_policy_rules(path: str) -> list[dict[str, Any]]:
|
|
55
|
+
"""Load CLI policy checker rules from a YAML file.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
path: Path to a YAML file with a ``rules`` section.
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
List of rule dicts suitable for ``PolicyChecker``.
|
|
62
|
+
|
|
63
|
+
Raises:
|
|
64
|
+
FileNotFoundError: If the config file does not exist.
|
|
65
|
+
ValueError: If the YAML is missing the ``rules`` section.
|
|
66
|
+
"""
|
|
67
|
+
import yaml
|
|
68
|
+
|
|
69
|
+
if not os.path.exists(path):
|
|
70
|
+
raise FileNotFoundError(f"CLI policy rules config not found: {path}")
|
|
71
|
+
|
|
72
|
+
with open(path, "r", encoding="utf-8") as fh:
|
|
73
|
+
data = yaml.safe_load(fh.read())
|
|
74
|
+
|
|
75
|
+
if not isinstance(data, dict) or "rules" not in data:
|
|
76
|
+
raise ValueError(f"YAML file must contain a 'rules' section: {path}")
|
|
77
|
+
|
|
78
|
+
return data["rules"]
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
# ============================================================================
|
|
82
|
+
# PolicyChecker
|
|
83
|
+
# ============================================================================
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class PolicyChecker:
|
|
87
|
+
"""Local-first code policy checker."""
|
|
88
|
+
|
|
89
|
+
def __init__(self, rules: list[dict[str, Any]] | None = None) -> None:
|
|
90
|
+
if rules is not None:
|
|
91
|
+
self.rules = rules
|
|
92
|
+
else:
|
|
93
|
+
self.rules = self._load_default_rules()
|
|
94
|
+
|
|
95
|
+
def _load_default_rules(self) -> list[dict[str, Any]]:
|
|
96
|
+
"""Load default safety rules.
|
|
97
|
+
|
|
98
|
+
.. deprecated::
|
|
99
|
+
Uses built-in sample rules. For production use, load an explicit
|
|
100
|
+
config with ``load_cli_policy_rules()``.
|
|
101
|
+
"""
|
|
102
|
+
warnings.warn(
|
|
103
|
+
"PolicyChecker._load_default_rules() uses built-in sample rules that may not "
|
|
104
|
+
"cover all security violations. For production use, load an "
|
|
105
|
+
"explicit config with load_cli_policy_rules(). "
|
|
106
|
+
"See examples/policies/cli-security-rules.yaml for a sample configuration.",
|
|
107
|
+
stacklevel=2,
|
|
108
|
+
)
|
|
109
|
+
return [
|
|
110
|
+
# Destructive SQL
|
|
111
|
+
{
|
|
112
|
+
'name': 'block-destructive-sql',
|
|
113
|
+
'pattern': r'\bDROP\s+(TABLE|DATABASE|SCHEMA|INDEX)\s+',
|
|
114
|
+
'message': 'Destructive SQL: DROP operation detected',
|
|
115
|
+
'severity': 'critical',
|
|
116
|
+
'suggestion': '-- Consider using soft delete or archiving instead',
|
|
117
|
+
'languages': ['sql', 'python', 'javascript', 'typescript', 'php', 'ruby', 'java']
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
'name': 'block-destructive-sql',
|
|
121
|
+
'pattern': r'\bDELETE\s+FROM\s+\w+\s*(;|$|WHERE\s+1\s*=\s*1)',
|
|
122
|
+
'message': 'Destructive SQL: DELETE without proper WHERE clause',
|
|
123
|
+
'severity': 'critical',
|
|
124
|
+
'suggestion': '-- Add a specific WHERE clause to limit deletion',
|
|
125
|
+
'languages': ['sql', 'python', 'javascript', 'typescript', 'php', 'ruby', 'java']
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
'name': 'block-destructive-sql',
|
|
129
|
+
'pattern': r'\bTRUNCATE\s+TABLE\s+',
|
|
130
|
+
'message': 'Destructive SQL: TRUNCATE operation detected',
|
|
131
|
+
'severity': 'critical',
|
|
132
|
+
'suggestion': '-- Consider archiving data before truncating',
|
|
133
|
+
'languages': ['sql', 'python', 'javascript', 'typescript', 'php', 'ruby', 'java']
|
|
134
|
+
},
|
|
135
|
+
# File deletion
|
|
136
|
+
{
|
|
137
|
+
'name': 'block-file-deletes',
|
|
138
|
+
'pattern': r'\brm\s+(-rf|-fr|--recursive\s+--force)\s+',
|
|
139
|
+
'message': 'Destructive operation: Recursive force delete (rm -rf)',
|
|
140
|
+
'severity': 'critical',
|
|
141
|
+
'suggestion': '# Use safer alternatives like trash-cli or move to backup',
|
|
142
|
+
'languages': ['bash', 'shell', 'sh', 'zsh']
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
'name': 'block-file-deletes',
|
|
146
|
+
'pattern': r'\bshutil\s*\.\s*rmtree\s*\(',
|
|
147
|
+
'message': 'Recursive directory deletion (shutil.rmtree)',
|
|
148
|
+
'severity': 'high',
|
|
149
|
+
'suggestion': '# Consider using send2trash for safer deletion',
|
|
150
|
+
'languages': ['python']
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
'name': 'block-file-deletes',
|
|
154
|
+
'pattern': r'\bos\s*\.\s*(remove|unlink|rmdir)\s*\(',
|
|
155
|
+
'message': 'File/directory deletion operation detected',
|
|
156
|
+
'severity': 'medium',
|
|
157
|
+
'languages': ['python']
|
|
158
|
+
},
|
|
159
|
+
# Secret exposure
|
|
160
|
+
{
|
|
161
|
+
'name': 'block-secret-exposure',
|
|
162
|
+
'pattern': r'(api[_-]?key|apikey|api[_-]?secret)\s*[=:]\s*["\'][a-zA-Z0-9_-]{20,}["\']',
|
|
163
|
+
'message': 'Hardcoded API key detected',
|
|
164
|
+
'severity': 'critical',
|
|
165
|
+
'suggestion': '# Use environment variables: os.environ["API_KEY"]',
|
|
166
|
+
'languages': None # All languages
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
'name': 'block-secret-exposure',
|
|
170
|
+
'pattern': r'(password|passwd|pwd)\s*[=:]\s*["\'][^"\']+["\']',
|
|
171
|
+
'message': 'Hardcoded password detected',
|
|
172
|
+
'severity': 'critical',
|
|
173
|
+
'suggestion': '# Use environment variables or a secrets manager',
|
|
174
|
+
'languages': None
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
'name': 'block-secret-exposure',
|
|
178
|
+
'pattern': r'AKIA[0-9A-Z]{16}',
|
|
179
|
+
'message': 'AWS Access Key ID detected in code',
|
|
180
|
+
'severity': 'critical',
|
|
181
|
+
'languages': None
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
'name': 'block-secret-exposure',
|
|
185
|
+
'pattern': r'-----BEGIN\s+(RSA|DSA|EC|OPENSSH)\s+PRIVATE\s+KEY-----',
|
|
186
|
+
'message': 'Private key detected in code',
|
|
187
|
+
'severity': 'critical',
|
|
188
|
+
'languages': None
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
'name': 'block-secret-exposure',
|
|
192
|
+
'pattern': r'gh[pousr]_[A-Za-z0-9_]{36,}',
|
|
193
|
+
'message': 'GitHub token detected in code',
|
|
194
|
+
'severity': 'critical',
|
|
195
|
+
'languages': None
|
|
196
|
+
},
|
|
197
|
+
# Privilege escalation
|
|
198
|
+
{
|
|
199
|
+
'name': 'block-privilege-escalation',
|
|
200
|
+
'pattern': r'\bsudo\s+',
|
|
201
|
+
'message': 'Privilege escalation: sudo command detected',
|
|
202
|
+
'severity': 'high',
|
|
203
|
+
'suggestion': '# Avoid sudo in scripts - run with appropriate permissions',
|
|
204
|
+
'languages': ['bash', 'shell', 'sh', 'zsh']
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
'name': 'block-privilege-escalation',
|
|
208
|
+
'pattern': r'\bchmod\s+777\s+',
|
|
209
|
+
'message': 'Insecure permissions: chmod 777 detected',
|
|
210
|
+
'severity': 'high',
|
|
211
|
+
'suggestion': '# Use more restrictive permissions: chmod 755 or chmod 644',
|
|
212
|
+
'languages': ['bash', 'shell', 'sh', 'zsh']
|
|
213
|
+
},
|
|
214
|
+
# Code injection
|
|
215
|
+
{
|
|
216
|
+
'name': 'block-arbitrary-exec',
|
|
217
|
+
'pattern': r'\beval\s*\(',
|
|
218
|
+
'message': 'Code injection risk: eval() usage detected',
|
|
219
|
+
'severity': 'high',
|
|
220
|
+
'suggestion': '# Remove eval() and use safer alternatives',
|
|
221
|
+
'languages': ['python', 'javascript', 'typescript', 'php', 'ruby']
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
'name': 'block-arbitrary-exec',
|
|
225
|
+
'pattern': r'\bos\s*\.\s*system\s*\([^)]*(\+|%|\.format|f["\'])',
|
|
226
|
+
'message': 'Command injection risk: os.system with dynamic input',
|
|
227
|
+
'severity': 'critical',
|
|
228
|
+
'suggestion': '# Use subprocess with shell=False and proper argument handling',
|
|
229
|
+
'languages': ['python']
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
'name': 'block-arbitrary-exec',
|
|
233
|
+
'pattern': r'\bexec\s*\(',
|
|
234
|
+
'message': 'Code injection risk: exec() usage detected',
|
|
235
|
+
'severity': 'high',
|
|
236
|
+
'suggestion': '# Remove exec() and use safer alternatives',
|
|
237
|
+
'languages': ['python']
|
|
238
|
+
},
|
|
239
|
+
# SQL injection
|
|
240
|
+
{
|
|
241
|
+
'name': 'block-sql-injection',
|
|
242
|
+
'pattern': r'["\']\s*\+\s*[^"\']+\s*\+\s*["\'].*(?:SELECT|INSERT|UPDATE|DELETE)',
|
|
243
|
+
'message': 'SQL injection risk: String concatenation in SQL query',
|
|
244
|
+
'severity': 'high',
|
|
245
|
+
'suggestion': '# Use parameterized queries instead',
|
|
246
|
+
'languages': ['python', 'javascript', 'typescript', 'php', 'ruby', 'java']
|
|
247
|
+
},
|
|
248
|
+
# XSS
|
|
249
|
+
{
|
|
250
|
+
'name': 'block-xss',
|
|
251
|
+
'pattern': r'\.innerHTML\s*=',
|
|
252
|
+
'message': 'XSS risk: innerHTML assignment detected',
|
|
253
|
+
'severity': 'medium',
|
|
254
|
+
'suggestion': '// Use textContent or a sanitization library',
|
|
255
|
+
'languages': ['javascript', 'typescript']
|
|
256
|
+
},
|
|
257
|
+
]
|
|
258
|
+
|
|
259
|
+
def _get_language(self, filepath: str) -> str:
|
|
260
|
+
"""Detect language from file extension."""
|
|
261
|
+
ext_map = {
|
|
262
|
+
'.py': 'python',
|
|
263
|
+
'.js': 'javascript',
|
|
264
|
+
'.ts': 'typescript',
|
|
265
|
+
'.jsx': 'javascript',
|
|
266
|
+
'.tsx': 'typescript',
|
|
267
|
+
'.sql': 'sql',
|
|
268
|
+
'.sh': 'shell',
|
|
269
|
+
'.bash': 'bash',
|
|
270
|
+
'.zsh': 'zsh',
|
|
271
|
+
'.php': 'php',
|
|
272
|
+
'.rb': 'ruby',
|
|
273
|
+
'.java': 'java',
|
|
274
|
+
'.cs': 'csharp',
|
|
275
|
+
'.go': 'go',
|
|
276
|
+
}
|
|
277
|
+
ext = Path(filepath).suffix.lower()
|
|
278
|
+
return ext_map.get(ext, 'unknown')
|
|
279
|
+
|
|
280
|
+
def check_file(self, filepath: str) -> list[PolicyViolation]:
|
|
281
|
+
"""Check a file for policy violations."""
|
|
282
|
+
path = Path(filepath)
|
|
283
|
+
if not path.exists():
|
|
284
|
+
raise FileNotFoundError(f"File not found: {filepath}")
|
|
285
|
+
|
|
286
|
+
language = self._get_language(filepath)
|
|
287
|
+
content = path.read_text(encoding='utf-8', errors='ignore')
|
|
288
|
+
lines = content.split('\n')
|
|
289
|
+
|
|
290
|
+
violations = []
|
|
291
|
+
|
|
292
|
+
for rule in self.rules:
|
|
293
|
+
# Check language filter
|
|
294
|
+
if rule['languages'] and language not in rule['languages']:
|
|
295
|
+
continue
|
|
296
|
+
|
|
297
|
+
pattern = re.compile(rule['pattern'], re.IGNORECASE)
|
|
298
|
+
|
|
299
|
+
for i, line in enumerate(lines, 1):
|
|
300
|
+
if pattern.search(line):
|
|
301
|
+
violations.append(PolicyViolation(
|
|
302
|
+
line=i,
|
|
303
|
+
code=line.strip(),
|
|
304
|
+
violation=rule['message'],
|
|
305
|
+
policy=rule['name'],
|
|
306
|
+
severity=rule['severity'],
|
|
307
|
+
suggestion=rule.get('suggestion')
|
|
308
|
+
))
|
|
309
|
+
|
|
310
|
+
return violations
|
|
311
|
+
|
|
312
|
+
def check_staged_files(self) -> dict[str, list[PolicyViolation]]:
|
|
313
|
+
"""Check all staged git files for violations."""
|
|
314
|
+
try:
|
|
315
|
+
result = subprocess.run(
|
|
316
|
+
['git', 'diff', '--cached', '--name-only'],
|
|
317
|
+
capture_output=True, text=True, check=True
|
|
318
|
+
)
|
|
319
|
+
files = [f for f in result.stdout.strip().split('\n') if f]
|
|
320
|
+
except subprocess.CalledProcessError:
|
|
321
|
+
return {}
|
|
322
|
+
|
|
323
|
+
all_violations = {}
|
|
324
|
+
for filepath in files:
|
|
325
|
+
if Path(filepath).exists():
|
|
326
|
+
violations = self.check_file(filepath)
|
|
327
|
+
if violations:
|
|
328
|
+
all_violations[filepath] = violations
|
|
329
|
+
|
|
330
|
+
return all_violations
|
agent_os/compat.py
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# Copyright (c) Microsoft Corporation.
|
|
2
|
+
# Licensed under the MIT License.
|
|
3
|
+
"""Graceful degradation helpers for optional toolkit dependencies.
|
|
4
|
+
|
|
5
|
+
Provides no-op fallbacks so consumers can optionally depend on the
|
|
6
|
+
toolkit without try/except import boilerplate.
|
|
7
|
+
|
|
8
|
+
Usage::
|
|
9
|
+
|
|
10
|
+
from agent_os.compat import PolicyEvaluator, get_evaluator
|
|
11
|
+
|
|
12
|
+
# Real class if agent-os-kernel installed, no-op otherwise.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import logging
|
|
18
|
+
from typing import Any
|
|
19
|
+
|
|
20
|
+
logger = logging.getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
try:
|
|
23
|
+
from agent_os.policies.evaluator import PolicyEvaluator as _RealEvaluator
|
|
24
|
+
|
|
25
|
+
TOOLKIT_AVAILABLE = True
|
|
26
|
+
except ImportError:
|
|
27
|
+
TOOLKIT_AVAILABLE = False
|
|
28
|
+
_RealEvaluator = None # type: ignore[assignment, misc]
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class _AllowDecision:
|
|
32
|
+
allowed = True
|
|
33
|
+
reason = "no-op"
|
|
34
|
+
matched_rule = None
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class NoOpPolicyEvaluator:
|
|
38
|
+
"""No-op policy evaluator — allows all actions."""
|
|
39
|
+
|
|
40
|
+
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
41
|
+
logger.debug("NoOpPolicyEvaluator: toolkit not installed, all actions allowed")
|
|
42
|
+
|
|
43
|
+
def evaluate(self, *args: Any, **kwargs: Any) -> _AllowDecision:
|
|
44
|
+
return _AllowDecision()
|
|
45
|
+
|
|
46
|
+
def load_policies(self, *args: Any, **kwargs: Any) -> None:
|
|
47
|
+
pass
|
|
48
|
+
|
|
49
|
+
def add_backend(self, *args: Any, **kwargs: Any) -> None:
|
|
50
|
+
pass
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class NoOpGovernanceMiddleware:
|
|
54
|
+
"""No-op governance middleware — passes all calls through."""
|
|
55
|
+
|
|
56
|
+
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
57
|
+
logger.debug("NoOpGovernanceMiddleware: toolkit not installed")
|
|
58
|
+
|
|
59
|
+
def __call__(self, func: Any) -> Any:
|
|
60
|
+
return func
|
|
61
|
+
|
|
62
|
+
def wrap(self, func: Any) -> Any:
|
|
63
|
+
return func
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def get_evaluator(**kwargs: Any) -> Any:
|
|
67
|
+
"""Get a PolicyEvaluator if available, otherwise a no-op."""
|
|
68
|
+
if TOOLKIT_AVAILABLE and _RealEvaluator is not None:
|
|
69
|
+
return _RealEvaluator(**kwargs)
|
|
70
|
+
return NoOpPolicyEvaluator(**kwargs)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
PolicyEvaluator = _RealEvaluator if TOOLKIT_AVAILABLE else NoOpPolicyEvaluator # type: ignore[assignment]
|
|
74
|
+
GovernanceMiddleware = NoOpGovernanceMiddleware
|