agenticdome-python-sdk 1.2.6__tar.gz → 1.2.8__tar.gz
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.
- {agenticdome_python_sdk-1.2.6/agenticdome_python_sdk.egg-info → agenticdome_python_sdk-1.2.8}/PKG-INFO +1 -1
- {agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.8/agenticdome_python_sdk.egg-info}/PKG-INFO +1 -1
- {agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.8}/agenticdome_python_sdk.egg-info/SOURCES.txt +1 -0
- {agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.8}/agenticdome_sdk/_framework_firewall.py +37 -2
- {agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.8}/agenticdome_sdk/client.py +234 -6
- {agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.8}/pyproject.toml +1 -1
- {agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.8}/tests/test_autogen_integration.py +22 -0
- {agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.8}/tests/test_client.py +79 -0
- agenticdome_python_sdk-1.2.8/tests/test_execution_broker.py +75 -0
- {agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.8}/LICENSE +0 -0
- {agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.8}/MANIFEST.in +0 -0
- {agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.8}/README.md +0 -0
- {agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.8}/agenticdome_python_sdk.egg-info/dependency_links.txt +0 -0
- {agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.8}/agenticdome_python_sdk.egg-info/requires.txt +0 -0
- {agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.8}/agenticdome_python_sdk.egg-info/top_level.txt +0 -0
- {agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.8}/agenticdome_sdk/__init__.py +0 -0
- {agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.8}/agenticdome_sdk/agno.py +0 -0
- {agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.8}/agenticdome_sdk/autogen.py +0 -0
- {agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.8}/agenticdome_sdk/aws_bedrock.py +0 -0
- {agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.8}/agenticdome_sdk/claude.py +0 -0
- {agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.8}/agenticdome_sdk/crewai.py +0 -0
- {agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.8}/agenticdome_sdk/google_adk.py +0 -0
- {agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.8}/agenticdome_sdk/identity.py +0 -0
- {agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.8}/agenticdome_sdk/langgraph.py +0 -0
- {agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.8}/agenticdome_sdk/llamaindex.py +0 -0
- {agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.8}/agenticdome_sdk/mcp_host.py +0 -0
- {agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.8}/agenticdome_sdk/microsoft_agent_framework.py +0 -0
- {agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.8}/agenticdome_sdk/microsoft_ai_foundry.py +0 -0
- {agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.8}/agenticdome_sdk/openai_agents.py +0 -0
- {agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.8}/agenticdome_sdk/proof.py +0 -0
- {agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.8}/agenticdome_sdk/pydantic.py +0 -0
- {agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.8}/agenticdome_sdk/smolagents.py +0 -0
- {agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.8}/examples/attack_demo.py +0 -0
- {agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.8}/examples/crewai_agenticdome_firewall.py +0 -0
- {agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.8}/setup.cfg +0 -0
- {agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.8}/setup.py +0 -0
- {agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.8}/tests/test_agno_integration.py +0 -0
- {agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.8}/tests/test_attack_demo.py +0 -0
- {agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.8}/tests/test_aws_bedrock_integration.py +0 -0
- {agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.8}/tests/test_claude_integration.py +0 -0
- {agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.8}/tests/test_crewai_integration.py +0 -0
- {agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.8}/tests/test_google_adk_integration.py +0 -0
- {agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.8}/tests/test_identity_context.py +0 -0
- {agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.8}/tests/test_langgraph_integration.py +0 -0
- {agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.8}/tests/test_live_tenant.py +0 -0
- {agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.8}/tests/test_llamaindex_integration.py +0 -0
- {agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.8}/tests/test_mcp_host_integration.py +0 -0
- {agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.8}/tests/test_microsoft_agent_framework_integration.py +0 -0
- {agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.8}/tests/test_microsoft_ai_foundry_integration.py +0 -0
- {agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.8}/tests/test_openai_agents_integration.py +0 -0
- {agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.8}/tests/test_pydanticai_integration.py +0 -0
- {agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.8}/tests/test_smolagents_integration.py +0 -0
|
@@ -36,6 +36,7 @@ tests/test_aws_bedrock_integration.py
|
|
|
36
36
|
tests/test_claude_integration.py
|
|
37
37
|
tests/test_client.py
|
|
38
38
|
tests/test_crewai_integration.py
|
|
39
|
+
tests/test_execution_broker.py
|
|
39
40
|
tests/test_google_adk_integration.py
|
|
40
41
|
tests/test_identity_context.py
|
|
41
42
|
tests/test_langgraph_integration.py
|
{agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.8}/agenticdome_sdk/_framework_firewall.py
RENAMED
|
@@ -63,6 +63,8 @@ class DecisionTokenRecord:
|
|
|
63
63
|
source_agent_id: str
|
|
64
64
|
created_at: float
|
|
65
65
|
token_hmac: str = ""
|
|
66
|
+
tool_version: str = ""
|
|
67
|
+
tool_digest: str = ""
|
|
66
68
|
|
|
67
69
|
|
|
68
70
|
def _stable_json(value: Any) -> str:
|
|
@@ -213,6 +215,22 @@ class FrameworkFirewallBase:
|
|
|
213
215
|
except Exception:
|
|
214
216
|
pass
|
|
215
217
|
|
|
218
|
+
def register_tool_provenance(
|
|
219
|
+
self,
|
|
220
|
+
tool_name: str,
|
|
221
|
+
*,
|
|
222
|
+
tool_version: Optional[str] = None,
|
|
223
|
+
tool_digest: Optional[str] = None,
|
|
224
|
+
tool_platform: Optional[str] = None,
|
|
225
|
+
) -> None:
|
|
226
|
+
"""Cache precomputed provenance once for this firewall's client."""
|
|
227
|
+
self.client.register_tool_provenance(
|
|
228
|
+
tool_name,
|
|
229
|
+
tool_version=tool_version,
|
|
230
|
+
tool_digest=tool_digest,
|
|
231
|
+
tool_platform=tool_platform or self.config.default_tool_platform,
|
|
232
|
+
)
|
|
233
|
+
|
|
216
234
|
@staticmethod
|
|
217
235
|
def serialize(value: Any) -> str:
|
|
218
236
|
return value if isinstance(value, str) else _stable_json(value)
|
|
@@ -372,6 +390,8 @@ class FrameworkFirewallBase:
|
|
|
372
390
|
tool_args: Dict[str, Any],
|
|
373
391
|
text: str = "",
|
|
374
392
|
tool_platform: Optional[str] = None,
|
|
393
|
+
tool_version: Optional[str] = None,
|
|
394
|
+
tool_digest: Optional[str] = None,
|
|
375
395
|
policy_context: Optional[Dict[str, Any]] = None,
|
|
376
396
|
source_agent_id: Optional[str] = None,
|
|
377
397
|
) -> Dict[str, Any]:
|
|
@@ -392,6 +412,8 @@ class FrameworkFirewallBase:
|
|
|
392
412
|
tool_platform=tool_platform or self.config.default_tool_platform,
|
|
393
413
|
tool_name=tool_name,
|
|
394
414
|
tool_args=clean_args,
|
|
415
|
+
tool_version=tool_version,
|
|
416
|
+
tool_digest=tool_digest,
|
|
395
417
|
policy_context=self._policy_context(session_id, f"{self.config.platform}_tool_call", policy_context, tool_name=tool_name),
|
|
396
418
|
)
|
|
397
419
|
if self.verdict(response) == "BLOCKED":
|
|
@@ -421,6 +443,8 @@ class FrameworkFirewallBase:
|
|
|
421
443
|
tool_args: Dict[str, Any],
|
|
422
444
|
text: str = "",
|
|
423
445
|
tool_platform: Optional[str] = None,
|
|
446
|
+
tool_version: Optional[str] = None,
|
|
447
|
+
tool_digest: Optional[str] = None,
|
|
424
448
|
policy_context: Optional[Dict[str, Any]] = None,
|
|
425
449
|
) -> Dict[str, Any]:
|
|
426
450
|
clean_args = self.strip_private_args(tool_args)
|
|
@@ -435,6 +459,8 @@ class FrameworkFirewallBase:
|
|
|
435
459
|
tool_platform=tool_platform or self.config.default_tool_platform,
|
|
436
460
|
tool_name=tool_name,
|
|
437
461
|
tool_args=clean_args,
|
|
462
|
+
tool_version=tool_version,
|
|
463
|
+
tool_digest=tool_digest,
|
|
438
464
|
session_id=session_id,
|
|
439
465
|
direction="outbound",
|
|
440
466
|
policy_context=self._policy_context(
|
|
@@ -456,7 +482,7 @@ class FrameworkFirewallBase:
|
|
|
456
482
|
target_agent_id=specialist_agent_id,
|
|
457
483
|
tool_name=tool_name,
|
|
458
484
|
tool_args=clean_args,
|
|
459
|
-
record=DecisionTokenRecord(token, manager_agent_id, time.time(), self._token_hmac(token)),
|
|
485
|
+
record=DecisionTokenRecord(token, manager_agent_id, time.time(), self._token_hmac(token), tool_version or "", tool_digest or ""),
|
|
460
486
|
ttl_s=self.config.handoff_token_ttl_s,
|
|
461
487
|
)
|
|
462
488
|
return env
|
|
@@ -475,6 +501,8 @@ class FrameworkFirewallBase:
|
|
|
475
501
|
tool_args: Dict[str, Any],
|
|
476
502
|
decision_token: Optional[str] = None,
|
|
477
503
|
source_agent_id: Optional[str] = None,
|
|
504
|
+
tool_version: Optional[str] = None,
|
|
505
|
+
tool_digest: Optional[str] = None,
|
|
478
506
|
) -> Dict[str, Any]:
|
|
479
507
|
clean_args = self.strip_private_args(tool_args)
|
|
480
508
|
record = None
|
|
@@ -488,6 +516,12 @@ class FrameworkFirewallBase:
|
|
|
488
516
|
if record:
|
|
489
517
|
decision_token = record.decision_token
|
|
490
518
|
source_agent_id = source_agent_id or record.source_agent_id
|
|
519
|
+
if tool_version and record.tool_version and tool_version != record.tool_version:
|
|
520
|
+
raise self.denied_error("AgenticDome delegated tool version differs from the authorized version.")
|
|
521
|
+
if tool_digest and record.tool_digest and tool_digest != record.tool_digest:
|
|
522
|
+
raise self.denied_error("AgenticDome delegated tool digest differs from the authorized digest.")
|
|
523
|
+
tool_version = tool_version or record.tool_version or None
|
|
524
|
+
tool_digest = tool_digest or record.tool_digest or None
|
|
491
525
|
if record and self.config.token_hmac_secret:
|
|
492
526
|
expected = self._token_hmac(record.decision_token)
|
|
493
527
|
if not record.token_hmac or not hmac.compare_digest(record.token_hmac, expected):
|
|
@@ -500,6 +534,8 @@ class FrameworkFirewallBase:
|
|
|
500
534
|
decision_token,
|
|
501
535
|
tool_name=tool_name,
|
|
502
536
|
tool_args=clean_args,
|
|
537
|
+
tool_version=tool_version,
|
|
538
|
+
tool_digest=tool_digest,
|
|
503
539
|
agent_id=specialist_agent_id,
|
|
504
540
|
source_agent_id=source_agent_id,
|
|
505
541
|
platform=self.config.platform,
|
|
@@ -611,4 +647,3 @@ class FrameworkFirewallBase:
|
|
|
611
647
|
def decorator(fn: Callable[..., Any]) -> Callable[..., Any]:
|
|
612
648
|
return self.wrap_tool_handler(tool_name=tool_name or fn.__name__, handler=fn, **options)
|
|
613
649
|
return decorator
|
|
614
|
-
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import base64
|
|
2
|
+
import hashlib
|
|
2
3
|
import os
|
|
3
|
-
|
|
4
|
+
import re
|
|
5
|
+
from typing import Optional, Dict, List, Any, Tuple, Union, Mapping
|
|
4
6
|
|
|
5
7
|
import requests
|
|
6
8
|
from requests.adapters import HTTPAdapter
|
|
@@ -58,6 +60,8 @@ class AgentGuardClient:
|
|
|
58
60
|
pool_maxsize: int = 100,
|
|
59
61
|
pool_block: bool = False,
|
|
60
62
|
service_token: Optional[str] = None,
|
|
63
|
+
tool_provenance: Optional[Mapping[str, Mapping[str, str]]] = None,
|
|
64
|
+
execution_broker_mode: Optional[str] = None,
|
|
61
65
|
):
|
|
62
66
|
self.api_base = self._require_nonempty("api_base", api_base).rstrip("/")
|
|
63
67
|
self.api_key = self._require_nonempty(
|
|
@@ -84,6 +88,21 @@ class AgentGuardClient:
|
|
|
84
88
|
default=5.0,
|
|
85
89
|
)
|
|
86
90
|
self.user_agent = user_agent
|
|
91
|
+
broker_mode = str(
|
|
92
|
+
execution_broker_mode
|
|
93
|
+
if execution_broker_mode is not None
|
|
94
|
+
else os.getenv("AGENTICDOME_EXECUTION_BROKER_MODE", "off")
|
|
95
|
+
).strip().lower()
|
|
96
|
+
if broker_mode not in {"off", "observe", "enforce"}:
|
|
97
|
+
raise ValueError("execution_broker_mode must be off, observe, or enforce")
|
|
98
|
+
self.execution_broker_mode = broker_mode
|
|
99
|
+
self._tool_provenance: Dict[str, Dict[str, str]] = {}
|
|
100
|
+
for registered_name, provenance in dict(tool_provenance or {}).items():
|
|
101
|
+
self.register_tool_provenance(
|
|
102
|
+
registered_name,
|
|
103
|
+
tool_version=(provenance or {}).get("tool_version") or (provenance or {}).get("version"),
|
|
104
|
+
tool_digest=(provenance or {}).get("tool_digest") or (provenance or {}).get("digest"),
|
|
105
|
+
)
|
|
87
106
|
|
|
88
107
|
self.session = requests.Session()
|
|
89
108
|
retry = Retry(
|
|
@@ -158,6 +177,55 @@ class AgentGuardClient:
|
|
|
158
177
|
s = str(value).strip()
|
|
159
178
|
return s or None
|
|
160
179
|
|
|
180
|
+
def register_tool_provenance(
|
|
181
|
+
self,
|
|
182
|
+
tool_name: str,
|
|
183
|
+
*,
|
|
184
|
+
tool_version: Optional[str] = None,
|
|
185
|
+
tool_digest: Optional[str] = None,
|
|
186
|
+
tool_platform: Optional[str] = None,
|
|
187
|
+
) -> None:
|
|
188
|
+
"""Register precomputed provenance once; request-time lookup is local O(1)."""
|
|
189
|
+
name = self._require_nonempty("tool_name", tool_name)
|
|
190
|
+
version = self._normalize_optional_string(tool_version)
|
|
191
|
+
digest = self._normalize_optional_string(tool_digest)
|
|
192
|
+
if digest is not None and not re.fullmatch(r"sha256:[0-9a-f]{64}", digest):
|
|
193
|
+
raise ValueError("'tool_digest' must be sha256 followed by 64 lowercase hexadecimal characters")
|
|
194
|
+
if version is None and digest is None:
|
|
195
|
+
raise ValueError("tool_version or tool_digest is required")
|
|
196
|
+
key = f"{self._normalize_optional_string(tool_platform)}:{name}" if tool_platform else name
|
|
197
|
+
self._tool_provenance[key] = {"tool_version": version or "", "tool_digest": digest or ""}
|
|
198
|
+
|
|
199
|
+
def unregister_tool_provenance(self, tool_name: str, *, tool_platform: Optional[str] = None) -> None:
|
|
200
|
+
name = self._require_nonempty("tool_name", tool_name)
|
|
201
|
+
key = f"{self._normalize_optional_string(tool_platform)}:{name}" if tool_platform else name
|
|
202
|
+
self._tool_provenance.pop(key, None)
|
|
203
|
+
|
|
204
|
+
def _resolve_tool_provenance(
|
|
205
|
+
self,
|
|
206
|
+
*,
|
|
207
|
+
tool_name: Optional[str],
|
|
208
|
+
tool_version: Optional[str],
|
|
209
|
+
tool_digest: Optional[str],
|
|
210
|
+
tool_platform: Optional[str] = None,
|
|
211
|
+
policy_context: Optional[Dict[str, Any]] = None,
|
|
212
|
+
) -> Tuple[Optional[str], Optional[str]]:
|
|
213
|
+
if not tool_name:
|
|
214
|
+
return tool_version, tool_digest
|
|
215
|
+
context = policy_context or {}
|
|
216
|
+
context_provenance = context.get("tool_provenance") if isinstance(context.get("tool_provenance"), dict) else {}
|
|
217
|
+
key = f"{tool_platform}:{tool_name}" if tool_platform else ""
|
|
218
|
+
registered = self._tool_provenance.get(key) or self._tool_provenance.get(str(tool_name)) or {}
|
|
219
|
+
version = self._normalize_optional_string(tool_version) or self._normalize_optional_string(
|
|
220
|
+
context.get("tool_version") or context_provenance.get("tool_version") or context_provenance.get("version") or registered.get("tool_version")
|
|
221
|
+
)
|
|
222
|
+
digest = self._normalize_optional_string(tool_digest) or self._normalize_optional_string(
|
|
223
|
+
context.get("tool_digest") or context_provenance.get("tool_digest") or context_provenance.get("digest") or registered.get("tool_digest")
|
|
224
|
+
)
|
|
225
|
+
if digest is not None and not re.fullmatch(r"sha256:[0-9a-f]{64}", digest):
|
|
226
|
+
raise ValueError("'tool_digest' must be sha256 followed by 64 lowercase hexadecimal characters")
|
|
227
|
+
return version, digest
|
|
228
|
+
|
|
161
229
|
def _positive_float(self, name: str, value: Any, *, default: float) -> float:
|
|
162
230
|
try:
|
|
163
231
|
parsed = float(value)
|
|
@@ -257,6 +325,7 @@ class AgentGuardClient:
|
|
|
257
325
|
token: str,
|
|
258
326
|
tool_name: Optional[str] = None,
|
|
259
327
|
tool_args: Optional[Dict[str, Any]] = None,
|
|
328
|
+
tool_digest: Optional[str] = None,
|
|
260
329
|
) -> None:
|
|
261
330
|
"""
|
|
262
331
|
Validate decision-token verification inputs.
|
|
@@ -268,6 +337,8 @@ class AgentGuardClient:
|
|
|
268
337
|
raise ValueError("'tool_args' is required when 'tool_name' is provided")
|
|
269
338
|
if tool_args is not None and not tool_name:
|
|
270
339
|
raise ValueError("'tool_name' is required when 'tool_args' is provided")
|
|
340
|
+
if tool_digest is not None and not re.fullmatch(r"sha256:[0-9a-f]{64}", str(tool_digest)):
|
|
341
|
+
raise ValueError("'tool_digest' must be sha256 followed by 64 lowercase hexadecimal characters")
|
|
271
342
|
|
|
272
343
|
def _request(
|
|
273
344
|
self,
|
|
@@ -453,6 +524,8 @@ class AgentGuardClient:
|
|
|
453
524
|
tool_platform: Optional[str] = None,
|
|
454
525
|
tool_name: Optional[str] = None,
|
|
455
526
|
tool_args: Optional[Dict[str, Any]] = None,
|
|
527
|
+
tool_version: Optional[str] = None,
|
|
528
|
+
tool_digest: Optional[str] = None,
|
|
456
529
|
policy_context: Optional[Dict[str, Any]] = None,
|
|
457
530
|
reasoning_trace: Optional[str] = None,
|
|
458
531
|
agent_instance_id: Optional[str] = None,
|
|
@@ -465,12 +538,23 @@ class AgentGuardClient:
|
|
|
465
538
|
actual_role: Optional[str] = None,
|
|
466
539
|
source_agent_role: Optional[str] = None,
|
|
467
540
|
target_agent_role: Optional[str] = None,
|
|
541
|
+
actor_chain: Optional[List[Dict[str, Any]]] = None,
|
|
542
|
+
scopes: Optional[List[str]] = None,
|
|
543
|
+
permissions: Optional[List[str]] = None,
|
|
544
|
+
parent_jti: Optional[str] = None,
|
|
545
|
+
root_jti: Optional[str] = None,
|
|
546
|
+
policy_id: Optional[str] = None,
|
|
547
|
+
policy_version: Optional[str] = None,
|
|
548
|
+
policy_hash: Optional[str] = None,
|
|
549
|
+
proof_thumbprint: Optional[str] = None,
|
|
468
550
|
redact_pii: Optional[bool] = None,
|
|
469
551
|
redact_secrets: Optional[bool] = None,
|
|
470
552
|
block_on_sensitive_output: Optional[bool] = None,
|
|
471
553
|
trusted_destination_domains: Optional[List[str]] = None,
|
|
472
554
|
allowed_destination_domains: Optional[List[str]] = None,
|
|
473
555
|
attachments: Optional[List[str]] = None,
|
|
556
|
+
execution_broker: Optional[bool] = None,
|
|
557
|
+
execution_boundary_id: Optional[str] = None,
|
|
474
558
|
tenant_id: Optional[Union[str, int]] = None,
|
|
475
559
|
) -> Dict[str, Any]:
|
|
476
560
|
"""
|
|
@@ -491,6 +575,13 @@ class AgentGuardClient:
|
|
|
491
575
|
Delegation rule:
|
|
492
576
|
- source_agent_id requires source_platform
|
|
493
577
|
"""
|
|
578
|
+
tool_version, tool_digest = self._resolve_tool_provenance(
|
|
579
|
+
tool_name=tool_name,
|
|
580
|
+
tool_version=tool_version,
|
|
581
|
+
tool_digest=tool_digest,
|
|
582
|
+
tool_platform=tool_platform,
|
|
583
|
+
policy_context=policy_context,
|
|
584
|
+
)
|
|
494
585
|
normalized_direction = self._validate_guardrail_args(
|
|
495
586
|
text=text,
|
|
496
587
|
agent_id=agent_id,
|
|
@@ -502,6 +593,8 @@ class AgentGuardClient:
|
|
|
502
593
|
source_platform=source_platform,
|
|
503
594
|
user_id=user_id,
|
|
504
595
|
)
|
|
596
|
+
if tool_digest is not None and not re.fullmatch(r"sha256:[0-9a-f]{64}", str(tool_digest)):
|
|
597
|
+
raise ValueError("'tool_digest' must be sha256 followed by 64 lowercase hexadecimal characters")
|
|
505
598
|
|
|
506
599
|
merged_policy_context = self._merge_policy_context(
|
|
507
600
|
policy_context,
|
|
@@ -511,6 +604,8 @@ class AgentGuardClient:
|
|
|
511
604
|
tool_platform=tool_platform,
|
|
512
605
|
tool_name=tool_name,
|
|
513
606
|
tool_args=tool_args,
|
|
607
|
+
tool_version=tool_version,
|
|
608
|
+
tool_digest=tool_digest,
|
|
514
609
|
reasoning_trace=reasoning_trace,
|
|
515
610
|
agent_instance_id=agent_instance_id,
|
|
516
611
|
user_id=user_id,
|
|
@@ -522,6 +617,15 @@ class AgentGuardClient:
|
|
|
522
617
|
actual_role=actual_role,
|
|
523
618
|
source_agent_role=source_agent_role,
|
|
524
619
|
target_agent_role=target_agent_role,
|
|
620
|
+
actor_chain=actor_chain,
|
|
621
|
+
scopes=scopes,
|
|
622
|
+
permissions=permissions,
|
|
623
|
+
parent_jti=parent_jti,
|
|
624
|
+
root_jti=root_jti,
|
|
625
|
+
policy_id=policy_id,
|
|
626
|
+
policy_version=policy_version,
|
|
627
|
+
policy_hash=policy_hash,
|
|
628
|
+
proof_thumbprint=proof_thumbprint,
|
|
525
629
|
redact_pii=redact_pii,
|
|
526
630
|
redact_secrets=redact_secrets,
|
|
527
631
|
block_on_sensitive_output=block_on_sensitive_output,
|
|
@@ -540,6 +644,8 @@ class AgentGuardClient:
|
|
|
540
644
|
"tool_platform": tool_platform,
|
|
541
645
|
"tool_name": tool_name,
|
|
542
646
|
"tool_args": tool_args,
|
|
647
|
+
"tool_version": tool_version,
|
|
648
|
+
"tool_digest": tool_digest,
|
|
543
649
|
"policy_context": merged_policy_context,
|
|
544
650
|
"reasoning_trace": reasoning_trace,
|
|
545
651
|
"agent_instance_id": agent_instance_id,
|
|
@@ -552,6 +658,15 @@ class AgentGuardClient:
|
|
|
552
658
|
"actual_role": actual_role,
|
|
553
659
|
"source_agent_role": source_agent_role,
|
|
554
660
|
"target_agent_role": target_agent_role,
|
|
661
|
+
"actor_chain": actor_chain,
|
|
662
|
+
"scopes": scopes,
|
|
663
|
+
"permissions": permissions,
|
|
664
|
+
"parent_jti": parent_jti,
|
|
665
|
+
"root_jti": root_jti,
|
|
666
|
+
"policy_id": policy_id,
|
|
667
|
+
"policy_version": policy_version,
|
|
668
|
+
"policy_hash": policy_hash,
|
|
669
|
+
"proof_thumbprint": proof_thumbprint,
|
|
555
670
|
"redact_pii": redact_pii,
|
|
556
671
|
"redact_secrets": redact_secrets,
|
|
557
672
|
"block_on_sensitive_output": block_on_sensitive_output,
|
|
@@ -561,12 +676,33 @@ class AgentGuardClient:
|
|
|
561
676
|
}
|
|
562
677
|
)
|
|
563
678
|
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
"
|
|
567
|
-
json_body=payload,
|
|
568
|
-
tenant_id=tenant_id,
|
|
679
|
+
broker_enabled = bool(
|
|
680
|
+
tool_name
|
|
681
|
+
and (self.execution_broker_mode in {"observe", "enforce"} or execution_broker is True)
|
|
569
682
|
)
|
|
683
|
+
if broker_enabled:
|
|
684
|
+
boundary_id = self._normalize_optional_string(execution_boundary_id)
|
|
685
|
+
if boundary_id is None:
|
|
686
|
+
material = "|".join([
|
|
687
|
+
str(platform or "unknown"),
|
|
688
|
+
str(agent_id),
|
|
689
|
+
str(tool_name),
|
|
690
|
+
str(session_id or "stateless"),
|
|
691
|
+
])
|
|
692
|
+
boundary_id = "sdk:" + hashlib.sha256(material.encode("utf-8")).hexdigest()[:32]
|
|
693
|
+
payload["boundary_id"] = boundary_id
|
|
694
|
+
|
|
695
|
+
path = "/tools/execution/authorize" if broker_enabled else "/tools/guardrail/validate"
|
|
696
|
+
response = self._request("POST", path, json_body=payload, tenant_id=tenant_id)
|
|
697
|
+
if broker_enabled:
|
|
698
|
+
broker = response.get("broker") if isinstance(response.get("broker"), dict) else {}
|
|
699
|
+
verified = bool(broker.get("verified")) and bool(broker.get("token_consumed"))
|
|
700
|
+
must_enforce = self.execution_broker_mode == "enforce" or execution_broker is True
|
|
701
|
+
if must_enforce and not verified:
|
|
702
|
+
raise AgentGuardError(
|
|
703
|
+
"AgenticDome execution broker did not return a verified, atomically consumed decision"
|
|
704
|
+
)
|
|
705
|
+
return response
|
|
570
706
|
|
|
571
707
|
# Backward compatibility alias
|
|
572
708
|
def guardrail_check(
|
|
@@ -585,6 +721,13 @@ class AgentGuardClient:
|
|
|
585
721
|
policy_context=policy_context,
|
|
586
722
|
)
|
|
587
723
|
|
|
724
|
+
def get_tool_provenance_status(
|
|
725
|
+
self,
|
|
726
|
+
tenant_id: Optional[Union[str, int]] = None,
|
|
727
|
+
) -> Dict[str, Any]:
|
|
728
|
+
"""Return the tenant's signed provenance cache readiness and tool count."""
|
|
729
|
+
return self._request("GET", "/tools/provenance/status", tenant_id=tenant_id)
|
|
730
|
+
|
|
588
731
|
# ------------------------------------------------------------------
|
|
589
732
|
# Mesh
|
|
590
733
|
# ------------------------------------------------------------------
|
|
@@ -780,6 +923,8 @@ class AgentGuardClient:
|
|
|
780
923
|
source_platform: str,
|
|
781
924
|
tool_name: str,
|
|
782
925
|
tool_args: Dict[str, Any],
|
|
926
|
+
tool_version: Optional[str] = None,
|
|
927
|
+
tool_digest: Optional[str] = None,
|
|
783
928
|
tool_platform: Optional[str] = None,
|
|
784
929
|
policy_context: Optional[Dict[str, Any]] = None,
|
|
785
930
|
session_id: Optional[str] = None,
|
|
@@ -830,6 +975,15 @@ class AgentGuardClient:
|
|
|
830
975
|
self._require_nonempty("source_agent_id", source_agent_id)
|
|
831
976
|
self._require_nonempty("source_platform", source_platform)
|
|
832
977
|
self._require_nonempty("tool_name", tool_name)
|
|
978
|
+
tool_version, tool_digest = self._resolve_tool_provenance(
|
|
979
|
+
tool_name=tool_name,
|
|
980
|
+
tool_version=tool_version,
|
|
981
|
+
tool_digest=tool_digest,
|
|
982
|
+
tool_platform=tool_platform,
|
|
983
|
+
policy_context=policy_context,
|
|
984
|
+
)
|
|
985
|
+
if tool_digest is not None and not re.fullmatch(r"sha256:[0-9a-f]{64}", str(tool_digest)):
|
|
986
|
+
raise ValueError("'tool_digest' must be sha256 followed by 64 lowercase hexadecimal characters")
|
|
833
987
|
|
|
834
988
|
normalized_direction = self._validate_guardrail_args(
|
|
835
989
|
text=text,
|
|
@@ -851,6 +1005,8 @@ class AgentGuardClient:
|
|
|
851
1005
|
source_agent_id=source_agent_id,
|
|
852
1006
|
tool_name=tool_name,
|
|
853
1007
|
tool_args=tool_args,
|
|
1008
|
+
tool_version=tool_version,
|
|
1009
|
+
tool_digest=tool_digest,
|
|
854
1010
|
request_purpose=request_purpose,
|
|
855
1011
|
purpose=purpose,
|
|
856
1012
|
intent=intent,
|
|
@@ -887,6 +1043,8 @@ class AgentGuardClient:
|
|
|
887
1043
|
"tool_platform": tool_platform,
|
|
888
1044
|
"tool_name": tool_name,
|
|
889
1045
|
"tool_args": tool_args,
|
|
1046
|
+
"tool_version": tool_version,
|
|
1047
|
+
"tool_digest": tool_digest,
|
|
890
1048
|
"policy_context": merged_policy_context,
|
|
891
1049
|
"source_agent_id": source_agent_id,
|
|
892
1050
|
"request_purpose": request_purpose,
|
|
@@ -937,6 +1095,8 @@ class AgentGuardClient:
|
|
|
937
1095
|
*,
|
|
938
1096
|
tool_name: Optional[str] = None,
|
|
939
1097
|
tool_args: Optional[Dict[str, Any]] = None,
|
|
1098
|
+
tool_version: Optional[str] = None,
|
|
1099
|
+
tool_digest: Optional[str] = None,
|
|
940
1100
|
agent_id: Optional[str] = None,
|
|
941
1101
|
source_agent_id: Optional[str] = None,
|
|
942
1102
|
platform: Optional[str] = None,
|
|
@@ -958,10 +1118,17 @@ class AgentGuardClient:
|
|
|
958
1118
|
- platform
|
|
959
1119
|
- source_agent_id (for delegated sensitive flows)
|
|
960
1120
|
"""
|
|
1121
|
+
tool_version, tool_digest = self._resolve_tool_provenance(
|
|
1122
|
+
tool_name=tool_name,
|
|
1123
|
+
tool_version=tool_version,
|
|
1124
|
+
tool_digest=tool_digest,
|
|
1125
|
+
tool_platform=platform,
|
|
1126
|
+
)
|
|
961
1127
|
self._validate_decision_verify_args(
|
|
962
1128
|
token=token,
|
|
963
1129
|
tool_name=tool_name,
|
|
964
1130
|
tool_args=tool_args,
|
|
1131
|
+
tool_digest=tool_digest,
|
|
965
1132
|
)
|
|
966
1133
|
|
|
967
1134
|
payload = self._drop_none(
|
|
@@ -969,6 +1136,8 @@ class AgentGuardClient:
|
|
|
969
1136
|
"token": token,
|
|
970
1137
|
"tool_name": tool_name,
|
|
971
1138
|
"tool_args": tool_args,
|
|
1139
|
+
"tool_version": tool_version,
|
|
1140
|
+
"tool_digest": tool_digest,
|
|
972
1141
|
"agent_id": agent_id,
|
|
973
1142
|
"source_agent_id": source_agent_id,
|
|
974
1143
|
"platform": platform,
|
|
@@ -989,6 +1158,8 @@ class AgentGuardClient:
|
|
|
989
1158
|
*,
|
|
990
1159
|
tool_name: Optional[str] = None,
|
|
991
1160
|
tool_args: Optional[Dict[str, Any]] = None,
|
|
1161
|
+
tool_version: Optional[str] = None,
|
|
1162
|
+
tool_digest: Optional[str] = None,
|
|
992
1163
|
agent_id: Optional[str] = None,
|
|
993
1164
|
source_agent_id: Optional[str] = None,
|
|
994
1165
|
platform: Optional[str] = None,
|
|
@@ -1011,10 +1182,17 @@ class AgentGuardClient:
|
|
|
1011
1182
|
- platform
|
|
1012
1183
|
- source_agent_id (for delegated sensitive flows)
|
|
1013
1184
|
"""
|
|
1185
|
+
tool_version, tool_digest = self._resolve_tool_provenance(
|
|
1186
|
+
tool_name=tool_name,
|
|
1187
|
+
tool_version=tool_version,
|
|
1188
|
+
tool_digest=tool_digest,
|
|
1189
|
+
tool_platform=platform,
|
|
1190
|
+
)
|
|
1014
1191
|
self._validate_decision_verify_args(
|
|
1015
1192
|
token=token,
|
|
1016
1193
|
tool_name=tool_name,
|
|
1017
1194
|
tool_args=tool_args,
|
|
1195
|
+
tool_digest=tool_digest,
|
|
1018
1196
|
)
|
|
1019
1197
|
|
|
1020
1198
|
arguments = self._drop_none(
|
|
@@ -1022,6 +1200,8 @@ class AgentGuardClient:
|
|
|
1022
1200
|
"token": token,
|
|
1023
1201
|
"tool_name": tool_name,
|
|
1024
1202
|
"tool_args": tool_args,
|
|
1203
|
+
"tool_version": tool_version,
|
|
1204
|
+
"tool_digest": tool_digest,
|
|
1025
1205
|
"agent_id": agent_id,
|
|
1026
1206
|
"source_agent_id": source_agent_id,
|
|
1027
1207
|
"platform": platform,
|
|
@@ -1111,6 +1291,8 @@ class AgentGuardClient:
|
|
|
1111
1291
|
tool_platform: Optional[str] = None,
|
|
1112
1292
|
tool_name: Optional[str] = None,
|
|
1113
1293
|
tool_args: Optional[Dict[str, Any]] = None,
|
|
1294
|
+
tool_version: Optional[str] = None,
|
|
1295
|
+
tool_digest: Optional[str] = None,
|
|
1114
1296
|
policy_context: Optional[Dict[str, Any]] = None,
|
|
1115
1297
|
direction: str = "outbound",
|
|
1116
1298
|
source_agent_id: Optional[str] = None,
|
|
@@ -1149,6 +1331,13 @@ class AgentGuardClient:
|
|
|
1149
1331
|
Delegation rule:
|
|
1150
1332
|
- source_agent_id requires source_platform
|
|
1151
1333
|
"""
|
|
1334
|
+
tool_version, tool_digest = self._resolve_tool_provenance(
|
|
1335
|
+
tool_name=tool_name,
|
|
1336
|
+
tool_version=tool_version,
|
|
1337
|
+
tool_digest=tool_digest,
|
|
1338
|
+
tool_platform=tool_platform,
|
|
1339
|
+
policy_context=policy_context,
|
|
1340
|
+
)
|
|
1152
1341
|
normalized_direction = self._validate_guardrail_args(
|
|
1153
1342
|
text=text,
|
|
1154
1343
|
agent_id=agent_id,
|
|
@@ -1170,6 +1359,8 @@ class AgentGuardClient:
|
|
|
1170
1359
|
user_id=user_id,
|
|
1171
1360
|
tool_name=tool_name,
|
|
1172
1361
|
tool_args=tool_args,
|
|
1362
|
+
tool_version=tool_version,
|
|
1363
|
+
tool_digest=tool_digest,
|
|
1173
1364
|
reasoning_trace=reasoning_trace,
|
|
1174
1365
|
request_purpose=request_purpose,
|
|
1175
1366
|
purpose=purpose,
|
|
@@ -1195,6 +1386,8 @@ class AgentGuardClient:
|
|
|
1195
1386
|
"tool_platform": tool_platform,
|
|
1196
1387
|
"tool_name": tool_name,
|
|
1197
1388
|
"tool_args": tool_args,
|
|
1389
|
+
"tool_version": tool_version,
|
|
1390
|
+
"tool_digest": tool_digest,
|
|
1198
1391
|
"policy_context": merged_policy_context,
|
|
1199
1392
|
"source_agent_id": source_agent_id,
|
|
1200
1393
|
"user_id": user_id,
|
|
@@ -1214,6 +1407,41 @@ class AgentGuardClient:
|
|
|
1214
1407
|
}
|
|
1215
1408
|
)
|
|
1216
1409
|
|
|
1410
|
+
# In broker mode the official MCP adapter uses the same one-request
|
|
1411
|
+
# execution boundary as REST. This preserves immediate decision
|
|
1412
|
+
# consumption without adding a second network round trip.
|
|
1413
|
+
if tool_name and self.execution_broker_mode in {"observe", "enforce"}:
|
|
1414
|
+
return self.guardrail_validate(
|
|
1415
|
+
text=text,
|
|
1416
|
+
agent_id=agent_id,
|
|
1417
|
+
platform=platform,
|
|
1418
|
+
source_platform=source_platform,
|
|
1419
|
+
tool_platform=tool_platform,
|
|
1420
|
+
tool_name=tool_name,
|
|
1421
|
+
tool_args=tool_args,
|
|
1422
|
+
tool_version=tool_version,
|
|
1423
|
+
tool_digest=tool_digest,
|
|
1424
|
+
policy_context=policy_context,
|
|
1425
|
+
direction=direction,
|
|
1426
|
+
source_agent_id=source_agent_id,
|
|
1427
|
+
user_id=user_id,
|
|
1428
|
+
reasoning_trace=reasoning_trace,
|
|
1429
|
+
request_purpose=request_purpose,
|
|
1430
|
+
purpose=purpose,
|
|
1431
|
+
intent=intent,
|
|
1432
|
+
claimed_role=claimed_role,
|
|
1433
|
+
actual_role=actual_role,
|
|
1434
|
+
source_agent_role=source_agent_role,
|
|
1435
|
+
target_agent_role=target_agent_role,
|
|
1436
|
+
redact_pii=redact_pii,
|
|
1437
|
+
redact_secrets=redact_secrets,
|
|
1438
|
+
block_on_sensitive_output=block_on_sensitive_output,
|
|
1439
|
+
trusted_destination_domains=trusted_destination_domains,
|
|
1440
|
+
allowed_destination_domains=allowed_destination_domains,
|
|
1441
|
+
execution_broker=True,
|
|
1442
|
+
tenant_id=tenant_id,
|
|
1443
|
+
)
|
|
1444
|
+
|
|
1217
1445
|
return self.mcp_tool_call(
|
|
1218
1446
|
"guardrail.validate",
|
|
1219
1447
|
arguments,
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "agenticdome-python-sdk"
|
|
7
|
-
version = "1.2.
|
|
7
|
+
version = "1.2.8"
|
|
8
8
|
description = "Official Python SDK for AgenticDome security guardrails, DLP, tool authorization, and multi-agent delegation enforcement."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.9"
|
{agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.8}/tests/test_autogen_integration.py
RENAMED
|
@@ -20,6 +20,10 @@ class FakeClient:
|
|
|
20
20
|
self.mesh_calls = []
|
|
21
21
|
self.incidents = []
|
|
22
22
|
self.revocations = []
|
|
23
|
+
self.provenance = {}
|
|
24
|
+
|
|
25
|
+
def register_tool_provenance(self, tool_name, **kwargs):
|
|
26
|
+
self.provenance[tool_name] = kwargs
|
|
23
27
|
|
|
24
28
|
def guardrail_validate(self, **kwargs):
|
|
25
29
|
self.guardrail_calls.append(kwargs)
|
|
@@ -69,6 +73,24 @@ def test_autogen_requires_agenticdome_runtime_configuration():
|
|
|
69
73
|
AgenticDomeAutoGenFirewall(FirewallConfig(api_base="", api_key="", tenant_id=""))
|
|
70
74
|
|
|
71
75
|
|
|
76
|
+
def test_autogen_forwards_explicit_tool_provenance_and_supports_cached_registration():
|
|
77
|
+
fw, client = firewall()
|
|
78
|
+
digest = "sha256:" + "c" * 64
|
|
79
|
+
fw.register_tool_provenance("crm.lookup", tool_version="3.0.0", tool_digest=digest)
|
|
80
|
+
fw.authorize_tool_call(
|
|
81
|
+
session_id="tool-provenance",
|
|
82
|
+
agent_id="planner",
|
|
83
|
+
tool_name="crm.lookup",
|
|
84
|
+
tool_args={"customer_id": "42"},
|
|
85
|
+
tool_version="3.0.0",
|
|
86
|
+
tool_digest=digest,
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
assert client.provenance["crm.lookup"]["tool_digest"] == digest
|
|
90
|
+
assert client.guardrail_calls[-1]["tool_version"] == "3.0.0"
|
|
91
|
+
assert client.guardrail_calls[-1]["tool_digest"] == digest
|
|
92
|
+
|
|
93
|
+
|
|
72
94
|
def test_conversation_window_sends_family_two_metrics_and_redacts_output():
|
|
73
95
|
fw, client = firewall()
|
|
74
96
|
|
|
@@ -90,6 +90,65 @@ def test_guardrail_validate_success(mock_request, client):
|
|
|
90
90
|
assert payload["platform"] == "microsoft"
|
|
91
91
|
|
|
92
92
|
|
|
93
|
+
@patch("agenticdome_sdk.client.requests.Session.request")
|
|
94
|
+
def test_registered_tool_provenance_is_forwarded_without_runtime_hashing(mock_request, client):
|
|
95
|
+
digest = "sha256:" + "a" * 64
|
|
96
|
+
mock_request.return_value = make_response(200, {"verdict": "ALLOWED"})
|
|
97
|
+
client.register_tool_provenance("crm.lookup", tool_version="4.2.1", tool_digest=digest, tool_platform="mcp")
|
|
98
|
+
|
|
99
|
+
client.guardrail_validate(
|
|
100
|
+
text="lookup customer",
|
|
101
|
+
agent_id="support-agent",
|
|
102
|
+
direction="outbound",
|
|
103
|
+
platform="langgraph",
|
|
104
|
+
tool_platform="mcp",
|
|
105
|
+
tool_name="crm.lookup",
|
|
106
|
+
tool_args={"customer_id": "42"},
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
payload = mock_request.call_args.kwargs["json"]
|
|
110
|
+
assert payload["tool_version"] == "4.2.1"
|
|
111
|
+
assert payload["tool_digest"] == digest
|
|
112
|
+
assert payload["policy_context"]["tool_digest"] == digest
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
@patch("agenticdome_sdk.client.requests.Session.request")
|
|
116
|
+
def test_registered_provenance_is_bound_to_authorize_and_verify(mock_request, client):
|
|
117
|
+
digest = "sha256:" + "b" * 64
|
|
118
|
+
mock_request.return_value = make_response(200, {"jsonrpc": "2.0", "id": "1", "result": {"verdict": "ALLOWED", "valid": True}})
|
|
119
|
+
client.register_tool_provenance("payments.refund", tool_version="2.0.0", tool_digest=digest)
|
|
120
|
+
|
|
121
|
+
client.a2a_authorize_tool(
|
|
122
|
+
text="delegate refund",
|
|
123
|
+
agent_id="billing-agent",
|
|
124
|
+
platform="autogen",
|
|
125
|
+
source_agent_id="manager-agent",
|
|
126
|
+
source_platform="autogen",
|
|
127
|
+
tool_name="payments.refund",
|
|
128
|
+
tool_args={"amount": 25},
|
|
129
|
+
)
|
|
130
|
+
authorize = mock_request.call_args.kwargs["json"]["params"]["arguments"]
|
|
131
|
+
assert authorize["tool_version"] == "2.0.0"
|
|
132
|
+
assert authorize["tool_digest"] == digest
|
|
133
|
+
|
|
134
|
+
client.a2a_verify_decision_token_rpc(
|
|
135
|
+
"decision-token",
|
|
136
|
+
tool_name="payments.refund",
|
|
137
|
+
tool_args={"amount": 25},
|
|
138
|
+
agent_id="billing-agent",
|
|
139
|
+
source_agent_id="manager-agent",
|
|
140
|
+
platform="autogen",
|
|
141
|
+
)
|
|
142
|
+
verify = mock_request.call_args.kwargs["json"]["params"]["arguments"]
|
|
143
|
+
assert verify["tool_version"] == "2.0.0"
|
|
144
|
+
assert verify["tool_digest"] == digest
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def test_invalid_registered_tool_digest_fails_before_network(client):
|
|
148
|
+
with pytest.raises(ValueError, match="tool_digest"):
|
|
149
|
+
client.register_tool_provenance("unsafe.tool", tool_digest="sha256:not-a-real-digest")
|
|
150
|
+
|
|
151
|
+
|
|
93
152
|
@patch("agenticdome_sdk.client.requests.Session.request")
|
|
94
153
|
def test_guardrail_validate_blocks_invalid_tool_pairing(mock_request, client):
|
|
95
154
|
with pytest.raises(ValueError, match="'tool_args' is required"):
|
|
@@ -297,6 +356,26 @@ def test_mcp_guardrail_validate(mock_request, client):
|
|
|
297
356
|
assert arguments["tool_name"] == "mcp.execute_remote_patch"
|
|
298
357
|
|
|
299
358
|
|
|
359
|
+
@patch("agenticdome_sdk.client.requests.Session.request")
|
|
360
|
+
def test_mcp_guardrail_uses_registered_tool_provenance(mock_request, client):
|
|
361
|
+
digest = "sha256:" + "d" * 64
|
|
362
|
+
mock_request.return_value = make_response(200, {"jsonrpc": "2.0", "id": "1", "result": {"verdict": "ALLOWED"}})
|
|
363
|
+
client.register_tool_provenance("mcp.customer.lookup", tool_version="1.8.0", tool_digest=digest, tool_platform="mcp")
|
|
364
|
+
|
|
365
|
+
client.mcp_guardrail_validate(
|
|
366
|
+
text="lookup customer",
|
|
367
|
+
agent_id="mcp-agent",
|
|
368
|
+
platform="mcp",
|
|
369
|
+
tool_platform="mcp",
|
|
370
|
+
tool_name="mcp.customer.lookup",
|
|
371
|
+
tool_args={"customer_id": "42"},
|
|
372
|
+
)
|
|
373
|
+
|
|
374
|
+
arguments = mock_request.call_args.kwargs["json"]["params"]["arguments"]
|
|
375
|
+
assert arguments["tool_version"] == "1.8.0"
|
|
376
|
+
assert arguments["tool_digest"] == digest
|
|
377
|
+
|
|
378
|
+
|
|
300
379
|
@patch("agenticdome_sdk.client.requests.Session.request")
|
|
301
380
|
def test_mesh_validate(mock_request, client):
|
|
302
381
|
mock_request.return_value = make_response(
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
from agenticdome_sdk.client import AgentGuardClient, AgentGuardError
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def _client(mode="enforce"):
|
|
5
|
+
return AgentGuardClient(
|
|
6
|
+
"https://sidecar.example",
|
|
7
|
+
api_key="test-key",
|
|
8
|
+
tenant_id="tenant-1",
|
|
9
|
+
execution_broker_mode=mode,
|
|
10
|
+
max_retries=0,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def test_tool_authorization_uses_one_request_broker_and_requires_receipt(monkeypatch):
|
|
15
|
+
client = _client()
|
|
16
|
+
observed = {}
|
|
17
|
+
|
|
18
|
+
def request(method, path, **kwargs):
|
|
19
|
+
observed.update({"method": method, "path": path, **kwargs})
|
|
20
|
+
return {"verdict": "ALLOWED", "broker": {"verified": True, "token_consumed": True}}
|
|
21
|
+
|
|
22
|
+
monkeypatch.setattr(client, "_request", request)
|
|
23
|
+
response = client.guardrail_validate(
|
|
24
|
+
text="lookup customer",
|
|
25
|
+
agent_id="support-agent",
|
|
26
|
+
platform="custom_python",
|
|
27
|
+
source_agent_id="support-manager",
|
|
28
|
+
source_platform="microsoft",
|
|
29
|
+
tool_name="crm.lookup",
|
|
30
|
+
tool_args={"id": "123"},
|
|
31
|
+
actor_chain=[
|
|
32
|
+
{"id": "support-manager", "framework": "microsoft"},
|
|
33
|
+
{"id": "support-agent", "framework": "custom_python"},
|
|
34
|
+
],
|
|
35
|
+
scopes=["crm.customer.read"],
|
|
36
|
+
permissions=["case.status.read"],
|
|
37
|
+
parent_jti="parent-decision",
|
|
38
|
+
root_jti="root-decision",
|
|
39
|
+
policy_id="support-policy",
|
|
40
|
+
policy_version="3",
|
|
41
|
+
policy_hash="sha256:" + "a" * 64,
|
|
42
|
+
proof_thumbprint="proof-thumbprint",
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
assert response["verdict"] == "ALLOWED"
|
|
46
|
+
assert observed["path"] == "/tools/execution/authorize"
|
|
47
|
+
assert observed["json_body"]["boundary_id"].startswith("sdk:")
|
|
48
|
+
assert observed["json_body"]["actor_chain"][1]["id"] == "support-agent"
|
|
49
|
+
assert observed["json_body"]["scopes"] == ["crm.customer.read"]
|
|
50
|
+
assert observed["json_body"]["permissions"] == ["case.status.read"]
|
|
51
|
+
assert observed["json_body"]["parent_jti"] == "parent-decision"
|
|
52
|
+
assert observed["json_body"]["root_jti"] == "root-decision"
|
|
53
|
+
assert observed["json_body"]["policy_id"] == "support-policy"
|
|
54
|
+
assert observed["json_body"]["policy_version"] == "3"
|
|
55
|
+
assert observed["json_body"]["policy_hash"] == "sha256:" + "a" * 64
|
|
56
|
+
assert observed["json_body"]["proof_thumbprint"] == "proof-thumbprint"
|
|
57
|
+
assert observed["json_body"]["policy_context"]["actor_chain"][0]["id"] == "support-manager"
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def test_enforced_broker_fails_closed_without_consumed_receipt(monkeypatch):
|
|
61
|
+
client = _client()
|
|
62
|
+
monkeypatch.setattr(client, "_request", lambda *args, **kwargs: {"verdict": "ALLOWED"})
|
|
63
|
+
|
|
64
|
+
try:
|
|
65
|
+
client.guardrail_validate(
|
|
66
|
+
text="lookup customer",
|
|
67
|
+
agent_id="support-agent",
|
|
68
|
+
platform="custom_python",
|
|
69
|
+
tool_name="crm.lookup",
|
|
70
|
+
tool_args={"id": "123"},
|
|
71
|
+
)
|
|
72
|
+
except AgentGuardError as exc:
|
|
73
|
+
assert "atomically consumed" in str(exc)
|
|
74
|
+
else:
|
|
75
|
+
raise AssertionError("missing broker receipt did not fail closed")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.8}/agenticdome_sdk/aws_bedrock.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.8}/agenticdome_sdk/openai_agents.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.8}/tests/test_agno_integration.py
RENAMED
|
File without changes
|
|
File without changes
|
{agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.8}/tests/test_aws_bedrock_integration.py
RENAMED
|
File without changes
|
{agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.8}/tests/test_claude_integration.py
RENAMED
|
File without changes
|
{agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.8}/tests/test_crewai_integration.py
RENAMED
|
File without changes
|
{agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.8}/tests/test_google_adk_integration.py
RENAMED
|
File without changes
|
{agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.8}/tests/test_identity_context.py
RENAMED
|
File without changes
|
{agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.8}/tests/test_langgraph_integration.py
RENAMED
|
File without changes
|
|
File without changes
|
{agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.8}/tests/test_llamaindex_integration.py
RENAMED
|
File without changes
|
{agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.8}/tests/test_mcp_host_integration.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.8}/tests/test_pydanticai_integration.py
RENAMED
|
File without changes
|
{agenticdome_python_sdk-1.2.6 → agenticdome_python_sdk-1.2.8}/tests/test_smolagents_integration.py
RENAMED
|
File without changes
|