proxilion 0.0.1__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.
- proxilion/__init__.py +136 -0
- proxilion/audit/__init__.py +133 -0
- proxilion/audit/base_exporters.py +527 -0
- proxilion/audit/compliance/__init__.py +130 -0
- proxilion/audit/compliance/base.py +457 -0
- proxilion/audit/compliance/eu_ai_act.py +603 -0
- proxilion/audit/compliance/iso27001.py +544 -0
- proxilion/audit/compliance/soc2.py +491 -0
- proxilion/audit/events.py +493 -0
- proxilion/audit/explainability.py +1173 -0
- proxilion/audit/exporters/__init__.py +58 -0
- proxilion/audit/exporters/aws_s3.py +636 -0
- proxilion/audit/exporters/azure_storage.py +608 -0
- proxilion/audit/exporters/cloud_base.py +468 -0
- proxilion/audit/exporters/gcp_storage.py +570 -0
- proxilion/audit/exporters/multi_exporter.py +498 -0
- proxilion/audit/hash_chain.py +652 -0
- proxilion/audit/logger.py +543 -0
- proxilion/caching/__init__.py +49 -0
- proxilion/caching/tool_cache.py +633 -0
- proxilion/context/__init__.py +73 -0
- proxilion/context/context_window.py +556 -0
- proxilion/context/message_history.py +505 -0
- proxilion/context/session.py +735 -0
- proxilion/contrib/__init__.py +51 -0
- proxilion/contrib/anthropic.py +609 -0
- proxilion/contrib/google.py +1012 -0
- proxilion/contrib/langchain.py +641 -0
- proxilion/contrib/mcp.py +893 -0
- proxilion/contrib/openai.py +646 -0
- proxilion/core.py +3058 -0
- proxilion/decorators.py +966 -0
- proxilion/engines/__init__.py +287 -0
- proxilion/engines/base.py +266 -0
- proxilion/engines/casbin_engine.py +412 -0
- proxilion/engines/opa_engine.py +493 -0
- proxilion/engines/simple.py +437 -0
- proxilion/exceptions.py +887 -0
- proxilion/guards/__init__.py +54 -0
- proxilion/guards/input_guard.py +522 -0
- proxilion/guards/output_guard.py +634 -0
- proxilion/observability/__init__.py +198 -0
- proxilion/observability/cost_tracker.py +866 -0
- proxilion/observability/hooks.py +683 -0
- proxilion/observability/metrics.py +798 -0
- proxilion/observability/session_cost_tracker.py +1063 -0
- proxilion/policies/__init__.py +67 -0
- proxilion/policies/base.py +304 -0
- proxilion/policies/builtin.py +486 -0
- proxilion/policies/registry.py +376 -0
- proxilion/providers/__init__.py +201 -0
- proxilion/providers/adapter.py +468 -0
- proxilion/providers/anthropic_adapter.py +330 -0
- proxilion/providers/gemini_adapter.py +391 -0
- proxilion/providers/openai_adapter.py +294 -0
- proxilion/py.typed +0 -0
- proxilion/resilience/__init__.py +81 -0
- proxilion/resilience/degradation.py +615 -0
- proxilion/resilience/fallback.py +555 -0
- proxilion/resilience/retry.py +554 -0
- proxilion/scheduling/__init__.py +57 -0
- proxilion/scheduling/priority_queue.py +419 -0
- proxilion/scheduling/scheduler.py +459 -0
- proxilion/security/__init__.py +244 -0
- proxilion/security/agent_trust.py +968 -0
- proxilion/security/behavioral_drift.py +794 -0
- proxilion/security/cascade_protection.py +869 -0
- proxilion/security/circuit_breaker.py +428 -0
- proxilion/security/cost_limiter.py +690 -0
- proxilion/security/idor_protection.py +460 -0
- proxilion/security/intent_capsule.py +849 -0
- proxilion/security/intent_validator.py +495 -0
- proxilion/security/memory_integrity.py +767 -0
- proxilion/security/rate_limiter.py +509 -0
- proxilion/security/scope_enforcer.py +680 -0
- proxilion/security/sequence_validator.py +636 -0
- proxilion/security/trust_boundaries.py +784 -0
- proxilion/streaming/__init__.py +70 -0
- proxilion/streaming/detector.py +761 -0
- proxilion/streaming/transformer.py +674 -0
- proxilion/timeouts/__init__.py +55 -0
- proxilion/timeouts/decorators.py +477 -0
- proxilion/timeouts/manager.py +545 -0
- proxilion/tools/__init__.py +69 -0
- proxilion/tools/decorators.py +493 -0
- proxilion/tools/registry.py +732 -0
- proxilion/types.py +339 -0
- proxilion/validation/__init__.py +93 -0
- proxilion/validation/pydantic_schema.py +351 -0
- proxilion/validation/schema.py +651 -0
- proxilion-0.0.1.dist-info/METADATA +872 -0
- proxilion-0.0.1.dist-info/RECORD +94 -0
- proxilion-0.0.1.dist-info/WHEEL +4 -0
- proxilion-0.0.1.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,968 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Agent-to-Agent Trust Enforcement for Proxilion.
|
|
3
|
+
|
|
4
|
+
Addresses OWASP ASI07: Insecure Inter-Agent Communication.
|
|
5
|
+
|
|
6
|
+
This module provides cryptographic trust verification for multi-agent
|
|
7
|
+
systems, ensuring that:
|
|
8
|
+
- Agents can only communicate with authorized peers
|
|
9
|
+
- Messages are signed and verified
|
|
10
|
+
- Delegation chains are tracked and validated
|
|
11
|
+
- Trust levels control what actions agents can request
|
|
12
|
+
|
|
13
|
+
Example:
|
|
14
|
+
>>> from proxilion.security.agent_trust import (
|
|
15
|
+
... AgentTrustManager,
|
|
16
|
+
... AgentCredential,
|
|
17
|
+
... DelegationChain,
|
|
18
|
+
... TrustLevel,
|
|
19
|
+
... )
|
|
20
|
+
>>>
|
|
21
|
+
>>> # Create trust manager
|
|
22
|
+
>>> manager = AgentTrustManager(secret_key="your-secret")
|
|
23
|
+
>>>
|
|
24
|
+
>>> # Register agents
|
|
25
|
+
>>> orchestrator = manager.register_agent(
|
|
26
|
+
... agent_id="orchestrator",
|
|
27
|
+
... trust_level=TrustLevel.FULL,
|
|
28
|
+
... capabilities=["delegate", "execute_all"],
|
|
29
|
+
... )
|
|
30
|
+
>>>
|
|
31
|
+
>>> worker = manager.register_agent(
|
|
32
|
+
... agent_id="worker_1",
|
|
33
|
+
... trust_level=TrustLevel.LIMITED,
|
|
34
|
+
... capabilities=["read", "write"],
|
|
35
|
+
... parent_agent="orchestrator",
|
|
36
|
+
... )
|
|
37
|
+
>>>
|
|
38
|
+
>>> # Create signed message from orchestrator to worker
|
|
39
|
+
>>> message = manager.create_signed_message(
|
|
40
|
+
... from_agent="orchestrator",
|
|
41
|
+
... to_agent="worker_1",
|
|
42
|
+
... action="execute",
|
|
43
|
+
... payload={"task": "process_data"},
|
|
44
|
+
... )
|
|
45
|
+
>>>
|
|
46
|
+
>>> # Verify and process on receiving end
|
|
47
|
+
>>> verified = manager.verify_message(message)
|
|
48
|
+
>>> if verified.valid:
|
|
49
|
+
... process_task(message.payload)
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
from __future__ import annotations
|
|
53
|
+
|
|
54
|
+
import hashlib
|
|
55
|
+
import hmac
|
|
56
|
+
import json
|
|
57
|
+
import logging
|
|
58
|
+
import secrets
|
|
59
|
+
import threading
|
|
60
|
+
import time
|
|
61
|
+
import uuid
|
|
62
|
+
from dataclasses import dataclass, field
|
|
63
|
+
from datetime import datetime, timedelta, timezone
|
|
64
|
+
from enum import IntEnum
|
|
65
|
+
from typing import Any
|
|
66
|
+
|
|
67
|
+
from proxilion.exceptions import AgentTrustError
|
|
68
|
+
|
|
69
|
+
logger = logging.getLogger(__name__)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class TrustLevel(IntEnum):
|
|
73
|
+
"""
|
|
74
|
+
Trust levels for agents.
|
|
75
|
+
|
|
76
|
+
Higher levels can delegate to lower levels but not vice versa.
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
UNTRUSTED = 0
|
|
80
|
+
"""No trust - cannot communicate."""
|
|
81
|
+
|
|
82
|
+
MINIMAL = 1
|
|
83
|
+
"""Can receive read-only requests."""
|
|
84
|
+
|
|
85
|
+
LIMITED = 2
|
|
86
|
+
"""Can receive read/write requests within scope."""
|
|
87
|
+
|
|
88
|
+
STANDARD = 3
|
|
89
|
+
"""Normal agent trust level."""
|
|
90
|
+
|
|
91
|
+
ELEVATED = 4
|
|
92
|
+
"""Can delegate to standard agents."""
|
|
93
|
+
|
|
94
|
+
FULL = 5
|
|
95
|
+
"""Full trust - can delegate to all levels."""
|
|
96
|
+
|
|
97
|
+
SYSTEM = 6
|
|
98
|
+
"""System-level trust (internal use)."""
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
@dataclass
|
|
102
|
+
class AgentCredential:
|
|
103
|
+
"""
|
|
104
|
+
Cryptographic credential for an agent.
|
|
105
|
+
|
|
106
|
+
Contains the agent's identity, trust level, capabilities,
|
|
107
|
+
and cryptographic material for signing messages.
|
|
108
|
+
"""
|
|
109
|
+
|
|
110
|
+
agent_id: str
|
|
111
|
+
trust_level: TrustLevel
|
|
112
|
+
capabilities: set[str]
|
|
113
|
+
public_key: str # Hex-encoded public identifier
|
|
114
|
+
parent_agent: str | None = None
|
|
115
|
+
created_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
|
|
116
|
+
expires_at: datetime | None = None
|
|
117
|
+
metadata: dict[str, Any] = field(default_factory=dict)
|
|
118
|
+
|
|
119
|
+
# Internal secret (never serialized)
|
|
120
|
+
_secret: str = field(default="", repr=False)
|
|
121
|
+
|
|
122
|
+
def is_expired(self) -> bool:
|
|
123
|
+
"""Check if credential has expired."""
|
|
124
|
+
if self.expires_at is None:
|
|
125
|
+
return False
|
|
126
|
+
return datetime.now(timezone.utc) > self.expires_at
|
|
127
|
+
|
|
128
|
+
def has_capability(self, capability: str) -> bool:
|
|
129
|
+
"""Check if agent has a specific capability."""
|
|
130
|
+
# Wildcard capability
|
|
131
|
+
if "*" in self.capabilities:
|
|
132
|
+
return True
|
|
133
|
+
# Exact match
|
|
134
|
+
if capability in self.capabilities:
|
|
135
|
+
return True
|
|
136
|
+
# Prefix match (e.g., "read" matches "read:documents")
|
|
137
|
+
for cap in self.capabilities:
|
|
138
|
+
if capability.startswith(cap + ":"):
|
|
139
|
+
return True
|
|
140
|
+
return False
|
|
141
|
+
|
|
142
|
+
def can_delegate_to(self, other: AgentCredential) -> bool:
|
|
143
|
+
"""Check if this agent can delegate to another."""
|
|
144
|
+
# Must have higher trust level
|
|
145
|
+
if self.trust_level <= other.trust_level:
|
|
146
|
+
return False
|
|
147
|
+
# Must have delegate capability
|
|
148
|
+
if not self.has_capability("delegate"):
|
|
149
|
+
return False
|
|
150
|
+
return True
|
|
151
|
+
|
|
152
|
+
def to_dict(self) -> dict[str, Any]:
|
|
153
|
+
"""Serialize to dict (excludes secret)."""
|
|
154
|
+
return {
|
|
155
|
+
"agent_id": self.agent_id,
|
|
156
|
+
"trust_level": self.trust_level.value,
|
|
157
|
+
"capabilities": list(self.capabilities),
|
|
158
|
+
"public_key": self.public_key,
|
|
159
|
+
"parent_agent": self.parent_agent,
|
|
160
|
+
"created_at": self.created_at.isoformat(),
|
|
161
|
+
"expires_at": self.expires_at.isoformat() if self.expires_at else None,
|
|
162
|
+
"metadata": self.metadata,
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
@dataclass
|
|
167
|
+
class DelegationToken:
|
|
168
|
+
"""
|
|
169
|
+
A token that grants delegated authority from one agent to another.
|
|
170
|
+
|
|
171
|
+
Delegation tokens are time-limited and scope-limited.
|
|
172
|
+
"""
|
|
173
|
+
|
|
174
|
+
token_id: str
|
|
175
|
+
issuer_agent: str
|
|
176
|
+
delegate_agent: str
|
|
177
|
+
granted_capabilities: set[str]
|
|
178
|
+
issued_at: datetime
|
|
179
|
+
expires_at: datetime
|
|
180
|
+
signature: str
|
|
181
|
+
chain_depth: int = 1 # How many delegations deep
|
|
182
|
+
max_chain_depth: int = 3 # Maximum delegation chain length
|
|
183
|
+
constraints: dict[str, Any] = field(default_factory=dict)
|
|
184
|
+
|
|
185
|
+
def is_expired(self) -> bool:
|
|
186
|
+
"""Check if token has expired."""
|
|
187
|
+
return datetime.now(timezone.utc) > self.expires_at
|
|
188
|
+
|
|
189
|
+
def is_valid(self) -> bool:
|
|
190
|
+
"""Basic validity check (not signature verification)."""
|
|
191
|
+
if self.is_expired():
|
|
192
|
+
return False
|
|
193
|
+
if self.chain_depth > self.max_chain_depth:
|
|
194
|
+
return False
|
|
195
|
+
return True
|
|
196
|
+
|
|
197
|
+
def to_dict(self) -> dict[str, Any]:
|
|
198
|
+
"""Serialize to dict."""
|
|
199
|
+
return {
|
|
200
|
+
"token_id": self.token_id,
|
|
201
|
+
"issuer_agent": self.issuer_agent,
|
|
202
|
+
"delegate_agent": self.delegate_agent,
|
|
203
|
+
"granted_capabilities": list(self.granted_capabilities),
|
|
204
|
+
"issued_at": self.issued_at.isoformat(),
|
|
205
|
+
"expires_at": self.expires_at.isoformat(),
|
|
206
|
+
"signature": self.signature,
|
|
207
|
+
"chain_depth": self.chain_depth,
|
|
208
|
+
"max_chain_depth": self.max_chain_depth,
|
|
209
|
+
"constraints": self.constraints,
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
@dataclass
|
|
214
|
+
class SignedMessage:
|
|
215
|
+
"""
|
|
216
|
+
A cryptographically signed message between agents.
|
|
217
|
+
"""
|
|
218
|
+
|
|
219
|
+
message_id: str
|
|
220
|
+
from_agent: str
|
|
221
|
+
to_agent: str
|
|
222
|
+
action: str
|
|
223
|
+
payload: dict[str, Any]
|
|
224
|
+
timestamp: float
|
|
225
|
+
signature: str
|
|
226
|
+
delegation_token: DelegationToken | None = None
|
|
227
|
+
reply_to: str | None = None # ID of message being replied to
|
|
228
|
+
metadata: dict[str, Any] = field(default_factory=dict)
|
|
229
|
+
|
|
230
|
+
def to_dict(self) -> dict[str, Any]:
|
|
231
|
+
"""Serialize to dict."""
|
|
232
|
+
return {
|
|
233
|
+
"message_id": self.message_id,
|
|
234
|
+
"from_agent": self.from_agent,
|
|
235
|
+
"to_agent": self.to_agent,
|
|
236
|
+
"action": self.action,
|
|
237
|
+
"payload": self.payload,
|
|
238
|
+
"timestamp": self.timestamp,
|
|
239
|
+
"signature": self.signature,
|
|
240
|
+
"delegation_token": self.delegation_token.to_dict() if self.delegation_token else None,
|
|
241
|
+
"reply_to": self.reply_to,
|
|
242
|
+
"metadata": self.metadata,
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
@dataclass
|
|
247
|
+
class VerificationResult:
|
|
248
|
+
"""Result of message or token verification."""
|
|
249
|
+
|
|
250
|
+
valid: bool
|
|
251
|
+
error: str | None = None
|
|
252
|
+
warnings: list[str] = field(default_factory=list)
|
|
253
|
+
verified_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
|
|
254
|
+
|
|
255
|
+
def to_dict(self) -> dict[str, Any]:
|
|
256
|
+
"""Serialize to dict."""
|
|
257
|
+
return {
|
|
258
|
+
"valid": self.valid,
|
|
259
|
+
"error": self.error,
|
|
260
|
+
"warnings": self.warnings,
|
|
261
|
+
"verified_at": self.verified_at.isoformat(),
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
class DelegationChain:
|
|
266
|
+
"""
|
|
267
|
+
Tracks the chain of delegations for an action.
|
|
268
|
+
|
|
269
|
+
Ensures that delegation chains don't exceed maximum depth
|
|
270
|
+
and that all delegations in the chain are valid.
|
|
271
|
+
"""
|
|
272
|
+
|
|
273
|
+
def __init__(self, max_depth: int = 3) -> None:
|
|
274
|
+
"""
|
|
275
|
+
Initialize delegation chain.
|
|
276
|
+
|
|
277
|
+
Args:
|
|
278
|
+
max_depth: Maximum allowed delegation depth.
|
|
279
|
+
"""
|
|
280
|
+
self.max_depth = max_depth
|
|
281
|
+
self._chain: list[DelegationToken] = []
|
|
282
|
+
|
|
283
|
+
def add(self, token: DelegationToken) -> bool:
|
|
284
|
+
"""
|
|
285
|
+
Add a delegation to the chain.
|
|
286
|
+
|
|
287
|
+
Returns:
|
|
288
|
+
True if added successfully, False if chain would be too deep.
|
|
289
|
+
"""
|
|
290
|
+
if len(self._chain) >= self.max_depth:
|
|
291
|
+
return False
|
|
292
|
+
self._chain.append(token)
|
|
293
|
+
return True
|
|
294
|
+
|
|
295
|
+
def validate(self) -> tuple[bool, str | None]:
|
|
296
|
+
"""
|
|
297
|
+
Validate the entire chain.
|
|
298
|
+
|
|
299
|
+
Returns:
|
|
300
|
+
Tuple of (valid, error message or None).
|
|
301
|
+
"""
|
|
302
|
+
if not self._chain:
|
|
303
|
+
return True, None
|
|
304
|
+
|
|
305
|
+
# Check depth
|
|
306
|
+
if len(self._chain) > self.max_depth:
|
|
307
|
+
return False, f"Chain depth {len(self._chain)} exceeds max {self.max_depth}"
|
|
308
|
+
|
|
309
|
+
# Check each token
|
|
310
|
+
for i, token in enumerate(self._chain):
|
|
311
|
+
if token.is_expired():
|
|
312
|
+
return False, f"Token at position {i} has expired"
|
|
313
|
+
if not token.is_valid():
|
|
314
|
+
return False, f"Token at position {i} is invalid"
|
|
315
|
+
|
|
316
|
+
# Check chain continuity
|
|
317
|
+
if i > 0:
|
|
318
|
+
prev_token = self._chain[i - 1]
|
|
319
|
+
if token.issuer_agent != prev_token.delegate_agent:
|
|
320
|
+
return False, f"Chain break at position {i}: {prev_token.delegate_agent} != {token.issuer_agent}"
|
|
321
|
+
|
|
322
|
+
return True, None
|
|
323
|
+
|
|
324
|
+
@property
|
|
325
|
+
def depth(self) -> int:
|
|
326
|
+
"""Current chain depth."""
|
|
327
|
+
return len(self._chain)
|
|
328
|
+
|
|
329
|
+
@property
|
|
330
|
+
def tokens(self) -> list[DelegationToken]:
|
|
331
|
+
"""Get all tokens in chain."""
|
|
332
|
+
return list(self._chain)
|
|
333
|
+
|
|
334
|
+
def get_effective_capabilities(self) -> set[str]:
|
|
335
|
+
"""Get capabilities available at end of chain (intersection of all)."""
|
|
336
|
+
if not self._chain:
|
|
337
|
+
return set()
|
|
338
|
+
|
|
339
|
+
caps = self._chain[0].granted_capabilities.copy()
|
|
340
|
+
for token in self._chain[1:]:
|
|
341
|
+
caps &= token.granted_capabilities
|
|
342
|
+
|
|
343
|
+
return caps
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
class AgentTrustManager:
|
|
347
|
+
"""
|
|
348
|
+
Manages trust relationships between agents.
|
|
349
|
+
|
|
350
|
+
Provides:
|
|
351
|
+
- Agent registration with cryptographic credentials
|
|
352
|
+
- Message signing and verification
|
|
353
|
+
- Delegation token creation and validation
|
|
354
|
+
- Trust level enforcement
|
|
355
|
+
|
|
356
|
+
Example:
|
|
357
|
+
>>> manager = AgentTrustManager(secret_key="master-secret")
|
|
358
|
+
>>>
|
|
359
|
+
>>> # Register agents
|
|
360
|
+
>>> manager.register_agent("orchestrator", TrustLevel.FULL, {"*"})
|
|
361
|
+
>>> manager.register_agent("worker", TrustLevel.LIMITED, {"read", "write"})
|
|
362
|
+
>>>
|
|
363
|
+
>>> # Create delegation
|
|
364
|
+
>>> token = manager.create_delegation(
|
|
365
|
+
... from_agent="orchestrator",
|
|
366
|
+
... to_agent="worker",
|
|
367
|
+
... capabilities={"read"},
|
|
368
|
+
... ttl_seconds=3600,
|
|
369
|
+
... )
|
|
370
|
+
>>>
|
|
371
|
+
>>> # Send signed message
|
|
372
|
+
>>> message = manager.create_signed_message(
|
|
373
|
+
... from_agent="orchestrator",
|
|
374
|
+
... to_agent="worker",
|
|
375
|
+
... action="read_file",
|
|
376
|
+
... payload={"path": "/data/file.txt"},
|
|
377
|
+
... )
|
|
378
|
+
>>>
|
|
379
|
+
>>> # Verify on receiving end
|
|
380
|
+
>>> result = manager.verify_message(message)
|
|
381
|
+
"""
|
|
382
|
+
|
|
383
|
+
def __init__(
|
|
384
|
+
self,
|
|
385
|
+
secret_key: str | bytes,
|
|
386
|
+
default_token_ttl: int = 3600,
|
|
387
|
+
max_delegation_depth: int = 3,
|
|
388
|
+
require_explicit_trust: bool = True,
|
|
389
|
+
) -> None:
|
|
390
|
+
"""
|
|
391
|
+
Initialize the trust manager.
|
|
392
|
+
|
|
393
|
+
Args:
|
|
394
|
+
secret_key: Master secret for deriving agent keys.
|
|
395
|
+
default_token_ttl: Default TTL for delegation tokens (seconds).
|
|
396
|
+
max_delegation_depth: Maximum delegation chain depth.
|
|
397
|
+
require_explicit_trust: If True, agents must be explicitly registered.
|
|
398
|
+
"""
|
|
399
|
+
if isinstance(secret_key, str):
|
|
400
|
+
secret_key = secret_key.encode()
|
|
401
|
+
|
|
402
|
+
self._master_secret = secret_key
|
|
403
|
+
self._default_token_ttl = default_token_ttl
|
|
404
|
+
self._max_delegation_depth = max_delegation_depth
|
|
405
|
+
self._require_explicit_trust = require_explicit_trust
|
|
406
|
+
|
|
407
|
+
self._agents: dict[str, AgentCredential] = {}
|
|
408
|
+
self._delegation_tokens: dict[str, DelegationToken] = {}
|
|
409
|
+
self._revoked_tokens: set[str] = set()
|
|
410
|
+
self._message_nonces: set[str] = set() # Replay protection
|
|
411
|
+
self._lock = threading.RLock()
|
|
412
|
+
|
|
413
|
+
logger.debug("AgentTrustManager initialized")
|
|
414
|
+
|
|
415
|
+
def _derive_agent_secret(self, agent_id: str) -> str:
|
|
416
|
+
"""Derive a unique secret for an agent."""
|
|
417
|
+
return hmac.new(
|
|
418
|
+
self._master_secret,
|
|
419
|
+
f"agent:{agent_id}".encode(),
|
|
420
|
+
hashlib.sha256,
|
|
421
|
+
).hexdigest()
|
|
422
|
+
|
|
423
|
+
def _derive_public_key(self, secret: str) -> str:
|
|
424
|
+
"""Derive public key from secret."""
|
|
425
|
+
return hashlib.sha256(secret.encode()).hexdigest()[:32]
|
|
426
|
+
|
|
427
|
+
def register_agent(
|
|
428
|
+
self,
|
|
429
|
+
agent_id: str,
|
|
430
|
+
trust_level: TrustLevel,
|
|
431
|
+
capabilities: set[str] | list[str],
|
|
432
|
+
parent_agent: str | None = None,
|
|
433
|
+
ttl_seconds: int | None = None,
|
|
434
|
+
metadata: dict[str, Any] | None = None,
|
|
435
|
+
) -> AgentCredential:
|
|
436
|
+
"""
|
|
437
|
+
Register an agent with the trust manager.
|
|
438
|
+
|
|
439
|
+
Args:
|
|
440
|
+
agent_id: Unique identifier for the agent.
|
|
441
|
+
trust_level: Trust level for the agent.
|
|
442
|
+
capabilities: Set of capabilities the agent has.
|
|
443
|
+
parent_agent: Optional parent agent (for hierarchical trust).
|
|
444
|
+
ttl_seconds: Optional credential TTL.
|
|
445
|
+
metadata: Optional metadata.
|
|
446
|
+
|
|
447
|
+
Returns:
|
|
448
|
+
AgentCredential for the registered agent.
|
|
449
|
+
|
|
450
|
+
Raises:
|
|
451
|
+
AgentTrustError: If agent already registered or invalid parent.
|
|
452
|
+
"""
|
|
453
|
+
with self._lock:
|
|
454
|
+
if agent_id in self._agents:
|
|
455
|
+
raise AgentTrustError(
|
|
456
|
+
source_agent=agent_id,
|
|
457
|
+
target_agent="",
|
|
458
|
+
reason=f"Agent '{agent_id}' is already registered",
|
|
459
|
+
)
|
|
460
|
+
|
|
461
|
+
# Validate parent
|
|
462
|
+
if parent_agent and parent_agent not in self._agents:
|
|
463
|
+
raise AgentTrustError(
|
|
464
|
+
source_agent=agent_id,
|
|
465
|
+
target_agent=parent_agent,
|
|
466
|
+
reason=f"Parent agent '{parent_agent}' not found",
|
|
467
|
+
)
|
|
468
|
+
|
|
469
|
+
# Check parent has higher trust
|
|
470
|
+
if parent_agent:
|
|
471
|
+
parent = self._agents[parent_agent]
|
|
472
|
+
if parent.trust_level <= trust_level:
|
|
473
|
+
raise AgentTrustError(
|
|
474
|
+
source_agent=agent_id,
|
|
475
|
+
target_agent=parent_agent,
|
|
476
|
+
reason="Child agent cannot have equal or higher trust than parent",
|
|
477
|
+
)
|
|
478
|
+
|
|
479
|
+
# Derive credentials
|
|
480
|
+
secret = self._derive_agent_secret(agent_id)
|
|
481
|
+
public_key = self._derive_public_key(secret)
|
|
482
|
+
|
|
483
|
+
expires_at = None
|
|
484
|
+
if ttl_seconds:
|
|
485
|
+
expires_at = datetime.now(timezone.utc) + timedelta(seconds=ttl_seconds)
|
|
486
|
+
|
|
487
|
+
if isinstance(capabilities, list):
|
|
488
|
+
capabilities = set(capabilities)
|
|
489
|
+
|
|
490
|
+
credential = AgentCredential(
|
|
491
|
+
agent_id=agent_id,
|
|
492
|
+
trust_level=trust_level,
|
|
493
|
+
capabilities=capabilities,
|
|
494
|
+
public_key=public_key,
|
|
495
|
+
parent_agent=parent_agent,
|
|
496
|
+
expires_at=expires_at,
|
|
497
|
+
metadata=metadata or {},
|
|
498
|
+
_secret=secret,
|
|
499
|
+
)
|
|
500
|
+
|
|
501
|
+
self._agents[agent_id] = credential
|
|
502
|
+
logger.info(f"Registered agent: {agent_id} (trust={trust_level.name})")
|
|
503
|
+
|
|
504
|
+
return credential
|
|
505
|
+
|
|
506
|
+
def unregister_agent(self, agent_id: str) -> bool:
|
|
507
|
+
"""
|
|
508
|
+
Unregister an agent.
|
|
509
|
+
|
|
510
|
+
Args:
|
|
511
|
+
agent_id: Agent to unregister.
|
|
512
|
+
|
|
513
|
+
Returns:
|
|
514
|
+
True if agent was unregistered.
|
|
515
|
+
"""
|
|
516
|
+
with self._lock:
|
|
517
|
+
if agent_id in self._agents:
|
|
518
|
+
del self._agents[agent_id]
|
|
519
|
+
|
|
520
|
+
# Revoke all tokens issued by or to this agent
|
|
521
|
+
tokens_to_revoke = [
|
|
522
|
+
tid for tid, token in self._delegation_tokens.items()
|
|
523
|
+
if token.issuer_agent == agent_id or token.delegate_agent == agent_id
|
|
524
|
+
]
|
|
525
|
+
for tid in tokens_to_revoke:
|
|
526
|
+
self._revoked_tokens.add(tid)
|
|
527
|
+
del self._delegation_tokens[tid]
|
|
528
|
+
|
|
529
|
+
logger.info(f"Unregistered agent: {agent_id}")
|
|
530
|
+
return True
|
|
531
|
+
return False
|
|
532
|
+
|
|
533
|
+
def get_agent(self, agent_id: str) -> AgentCredential | None:
|
|
534
|
+
"""Get an agent's credential."""
|
|
535
|
+
with self._lock:
|
|
536
|
+
return self._agents.get(agent_id)
|
|
537
|
+
|
|
538
|
+
def create_delegation(
|
|
539
|
+
self,
|
|
540
|
+
from_agent: str,
|
|
541
|
+
to_agent: str,
|
|
542
|
+
capabilities: set[str] | list[str],
|
|
543
|
+
ttl_seconds: int | None = None,
|
|
544
|
+
constraints: dict[str, Any] | None = None,
|
|
545
|
+
) -> DelegationToken:
|
|
546
|
+
"""
|
|
547
|
+
Create a delegation token from one agent to another.
|
|
548
|
+
|
|
549
|
+
Args:
|
|
550
|
+
from_agent: Agent issuing the delegation.
|
|
551
|
+
to_agent: Agent receiving the delegation.
|
|
552
|
+
capabilities: Capabilities being delegated.
|
|
553
|
+
ttl_seconds: Token lifetime (default: manager default).
|
|
554
|
+
constraints: Optional constraints on the delegation.
|
|
555
|
+
|
|
556
|
+
Returns:
|
|
557
|
+
DelegationToken that can be used for delegated actions.
|
|
558
|
+
|
|
559
|
+
Raises:
|
|
560
|
+
AgentTrustError: If delegation is not allowed.
|
|
561
|
+
"""
|
|
562
|
+
with self._lock:
|
|
563
|
+
# Validate agents
|
|
564
|
+
issuer = self._agents.get(from_agent)
|
|
565
|
+
delegate = self._agents.get(to_agent)
|
|
566
|
+
|
|
567
|
+
if not issuer:
|
|
568
|
+
raise AgentTrustError(
|
|
569
|
+
source_agent=from_agent,
|
|
570
|
+
target_agent=to_agent,
|
|
571
|
+
reason=f"Issuer agent '{from_agent}' not found",
|
|
572
|
+
)
|
|
573
|
+
|
|
574
|
+
if not delegate:
|
|
575
|
+
raise AgentTrustError(
|
|
576
|
+
source_agent=from_agent,
|
|
577
|
+
target_agent=to_agent,
|
|
578
|
+
reason=f"Delegate agent '{to_agent}' not found",
|
|
579
|
+
)
|
|
580
|
+
|
|
581
|
+
if issuer.is_expired():
|
|
582
|
+
raise AgentTrustError(
|
|
583
|
+
source_agent=from_agent,
|
|
584
|
+
target_agent=to_agent,
|
|
585
|
+
reason="Issuer credential has expired",
|
|
586
|
+
)
|
|
587
|
+
|
|
588
|
+
if not issuer.can_delegate_to(delegate):
|
|
589
|
+
raise AgentTrustError(
|
|
590
|
+
source_agent=from_agent,
|
|
591
|
+
target_agent=to_agent,
|
|
592
|
+
reason=f"Agent '{from_agent}' cannot delegate to '{to_agent}'",
|
|
593
|
+
)
|
|
594
|
+
|
|
595
|
+
# Normalize capabilities
|
|
596
|
+
if isinstance(capabilities, list):
|
|
597
|
+
capabilities = set(capabilities)
|
|
598
|
+
|
|
599
|
+
# Can only delegate capabilities the issuer has
|
|
600
|
+
invalid_caps = capabilities - issuer.capabilities
|
|
601
|
+
if invalid_caps and "*" not in issuer.capabilities:
|
|
602
|
+
raise AgentTrustError(
|
|
603
|
+
source_agent=from_agent,
|
|
604
|
+
target_agent=to_agent,
|
|
605
|
+
reason=f"Cannot delegate capabilities not owned: {invalid_caps}",
|
|
606
|
+
)
|
|
607
|
+
|
|
608
|
+
# Create token
|
|
609
|
+
ttl = ttl_seconds or self._default_token_ttl
|
|
610
|
+
now = datetime.now(timezone.utc)
|
|
611
|
+
token_id = str(uuid.uuid4())
|
|
612
|
+
|
|
613
|
+
# Sign the token
|
|
614
|
+
token_data = f"{token_id}|{from_agent}|{to_agent}|{sorted(capabilities)}|{now.isoformat()}"
|
|
615
|
+
signature = hmac.new(
|
|
616
|
+
issuer._secret.encode(),
|
|
617
|
+
token_data.encode(),
|
|
618
|
+
hashlib.sha256,
|
|
619
|
+
).hexdigest()
|
|
620
|
+
|
|
621
|
+
token = DelegationToken(
|
|
622
|
+
token_id=token_id,
|
|
623
|
+
issuer_agent=from_agent,
|
|
624
|
+
delegate_agent=to_agent,
|
|
625
|
+
granted_capabilities=capabilities,
|
|
626
|
+
issued_at=now,
|
|
627
|
+
expires_at=now + timedelta(seconds=ttl),
|
|
628
|
+
signature=signature,
|
|
629
|
+
chain_depth=1,
|
|
630
|
+
max_chain_depth=self._max_delegation_depth,
|
|
631
|
+
constraints=constraints or {},
|
|
632
|
+
)
|
|
633
|
+
|
|
634
|
+
self._delegation_tokens[token_id] = token
|
|
635
|
+
logger.debug(f"Created delegation: {from_agent} -> {to_agent} ({capabilities})")
|
|
636
|
+
|
|
637
|
+
return token
|
|
638
|
+
|
|
639
|
+
def revoke_delegation(self, token_id: str) -> bool:
|
|
640
|
+
"""
|
|
641
|
+
Revoke a delegation token.
|
|
642
|
+
|
|
643
|
+
Args:
|
|
644
|
+
token_id: Token to revoke.
|
|
645
|
+
|
|
646
|
+
Returns:
|
|
647
|
+
True if token was revoked.
|
|
648
|
+
"""
|
|
649
|
+
with self._lock:
|
|
650
|
+
if token_id in self._delegation_tokens:
|
|
651
|
+
self._revoked_tokens.add(token_id)
|
|
652
|
+
del self._delegation_tokens[token_id]
|
|
653
|
+
logger.info(f"Revoked delegation token: {token_id}")
|
|
654
|
+
return True
|
|
655
|
+
return False
|
|
656
|
+
|
|
657
|
+
def create_signed_message(
|
|
658
|
+
self,
|
|
659
|
+
from_agent: str,
|
|
660
|
+
to_agent: str,
|
|
661
|
+
action: str,
|
|
662
|
+
payload: dict[str, Any],
|
|
663
|
+
delegation_token: DelegationToken | None = None,
|
|
664
|
+
reply_to: str | None = None,
|
|
665
|
+
metadata: dict[str, Any] | None = None,
|
|
666
|
+
) -> SignedMessage:
|
|
667
|
+
"""
|
|
668
|
+
Create a signed message from one agent to another.
|
|
669
|
+
|
|
670
|
+
Args:
|
|
671
|
+
from_agent: Sending agent.
|
|
672
|
+
to_agent: Receiving agent.
|
|
673
|
+
action: Action being requested.
|
|
674
|
+
payload: Message payload.
|
|
675
|
+
delegation_token: Optional delegation token for delegated actions.
|
|
676
|
+
reply_to: Message ID being replied to.
|
|
677
|
+
metadata: Optional metadata.
|
|
678
|
+
|
|
679
|
+
Returns:
|
|
680
|
+
SignedMessage that can be verified by the receiver.
|
|
681
|
+
|
|
682
|
+
Raises:
|
|
683
|
+
AgentTrustError: If agent not found or expired.
|
|
684
|
+
"""
|
|
685
|
+
with self._lock:
|
|
686
|
+
sender = self._agents.get(from_agent)
|
|
687
|
+
if not sender:
|
|
688
|
+
raise AgentTrustError(
|
|
689
|
+
source_agent=from_agent,
|
|
690
|
+
target_agent=to_agent,
|
|
691
|
+
reason=f"Sender agent '{from_agent}' not found",
|
|
692
|
+
)
|
|
693
|
+
|
|
694
|
+
if sender.is_expired():
|
|
695
|
+
raise AgentTrustError(
|
|
696
|
+
source_agent=from_agent,
|
|
697
|
+
target_agent=to_agent,
|
|
698
|
+
reason="Sender credential has expired",
|
|
699
|
+
)
|
|
700
|
+
|
|
701
|
+
# Create message
|
|
702
|
+
message_id = str(uuid.uuid4())
|
|
703
|
+
timestamp = time.time()
|
|
704
|
+
|
|
705
|
+
# Build signature data
|
|
706
|
+
sig_data = (
|
|
707
|
+
f"{message_id}|{from_agent}|{to_agent}|{action}|"
|
|
708
|
+
f"{json.dumps(payload, sort_keys=True)}|{timestamp}"
|
|
709
|
+
)
|
|
710
|
+
signature = hmac.new(
|
|
711
|
+
sender._secret.encode(),
|
|
712
|
+
sig_data.encode(),
|
|
713
|
+
hashlib.sha256,
|
|
714
|
+
).hexdigest()
|
|
715
|
+
|
|
716
|
+
return SignedMessage(
|
|
717
|
+
message_id=message_id,
|
|
718
|
+
from_agent=from_agent,
|
|
719
|
+
to_agent=to_agent,
|
|
720
|
+
action=action,
|
|
721
|
+
payload=payload,
|
|
722
|
+
timestamp=timestamp,
|
|
723
|
+
signature=signature,
|
|
724
|
+
delegation_token=delegation_token,
|
|
725
|
+
reply_to=reply_to,
|
|
726
|
+
metadata=metadata or {},
|
|
727
|
+
)
|
|
728
|
+
|
|
729
|
+
def verify_message(
|
|
730
|
+
self,
|
|
731
|
+
message: SignedMessage,
|
|
732
|
+
check_replay: bool = True,
|
|
733
|
+
max_age_seconds: float = 300.0,
|
|
734
|
+
) -> VerificationResult:
|
|
735
|
+
"""
|
|
736
|
+
Verify a signed message.
|
|
737
|
+
|
|
738
|
+
Args:
|
|
739
|
+
message: Message to verify.
|
|
740
|
+
check_replay: Check for replay attacks.
|
|
741
|
+
max_age_seconds: Maximum message age.
|
|
742
|
+
|
|
743
|
+
Returns:
|
|
744
|
+
VerificationResult indicating if message is valid.
|
|
745
|
+
"""
|
|
746
|
+
warnings: list[str] = []
|
|
747
|
+
|
|
748
|
+
with self._lock:
|
|
749
|
+
# Check replay
|
|
750
|
+
if check_replay:
|
|
751
|
+
if message.message_id in self._message_nonces:
|
|
752
|
+
return VerificationResult(
|
|
753
|
+
valid=False,
|
|
754
|
+
error="Replay attack detected: message already processed",
|
|
755
|
+
)
|
|
756
|
+
|
|
757
|
+
# Check message age
|
|
758
|
+
age = time.time() - message.timestamp
|
|
759
|
+
if age > max_age_seconds:
|
|
760
|
+
return VerificationResult(
|
|
761
|
+
valid=False,
|
|
762
|
+
error=f"Message too old: {age:.1f}s > {max_age_seconds}s",
|
|
763
|
+
)
|
|
764
|
+
if age < -60: # Allow 60s clock skew
|
|
765
|
+
return VerificationResult(
|
|
766
|
+
valid=False,
|
|
767
|
+
error="Message timestamp in the future",
|
|
768
|
+
)
|
|
769
|
+
|
|
770
|
+
# Get sender
|
|
771
|
+
sender = self._agents.get(message.from_agent)
|
|
772
|
+
if not sender:
|
|
773
|
+
if self._require_explicit_trust:
|
|
774
|
+
return VerificationResult(
|
|
775
|
+
valid=False,
|
|
776
|
+
error=f"Unknown sender: {message.from_agent}",
|
|
777
|
+
)
|
|
778
|
+
else:
|
|
779
|
+
warnings.append(f"Sender {message.from_agent} not registered")
|
|
780
|
+
|
|
781
|
+
# Check sender credentials
|
|
782
|
+
if sender:
|
|
783
|
+
if sender.is_expired():
|
|
784
|
+
return VerificationResult(
|
|
785
|
+
valid=False,
|
|
786
|
+
error="Sender credential has expired",
|
|
787
|
+
)
|
|
788
|
+
|
|
789
|
+
# Verify signature
|
|
790
|
+
sig_data = (
|
|
791
|
+
f"{message.message_id}|{message.from_agent}|{message.to_agent}|"
|
|
792
|
+
f"{message.action}|{json.dumps(message.payload, sort_keys=True)}|"
|
|
793
|
+
f"{message.timestamp}"
|
|
794
|
+
)
|
|
795
|
+
expected_sig = hmac.new(
|
|
796
|
+
sender._secret.encode(),
|
|
797
|
+
sig_data.encode(),
|
|
798
|
+
hashlib.sha256,
|
|
799
|
+
).hexdigest()
|
|
800
|
+
|
|
801
|
+
if not hmac.compare_digest(expected_sig, message.signature):
|
|
802
|
+
return VerificationResult(
|
|
803
|
+
valid=False,
|
|
804
|
+
error="Invalid signature",
|
|
805
|
+
)
|
|
806
|
+
|
|
807
|
+
# Check receiver exists
|
|
808
|
+
receiver = self._agents.get(message.to_agent)
|
|
809
|
+
if not receiver and self._require_explicit_trust:
|
|
810
|
+
return VerificationResult(
|
|
811
|
+
valid=False,
|
|
812
|
+
error=f"Unknown receiver: {message.to_agent}",
|
|
813
|
+
)
|
|
814
|
+
|
|
815
|
+
# Check trust levels allow communication
|
|
816
|
+
if sender and receiver:
|
|
817
|
+
if sender.trust_level == TrustLevel.UNTRUSTED:
|
|
818
|
+
return VerificationResult(
|
|
819
|
+
valid=False,
|
|
820
|
+
error="Sender has UNTRUSTED trust level",
|
|
821
|
+
)
|
|
822
|
+
|
|
823
|
+
# Check if action is allowed
|
|
824
|
+
if not sender.has_capability(message.action):
|
|
825
|
+
# Check delegation token
|
|
826
|
+
if message.delegation_token:
|
|
827
|
+
token = message.delegation_token
|
|
828
|
+
if token.token_id in self._revoked_tokens:
|
|
829
|
+
return VerificationResult(
|
|
830
|
+
valid=False,
|
|
831
|
+
error="Delegation token has been revoked",
|
|
832
|
+
)
|
|
833
|
+
if token.is_expired():
|
|
834
|
+
return VerificationResult(
|
|
835
|
+
valid=False,
|
|
836
|
+
error="Delegation token has expired",
|
|
837
|
+
)
|
|
838
|
+
if message.action not in token.granted_capabilities:
|
|
839
|
+
if "*" not in token.granted_capabilities:
|
|
840
|
+
return VerificationResult(
|
|
841
|
+
valid=False,
|
|
842
|
+
error=f"Action '{message.action}' not in delegation",
|
|
843
|
+
)
|
|
844
|
+
else:
|
|
845
|
+
return VerificationResult(
|
|
846
|
+
valid=False,
|
|
847
|
+
error=f"Sender lacks capability for action '{message.action}'",
|
|
848
|
+
)
|
|
849
|
+
|
|
850
|
+
# Record nonce for replay protection
|
|
851
|
+
if check_replay:
|
|
852
|
+
self._message_nonces.add(message.message_id)
|
|
853
|
+
# Cleanup old nonces (keep last 10000)
|
|
854
|
+
if len(self._message_nonces) > 10000:
|
|
855
|
+
# Remove oldest (this is approximate but effective)
|
|
856
|
+
to_remove = list(self._message_nonces)[:5000]
|
|
857
|
+
for nonce in to_remove:
|
|
858
|
+
self._message_nonces.discard(nonce)
|
|
859
|
+
|
|
860
|
+
return VerificationResult(valid=True, warnings=warnings)
|
|
861
|
+
|
|
862
|
+
def verify_delegation_chain(self, chain: DelegationChain) -> VerificationResult:
|
|
863
|
+
"""
|
|
864
|
+
Verify a delegation chain.
|
|
865
|
+
|
|
866
|
+
Args:
|
|
867
|
+
chain: The delegation chain to verify.
|
|
868
|
+
|
|
869
|
+
Returns:
|
|
870
|
+
VerificationResult for the chain.
|
|
871
|
+
"""
|
|
872
|
+
valid, error = chain.validate()
|
|
873
|
+
if not valid:
|
|
874
|
+
return VerificationResult(valid=False, error=error)
|
|
875
|
+
|
|
876
|
+
# Verify each token's signature
|
|
877
|
+
with self._lock:
|
|
878
|
+
for token in chain.tokens:
|
|
879
|
+
if token.token_id in self._revoked_tokens:
|
|
880
|
+
return VerificationResult(
|
|
881
|
+
valid=False,
|
|
882
|
+
error=f"Token {token.token_id} has been revoked",
|
|
883
|
+
)
|
|
884
|
+
|
|
885
|
+
issuer = self._agents.get(token.issuer_agent)
|
|
886
|
+
if not issuer:
|
|
887
|
+
return VerificationResult(
|
|
888
|
+
valid=False,
|
|
889
|
+
error=f"Issuer {token.issuer_agent} not found",
|
|
890
|
+
)
|
|
891
|
+
|
|
892
|
+
# Verify signature
|
|
893
|
+
token_data = (
|
|
894
|
+
f"{token.token_id}|{token.issuer_agent}|{token.delegate_agent}|"
|
|
895
|
+
f"{sorted(token.granted_capabilities)}|{token.issued_at.isoformat()}"
|
|
896
|
+
)
|
|
897
|
+
expected_sig = hmac.new(
|
|
898
|
+
issuer._secret.encode(),
|
|
899
|
+
token_data.encode(),
|
|
900
|
+
hashlib.sha256,
|
|
901
|
+
).hexdigest()
|
|
902
|
+
|
|
903
|
+
if not hmac.compare_digest(expected_sig, token.signature):
|
|
904
|
+
return VerificationResult(
|
|
905
|
+
valid=False,
|
|
906
|
+
error=f"Invalid signature on token {token.token_id}",
|
|
907
|
+
)
|
|
908
|
+
|
|
909
|
+
return VerificationResult(valid=True)
|
|
910
|
+
|
|
911
|
+
def get_registered_agents(self) -> list[str]:
|
|
912
|
+
"""Get list of registered agent IDs."""
|
|
913
|
+
with self._lock:
|
|
914
|
+
return list(self._agents.keys())
|
|
915
|
+
|
|
916
|
+
def get_trust_level(self, agent_id: str) -> TrustLevel | None:
|
|
917
|
+
"""Get an agent's trust level."""
|
|
918
|
+
with self._lock:
|
|
919
|
+
agent = self._agents.get(agent_id)
|
|
920
|
+
return agent.trust_level if agent else None
|
|
921
|
+
|
|
922
|
+
def cleanup_expired(self) -> int:
|
|
923
|
+
"""
|
|
924
|
+
Clean up expired credentials and tokens.
|
|
925
|
+
|
|
926
|
+
Returns:
|
|
927
|
+
Number of items cleaned up.
|
|
928
|
+
"""
|
|
929
|
+
count = 0
|
|
930
|
+
now = datetime.now(timezone.utc)
|
|
931
|
+
|
|
932
|
+
with self._lock:
|
|
933
|
+
# Clean expired agents
|
|
934
|
+
expired_agents = [
|
|
935
|
+
aid for aid, agent in self._agents.items()
|
|
936
|
+
if agent.expires_at and agent.expires_at < now
|
|
937
|
+
]
|
|
938
|
+
for aid in expired_agents:
|
|
939
|
+
del self._agents[aid]
|
|
940
|
+
count += 1
|
|
941
|
+
|
|
942
|
+
# Clean expired tokens
|
|
943
|
+
expired_tokens = [
|
|
944
|
+
tid for tid, token in self._delegation_tokens.items()
|
|
945
|
+
if token.expires_at < now
|
|
946
|
+
]
|
|
947
|
+
for tid in expired_tokens:
|
|
948
|
+
del self._delegation_tokens[tid]
|
|
949
|
+
count += 1
|
|
950
|
+
|
|
951
|
+
if count > 0:
|
|
952
|
+
logger.debug(f"Cleaned up {count} expired items")
|
|
953
|
+
|
|
954
|
+
return count
|
|
955
|
+
|
|
956
|
+
|
|
957
|
+
# Convenience exports
|
|
958
|
+
__all__ = [
|
|
959
|
+
# Core classes
|
|
960
|
+
"AgentTrustManager",
|
|
961
|
+
"AgentCredential",
|
|
962
|
+
"DelegationToken",
|
|
963
|
+
"DelegationChain",
|
|
964
|
+
"SignedMessage",
|
|
965
|
+
"VerificationResult",
|
|
966
|
+
# Enums
|
|
967
|
+
"TrustLevel",
|
|
968
|
+
]
|