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/memory_guard.py
ADDED
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
# Copyright (c) Microsoft Corporation.
|
|
2
|
+
# Licensed under the MIT License.
|
|
3
|
+
"""Memory & Context Poisoning Detection — OWASP ASI06.
|
|
4
|
+
|
|
5
|
+
Guards agent memory stores (RAG, episodic, working memory) against
|
|
6
|
+
poisoning attacks where adversaries inject malicious data to manipulate
|
|
7
|
+
agent behaviour.
|
|
8
|
+
|
|
9
|
+
Public Preview protections:
|
|
10
|
+
- **Hash integrity**: SHA-256 hash per memory entry; detects tampering.
|
|
11
|
+
- **Injection pattern detection**: Blocks prompt-injection payloads
|
|
12
|
+
written into memory.
|
|
13
|
+
- **Content validation**: Rejects entries with dangerous code or
|
|
14
|
+
excessive special-character manipulation.
|
|
15
|
+
- **Write audit trail**: Logs every memory write with timestamp and
|
|
16
|
+
source for forensic review.
|
|
17
|
+
|
|
18
|
+
Architecture:
|
|
19
|
+
MemoryGuard
|
|
20
|
+
├─ validate_write() — pre-write content screening
|
|
21
|
+
├─ verify_integrity() — post-read hash verification
|
|
22
|
+
└─ scan_memory() — batch scan for poisoning indicators
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
import hashlib
|
|
28
|
+
import logging
|
|
29
|
+
import re
|
|
30
|
+
import unicodedata
|
|
31
|
+
from collections.abc import Sequence
|
|
32
|
+
from dataclasses import dataclass, field
|
|
33
|
+
from datetime import datetime, timezone
|
|
34
|
+
from enum import Enum
|
|
35
|
+
|
|
36
|
+
logger = logging.getLogger(__name__)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
# ---------------------------------------------------------------------------
|
|
40
|
+
# Data models
|
|
41
|
+
# ---------------------------------------------------------------------------
|
|
42
|
+
|
|
43
|
+
class AlertSeverity(Enum):
|
|
44
|
+
"""Severity level for memory poisoning alerts."""
|
|
45
|
+
LOW = "low"
|
|
46
|
+
MEDIUM = "medium"
|
|
47
|
+
HIGH = "high"
|
|
48
|
+
CRITICAL = "critical"
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class AlertType(Enum):
|
|
52
|
+
"""Classification of a memory poisoning alert."""
|
|
53
|
+
INJECTION_PATTERN = "injection_pattern"
|
|
54
|
+
CODE_INJECTION = "code_injection"
|
|
55
|
+
INTEGRITY_VIOLATION = "integrity_violation"
|
|
56
|
+
UNICODE_MANIPULATION = "unicode_manipulation"
|
|
57
|
+
EXCESSIVE_SPECIAL_CHARS = "excessive_special_chars"
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@dataclass
|
|
61
|
+
class MemoryEntry:
|
|
62
|
+
"""A single entry in agent memory with integrity metadata.
|
|
63
|
+
|
|
64
|
+
Attributes:
|
|
65
|
+
content: The text content stored in memory.
|
|
66
|
+
source: Identifier of the component that wrote this entry.
|
|
67
|
+
timestamp: UTC timestamp of when the entry was created.
|
|
68
|
+
content_hash: SHA-256 hex digest of ``content``.
|
|
69
|
+
"""
|
|
70
|
+
content: str
|
|
71
|
+
source: str
|
|
72
|
+
timestamp: datetime
|
|
73
|
+
content_hash: str
|
|
74
|
+
|
|
75
|
+
@staticmethod
|
|
76
|
+
def compute_hash(content: str) -> str:
|
|
77
|
+
"""Compute SHA-256 hash of content."""
|
|
78
|
+
return hashlib.sha256(content.encode("utf-8")).hexdigest()
|
|
79
|
+
|
|
80
|
+
@classmethod
|
|
81
|
+
def create(cls, content: str, source: str) -> MemoryEntry:
|
|
82
|
+
"""Factory that auto-generates timestamp and hash."""
|
|
83
|
+
return cls(
|
|
84
|
+
content=content,
|
|
85
|
+
source=source,
|
|
86
|
+
timestamp=datetime.now(timezone.utc),
|
|
87
|
+
content_hash=cls.compute_hash(content),
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
@dataclass
|
|
92
|
+
class Alert:
|
|
93
|
+
"""A poisoning indicator found during memory scanning.
|
|
94
|
+
|
|
95
|
+
Attributes:
|
|
96
|
+
alert_type: Classification of the alert.
|
|
97
|
+
severity: How critical the finding is.
|
|
98
|
+
message: Human-readable description.
|
|
99
|
+
entry_source: Source field of the offending entry (if available).
|
|
100
|
+
matched_pattern: The pattern that triggered this alert.
|
|
101
|
+
"""
|
|
102
|
+
alert_type: AlertType
|
|
103
|
+
severity: AlertSeverity
|
|
104
|
+
message: str
|
|
105
|
+
entry_source: str | None = None
|
|
106
|
+
matched_pattern: str | None = None
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
@dataclass
|
|
110
|
+
class ValidationResult:
|
|
111
|
+
"""Outcome of a memory write validation.
|
|
112
|
+
|
|
113
|
+
Attributes:
|
|
114
|
+
allowed: Whether the write should be permitted.
|
|
115
|
+
alerts: Any alerts raised during validation.
|
|
116
|
+
"""
|
|
117
|
+
allowed: bool
|
|
118
|
+
alerts: list[Alert] = field(default_factory=list)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
@dataclass
|
|
122
|
+
class AuditRecord:
|
|
123
|
+
"""Immutable record of a memory write attempt.
|
|
124
|
+
|
|
125
|
+
Attributes:
|
|
126
|
+
timestamp: When the write was attempted.
|
|
127
|
+
source: Component that requested the write.
|
|
128
|
+
content_hash: SHA-256 of the content.
|
|
129
|
+
allowed: Whether the write was permitted.
|
|
130
|
+
alerts: Alerts raised (may be empty).
|
|
131
|
+
"""
|
|
132
|
+
timestamp: datetime
|
|
133
|
+
source: str
|
|
134
|
+
content_hash: str
|
|
135
|
+
allowed: bool
|
|
136
|
+
alerts: list[Alert] = field(default_factory=list)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
# ---------------------------------------------------------------------------
|
|
140
|
+
# Injection patterns (CE basics)
|
|
141
|
+
# ---------------------------------------------------------------------------
|
|
142
|
+
|
|
143
|
+
_INJECTION_PATTERNS: list[re.Pattern[str]] = [
|
|
144
|
+
re.compile(r"ignore\s+(all\s+)?previous\s+instructions", re.IGNORECASE),
|
|
145
|
+
re.compile(r"you\s+are\s+now\b", re.IGNORECASE),
|
|
146
|
+
re.compile(r"system\s*prompt\s*:", re.IGNORECASE),
|
|
147
|
+
re.compile(r"disregard\s+(all\s+)?(prior|above)\s+", re.IGNORECASE),
|
|
148
|
+
re.compile(r"forget\s+(everything|all|your)\s+", re.IGNORECASE),
|
|
149
|
+
re.compile(r"new\s+instructions?\s*:", re.IGNORECASE),
|
|
150
|
+
re.compile(r"override\s+(previous\s+)?instructions", re.IGNORECASE),
|
|
151
|
+
]
|
|
152
|
+
|
|
153
|
+
_CODE_INJECTION_PATTERNS: list[re.Pattern[str]] = [
|
|
154
|
+
re.compile(r"```\s*python\s*\n\s*import\s+os\b", re.IGNORECASE),
|
|
155
|
+
re.compile(r"```\s*python\s*\n\s*import\s+subprocess\b", re.IGNORECASE),
|
|
156
|
+
re.compile(r"```\s*python\s*\n\s*import\s+shutil\b", re.IGNORECASE),
|
|
157
|
+
re.compile(r"exec\s*\(", re.IGNORECASE),
|
|
158
|
+
re.compile(r"eval\s*\(", re.IGNORECASE),
|
|
159
|
+
re.compile(r"__import__\s*\(", re.IGNORECASE),
|
|
160
|
+
]
|
|
161
|
+
|
|
162
|
+
# Fraction of characters that are "special" before we flag the entry
|
|
163
|
+
_SPECIAL_CHAR_THRESHOLD = 0.3
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
# ---------------------------------------------------------------------------
|
|
167
|
+
# MemoryGuard
|
|
168
|
+
# ---------------------------------------------------------------------------
|
|
169
|
+
|
|
170
|
+
class MemoryGuard:
|
|
171
|
+
"""Guards agent memory against poisoning attacks (OWASP ASI06).
|
|
172
|
+
|
|
173
|
+
Usage::
|
|
174
|
+
|
|
175
|
+
guard = MemoryGuard()
|
|
176
|
+
result = guard.validate_write("some content", source="rag-loader")
|
|
177
|
+
if result.allowed:
|
|
178
|
+
store.save(MemoryEntry.create("some content", "rag-loader"))
|
|
179
|
+
"""
|
|
180
|
+
|
|
181
|
+
def __init__(self) -> None:
|
|
182
|
+
self._audit_log: list[AuditRecord] = []
|
|
183
|
+
|
|
184
|
+
# -- public API ---------------------------------------------------------
|
|
185
|
+
|
|
186
|
+
def validate_write(self, content: str, source: str) -> ValidationResult:
|
|
187
|
+
"""Check content for injection patterns before writing to memory.
|
|
188
|
+
|
|
189
|
+
Returns a ``ValidationResult`` indicating whether the write should
|
|
190
|
+
proceed and any alerts raised.
|
|
191
|
+
"""
|
|
192
|
+
alerts: list[Alert] = []
|
|
193
|
+
|
|
194
|
+
try:
|
|
195
|
+
alerts.extend(self._check_injection_patterns(content, source))
|
|
196
|
+
alerts.extend(self._check_code_injection(content, source))
|
|
197
|
+
alerts.extend(self._check_special_characters(content, source))
|
|
198
|
+
alerts.extend(self._check_unicode_manipulation(content, source))
|
|
199
|
+
except Exception:
|
|
200
|
+
# Fail closed: block the write if validation itself errors
|
|
201
|
+
logger.error(
|
|
202
|
+
"Memory validation error — blocking write (fail closed) | source=%s",
|
|
203
|
+
source, exc_info=True,
|
|
204
|
+
)
|
|
205
|
+
alerts.append(Alert(
|
|
206
|
+
alert_type=AlertType.INJECTION_PATTERN,
|
|
207
|
+
severity=AlertSeverity.CRITICAL,
|
|
208
|
+
message=f"Validation error — write blocked (fail closed) for source {source}",
|
|
209
|
+
entry_source=source,
|
|
210
|
+
))
|
|
211
|
+
|
|
212
|
+
allowed = not any(
|
|
213
|
+
a.severity in (AlertSeverity.HIGH, AlertSeverity.CRITICAL)
|
|
214
|
+
for a in alerts
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
result = ValidationResult(allowed=allowed, alerts=alerts)
|
|
218
|
+
|
|
219
|
+
# Audit trail
|
|
220
|
+
record = AuditRecord(
|
|
221
|
+
timestamp=datetime.now(timezone.utc),
|
|
222
|
+
source=source,
|
|
223
|
+
content_hash=MemoryEntry.compute_hash(content),
|
|
224
|
+
allowed=allowed,
|
|
225
|
+
alerts=list(alerts),
|
|
226
|
+
)
|
|
227
|
+
self._audit_log.append(record)
|
|
228
|
+
|
|
229
|
+
if not allowed:
|
|
230
|
+
logger.warning(
|
|
231
|
+
"Memory write BLOCKED from source=%s alerts=%d",
|
|
232
|
+
source,
|
|
233
|
+
len(alerts),
|
|
234
|
+
)
|
|
235
|
+
else:
|
|
236
|
+
logger.debug(
|
|
237
|
+
"Memory write allowed from source=%s alerts=%d",
|
|
238
|
+
source,
|
|
239
|
+
len(alerts),
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
return result
|
|
243
|
+
|
|
244
|
+
def verify_integrity(self, entry: MemoryEntry) -> bool:
|
|
245
|
+
"""Verify hash integrity of a memory entry.
|
|
246
|
+
|
|
247
|
+
Returns ``True`` if the stored hash matches a fresh computation.
|
|
248
|
+
"""
|
|
249
|
+
expected = MemoryEntry.compute_hash(entry.content)
|
|
250
|
+
intact = expected == entry.content_hash
|
|
251
|
+
if not intact:
|
|
252
|
+
logger.warning(
|
|
253
|
+
"Integrity violation for entry from source=%s "
|
|
254
|
+
"(expected=%s, stored=%s)",
|
|
255
|
+
entry.source,
|
|
256
|
+
expected,
|
|
257
|
+
entry.content_hash,
|
|
258
|
+
)
|
|
259
|
+
return intact
|
|
260
|
+
|
|
261
|
+
def scan_memory(self, entries: Sequence[MemoryEntry]) -> list[Alert]:
|
|
262
|
+
"""Scan existing memory entries for poisoning indicators.
|
|
263
|
+
|
|
264
|
+
Checks both content patterns and hash integrity for every entry.
|
|
265
|
+
"""
|
|
266
|
+
all_alerts: list[Alert] = []
|
|
267
|
+
for entry in entries:
|
|
268
|
+
try:
|
|
269
|
+
# Integrity check
|
|
270
|
+
if not self.verify_integrity(entry):
|
|
271
|
+
all_alerts.append(Alert(
|
|
272
|
+
alert_type=AlertType.INTEGRITY_VIOLATION,
|
|
273
|
+
severity=AlertSeverity.CRITICAL,
|
|
274
|
+
message=f"Hash mismatch for entry from {entry.source}",
|
|
275
|
+
entry_source=entry.source,
|
|
276
|
+
))
|
|
277
|
+
|
|
278
|
+
# Content checks (reuse validate_write logic)
|
|
279
|
+
all_alerts.extend(self._check_injection_patterns(entry.content, entry.source))
|
|
280
|
+
all_alerts.extend(self._check_code_injection(entry.content, entry.source))
|
|
281
|
+
all_alerts.extend(self._check_special_characters(entry.content, entry.source))
|
|
282
|
+
all_alerts.extend(self._check_unicode_manipulation(entry.content, entry.source))
|
|
283
|
+
except Exception:
|
|
284
|
+
logger.error(
|
|
285
|
+
"Error scanning memory entry — flagging as suspicious | source=%s",
|
|
286
|
+
entry.source, exc_info=True,
|
|
287
|
+
)
|
|
288
|
+
all_alerts.append(Alert(
|
|
289
|
+
alert_type=AlertType.INTEGRITY_VIOLATION,
|
|
290
|
+
severity=AlertSeverity.CRITICAL,
|
|
291
|
+
message=f"Scan error for entry from {entry.source} — flagged as suspicious",
|
|
292
|
+
entry_source=entry.source,
|
|
293
|
+
))
|
|
294
|
+
|
|
295
|
+
return all_alerts
|
|
296
|
+
|
|
297
|
+
@property
|
|
298
|
+
def audit_log(self) -> list[AuditRecord]:
|
|
299
|
+
"""Return a copy of the audit trail."""
|
|
300
|
+
return list(self._audit_log)
|
|
301
|
+
|
|
302
|
+
# -- internal checks ----------------------------------------------------
|
|
303
|
+
|
|
304
|
+
def _check_injection_patterns(
|
|
305
|
+
self, content: str, source: str
|
|
306
|
+
) -> list[Alert]:
|
|
307
|
+
alerts: list[Alert] = []
|
|
308
|
+
for pattern in _INJECTION_PATTERNS:
|
|
309
|
+
if pattern.search(content):
|
|
310
|
+
alerts.append(Alert(
|
|
311
|
+
alert_type=AlertType.INJECTION_PATTERN,
|
|
312
|
+
severity=AlertSeverity.HIGH,
|
|
313
|
+
message=f"Prompt injection pattern detected: {pattern.pattern}",
|
|
314
|
+
entry_source=source,
|
|
315
|
+
matched_pattern=pattern.pattern,
|
|
316
|
+
))
|
|
317
|
+
return alerts
|
|
318
|
+
|
|
319
|
+
def _check_code_injection(
|
|
320
|
+
self, content: str, source: str
|
|
321
|
+
) -> list[Alert]:
|
|
322
|
+
alerts: list[Alert] = []
|
|
323
|
+
for pattern in _CODE_INJECTION_PATTERNS:
|
|
324
|
+
if pattern.search(content):
|
|
325
|
+
alerts.append(Alert(
|
|
326
|
+
alert_type=AlertType.CODE_INJECTION,
|
|
327
|
+
severity=AlertSeverity.HIGH,
|
|
328
|
+
message=f"Code injection pattern detected: {pattern.pattern}",
|
|
329
|
+
entry_source=source,
|
|
330
|
+
matched_pattern=pattern.pattern,
|
|
331
|
+
))
|
|
332
|
+
return alerts
|
|
333
|
+
|
|
334
|
+
def _check_special_characters(
|
|
335
|
+
self, content: str, source: str
|
|
336
|
+
) -> list[Alert]:
|
|
337
|
+
if not content:
|
|
338
|
+
return []
|
|
339
|
+
special = sum(
|
|
340
|
+
1 for c in content
|
|
341
|
+
if not c.isalnum() and not c.isspace()
|
|
342
|
+
)
|
|
343
|
+
ratio = special / len(content)
|
|
344
|
+
if ratio > _SPECIAL_CHAR_THRESHOLD:
|
|
345
|
+
return [Alert(
|
|
346
|
+
alert_type=AlertType.EXCESSIVE_SPECIAL_CHARS,
|
|
347
|
+
severity=AlertSeverity.MEDIUM,
|
|
348
|
+
message=(
|
|
349
|
+
f"Excessive special characters ({ratio:.0%}) "
|
|
350
|
+
f"from source {source}"
|
|
351
|
+
),
|
|
352
|
+
entry_source=source,
|
|
353
|
+
)]
|
|
354
|
+
return []
|
|
355
|
+
|
|
356
|
+
def _check_unicode_manipulation(
|
|
357
|
+
self, content: str, source: str
|
|
358
|
+
) -> list[Alert]:
|
|
359
|
+
alerts: list[Alert] = []
|
|
360
|
+
# Detect right-to-left override and other bidi control characters
|
|
361
|
+
bidi_chars = {
|
|
362
|
+
"\u200e", # LRM
|
|
363
|
+
"\u200f", # RLM
|
|
364
|
+
"\u202a", # LRE
|
|
365
|
+
"\u202b", # RLE
|
|
366
|
+
"\u202c", # PDF
|
|
367
|
+
"\u202d", # LRO
|
|
368
|
+
"\u202e", # RLO
|
|
369
|
+
"\u2066", # LRI
|
|
370
|
+
"\u2067", # RLI
|
|
371
|
+
"\u2068", # FSI
|
|
372
|
+
"\u2069", # PDI
|
|
373
|
+
}
|
|
374
|
+
found = [c for c in content if c in bidi_chars]
|
|
375
|
+
if found:
|
|
376
|
+
alerts.append(Alert(
|
|
377
|
+
alert_type=AlertType.UNICODE_MANIPULATION,
|
|
378
|
+
severity=AlertSeverity.HIGH,
|
|
379
|
+
message=(
|
|
380
|
+
f"Bidirectional unicode control characters detected "
|
|
381
|
+
f"({len(found)} occurrences) from source {source}"
|
|
382
|
+
),
|
|
383
|
+
entry_source=source,
|
|
384
|
+
))
|
|
385
|
+
|
|
386
|
+
# Detect homoglyph-heavy content (characters from mixed scripts)
|
|
387
|
+
scripts: set[str] = set()
|
|
388
|
+
for c in content:
|
|
389
|
+
if c.isalpha():
|
|
390
|
+
# Use unicodedata to get script-like categorisation
|
|
391
|
+
name = unicodedata.name(c, "")
|
|
392
|
+
if name.startswith("LATIN"):
|
|
393
|
+
scripts.add("LATIN")
|
|
394
|
+
elif name.startswith("CYRILLIC"):
|
|
395
|
+
scripts.add("CYRILLIC")
|
|
396
|
+
elif name.startswith("GREEK"):
|
|
397
|
+
scripts.add("GREEK")
|
|
398
|
+
if len(scripts) > 1:
|
|
399
|
+
alerts.append(Alert(
|
|
400
|
+
alert_type=AlertType.UNICODE_MANIPULATION,
|
|
401
|
+
severity=AlertSeverity.MEDIUM,
|
|
402
|
+
message=(
|
|
403
|
+
f"Mixed unicode scripts detected ({', '.join(sorted(scripts))}) "
|
|
404
|
+
f"— possible homoglyph attack from source {source}"
|
|
405
|
+
),
|
|
406
|
+
entry_source=source,
|
|
407
|
+
))
|
|
408
|
+
|
|
409
|
+
return alerts
|
agent_os/metrics.py
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# Copyright (c) Microsoft Corporation.
|
|
2
|
+
# Licensed under the MIT License.
|
|
3
|
+
"""
|
|
4
|
+
Governance Metrics Collector — Tracks policy enforcement statistics.
|
|
5
|
+
|
|
6
|
+
Thread-safe singleton that records policy checks, violations, approvals,
|
|
7
|
+
and blocked tool calls across all governance adapters.
|
|
8
|
+
|
|
9
|
+
Example:
|
|
10
|
+
>>> from agent_os.metrics import metrics
|
|
11
|
+
>>>
|
|
12
|
+
>>> metrics.record_check("langchain", latency_ms=1.2, approved=True)
|
|
13
|
+
>>> metrics.record_violation("crewai")
|
|
14
|
+
>>> metrics.record_blocked("crewai")
|
|
15
|
+
>>> snap = metrics.snapshot()
|
|
16
|
+
>>> snap["total_checks"] # 1
|
|
17
|
+
>>> snap["violations"] # 1
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
import threading
|
|
23
|
+
from dataclasses import dataclass, field
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass
|
|
27
|
+
class GovernanceMetrics:
|
|
28
|
+
"""Collects governance enforcement metrics across adapters.
|
|
29
|
+
|
|
30
|
+
All public methods are thread-safe via an internal lock.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
total_checks: int = 0
|
|
34
|
+
violations: int = 0
|
|
35
|
+
approvals: int = 0
|
|
36
|
+
blocked: int = 0
|
|
37
|
+
avg_latency_ms: float = 0.0
|
|
38
|
+
|
|
39
|
+
_adapter_checks: dict[str, int] = field(default_factory=dict)
|
|
40
|
+
_adapter_violations: dict[str, int] = field(default_factory=dict)
|
|
41
|
+
_adapter_blocked: dict[str, int] = field(default_factory=dict)
|
|
42
|
+
_total_latency_ms: float = field(default=0.0, repr=False)
|
|
43
|
+
_lock: threading.Lock = field(default_factory=threading.Lock, repr=False)
|
|
44
|
+
|
|
45
|
+
def record_check(self, adapter: str, latency_ms: float, approved: bool) -> None:
|
|
46
|
+
"""Record a policy check result.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
adapter: Adapter name (e.g. ``"langchain"``).
|
|
50
|
+
latency_ms: Time taken for the check in milliseconds.
|
|
51
|
+
approved: Whether the action was approved.
|
|
52
|
+
"""
|
|
53
|
+
with self._lock:
|
|
54
|
+
self.total_checks += 1
|
|
55
|
+
self._total_latency_ms += latency_ms
|
|
56
|
+
self.avg_latency_ms = self._total_latency_ms / self.total_checks
|
|
57
|
+
self._adapter_checks[adapter] = self._adapter_checks.get(adapter, 0) + 1
|
|
58
|
+
if approved:
|
|
59
|
+
self.approvals += 1
|
|
60
|
+
else:
|
|
61
|
+
self.violations += 1
|
|
62
|
+
self._adapter_violations[adapter] = (
|
|
63
|
+
self._adapter_violations.get(adapter, 0) + 1
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
def record_violation(self, adapter: str) -> None:
|
|
67
|
+
"""Record a standalone policy violation.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
adapter: Adapter name.
|
|
71
|
+
"""
|
|
72
|
+
with self._lock:
|
|
73
|
+
self.violations += 1
|
|
74
|
+
self._adapter_violations[adapter] = (
|
|
75
|
+
self._adapter_violations.get(adapter, 0) + 1
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
def record_blocked(self, adapter: str) -> None:
|
|
79
|
+
"""Record a blocked tool call.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
adapter: Adapter name.
|
|
83
|
+
"""
|
|
84
|
+
with self._lock:
|
|
85
|
+
self.blocked += 1
|
|
86
|
+
self._adapter_blocked[adapter] = (
|
|
87
|
+
self._adapter_blocked.get(adapter, 0) + 1
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
def snapshot(self) -> dict:
|
|
91
|
+
"""Return a JSON-serializable snapshot of all metrics.
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
Dictionary containing global and per-adapter metrics.
|
|
95
|
+
"""
|
|
96
|
+
with self._lock:
|
|
97
|
+
return {
|
|
98
|
+
"total_checks": self.total_checks,
|
|
99
|
+
"violations": self.violations,
|
|
100
|
+
"approvals": self.approvals,
|
|
101
|
+
"blocked": self.blocked,
|
|
102
|
+
"avg_latency_ms": round(self.avg_latency_ms, 4),
|
|
103
|
+
"adapters": {
|
|
104
|
+
adapter: {
|
|
105
|
+
"checks": self._adapter_checks.get(adapter, 0),
|
|
106
|
+
"violations": self._adapter_violations.get(adapter, 0),
|
|
107
|
+
"blocked": self._adapter_blocked.get(adapter, 0),
|
|
108
|
+
}
|
|
109
|
+
for adapter in sorted(
|
|
110
|
+
set(self._adapter_checks)
|
|
111
|
+
| set(self._adapter_violations)
|
|
112
|
+
| set(self._adapter_blocked)
|
|
113
|
+
)
|
|
114
|
+
},
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
def reset(self) -> None:
|
|
118
|
+
"""Reset all counters to zero (useful for test isolation)."""
|
|
119
|
+
with self._lock:
|
|
120
|
+
self.total_checks = 0
|
|
121
|
+
self.violations = 0
|
|
122
|
+
self.approvals = 0
|
|
123
|
+
self.blocked = 0
|
|
124
|
+
self.avg_latency_ms = 0.0
|
|
125
|
+
self._total_latency_ms = 0.0
|
|
126
|
+
self._adapter_checks.clear()
|
|
127
|
+
self._adapter_violations.clear()
|
|
128
|
+
self._adapter_blocked.clear()
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
# Module-level singleton
|
|
132
|
+
metrics = GovernanceMetrics()
|
|
133
|
+
|
|
134
|
+
__all__ = ["GovernanceMetrics", "metrics"]
|