agentguardproxy 0.5.0__tar.gz → 0.5.2__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.
- {agentguardproxy-0.5.0 → agentguardproxy-0.5.2}/PKG-INFO +2 -3
- {agentguardproxy-0.5.0 → agentguardproxy-0.5.2}/agentguard/__init__.py +61 -64
- {agentguardproxy-0.5.0 → agentguardproxy-0.5.2}/agentguard/adapters/browseruse.py +14 -15
- agentguardproxy-0.5.2/agentguard/adapters/crewai.py +610 -0
- agentguardproxy-0.5.2/agentguard/adapters/langchain.py +619 -0
- {agentguardproxy-0.5.0 → agentguardproxy-0.5.2}/agentguard/adapters/mcp.py +34 -36
- {agentguardproxy-0.5.0 → agentguardproxy-0.5.2}/agentguardproxy.egg-info/PKG-INFO +2 -3
- {agentguardproxy-0.5.0 → agentguardproxy-0.5.2}/pyproject.toml +8 -9
- {agentguardproxy-0.5.0 → agentguardproxy-0.5.2}/tests/test_adapters_extended.py +11 -12
- {agentguardproxy-0.5.0 → agentguardproxy-0.5.2}/tests/test_crewai.py +128 -78
- {agentguardproxy-0.5.0 → agentguardproxy-0.5.2}/tests/test_guard.py +1 -1
- {agentguardproxy-0.5.0 → agentguardproxy-0.5.2}/tests/test_langchain.py +96 -25
- agentguardproxy-0.5.0/agentguard/adapters/crewai.py +0 -596
- agentguardproxy-0.5.0/agentguard/adapters/langchain.py +0 -470
- {agentguardproxy-0.5.0 → agentguardproxy-0.5.2}/README.md +0 -0
- {agentguardproxy-0.5.0 → agentguardproxy-0.5.2}/agentguard/adapters/__init__.py +0 -0
- {agentguardproxy-0.5.0 → agentguardproxy-0.5.2}/agentguardproxy.egg-info/SOURCES.txt +0 -0
- {agentguardproxy-0.5.0 → agentguardproxy-0.5.2}/agentguardproxy.egg-info/dependency_links.txt +0 -0
- {agentguardproxy-0.5.0 → agentguardproxy-0.5.2}/agentguardproxy.egg-info/requires.txt +0 -0
- {agentguardproxy-0.5.0 → agentguardproxy-0.5.2}/agentguardproxy.egg-info/top_level.txt +0 -0
- {agentguardproxy-0.5.0 → agentguardproxy-0.5.2}/setup.cfg +0 -0
- {agentguardproxy-0.5.0 → agentguardproxy-0.5.2}/tests/test_adapters.py +0 -0
- {agentguardproxy-0.5.0 → agentguardproxy-0.5.2}/tests/test_browseruse.py +0 -0
- {agentguardproxy-0.5.0 → agentguardproxy-0.5.2}/tests/test_decorator.py +0 -0
- {agentguardproxy-0.5.0 → agentguardproxy-0.5.2}/tests/test_end_to_end_real_server.py +0 -0
- {agentguardproxy-0.5.0 → agentguardproxy-0.5.2}/tests/test_integration.py +0 -0
- {agentguardproxy-0.5.0 → agentguardproxy-0.5.2}/tests/test_mcp.py +0 -0
- {agentguardproxy-0.5.0 → agentguardproxy-0.5.2}/tests/test_mcp_fuzz.py +0 -0
- {agentguardproxy-0.5.0 → agentguardproxy-0.5.2}/tests/test_mcp_gateway.py +0 -0
- {agentguardproxy-0.5.0 → agentguardproxy-0.5.2}/tests/test_polling_jitter.py +0 -0
- {agentguardproxy-0.5.0 → agentguardproxy-0.5.2}/tests/test_redactor_property.py +0 -0
- {agentguardproxy-0.5.0 → agentguardproxy-0.5.2}/tests/test_sdk_integration.py +0 -0
- {agentguardproxy-0.5.0 → agentguardproxy-0.5.2}/tests/test_sdk_polish.py +0 -0
- {agentguardproxy-0.5.0 → agentguardproxy-0.5.2}/tests/test_tenant_routing.py +0 -0
- {agentguardproxy-0.5.0 → agentguardproxy-0.5.2}/tests/test_wire_format.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agentguardproxy
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.2
|
|
4
4
|
Summary: Python SDK for AgentGuard — the firewall for AI agents
|
|
5
5
|
Author-email: Cauã Ferraz <cauaferrazp@gmail.com>
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -12,14 +12,13 @@ Keywords: ai,agents,firewall,policy,guardrails,safety
|
|
|
12
12
|
Classifier: Development Status :: 3 - Alpha
|
|
13
13
|
Classifier: Intended Audience :: Developers
|
|
14
14
|
Classifier: Programming Language :: Python :: 3
|
|
15
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
16
15
|
Classifier: Programming Language :: Python :: 3.10
|
|
17
16
|
Classifier: Programming Language :: Python :: 3.11
|
|
18
17
|
Classifier: Programming Language :: Python :: 3.12
|
|
19
18
|
Classifier: Programming Language :: Python :: 3.13
|
|
20
19
|
Classifier: Topic :: Security
|
|
21
20
|
Classifier: Topic :: Software Development :: Libraries
|
|
22
|
-
Requires-Python: >=3.
|
|
21
|
+
Requires-Python: >=3.10
|
|
23
22
|
Description-Content-Type: text/markdown
|
|
24
23
|
Provides-Extra: langchain
|
|
25
24
|
Requires-Dist: langchain-core<2.0,>=0.3; extra == "langchain"
|
|
@@ -47,7 +47,7 @@ DECISION_REQUIRE_APPROVAL = "REQUIRE_APPROVAL"
|
|
|
47
47
|
|
|
48
48
|
# API endpoint paths. The leading "/v1" is added by Guard._url so a single
|
|
49
49
|
# code path handles both the legacy /v1/<suffix> URLs and the tenant-aware
|
|
50
|
-
# /v1/t/<tenant>/<suffix> form
|
|
50
|
+
# /v1/t/<tenant>/<suffix> form.
|
|
51
51
|
ENDPOINT_CHECK = "/check"
|
|
52
52
|
ENDPOINT_APPROVE = "/approve/"
|
|
53
53
|
ENDPOINT_DENY = "/deny/"
|
|
@@ -61,10 +61,10 @@ ENDPOINT_STATUS = "/status/"
|
|
|
61
61
|
LOCAL_TENANT_ID = "local"
|
|
62
62
|
|
|
63
63
|
# Fail-mode values for the Guard() constructor. "deny" fails closed when the
|
|
64
|
-
# AgentGuard proxy is unreachable (the
|
|
65
|
-
#
|
|
66
|
-
#
|
|
67
|
-
#
|
|
64
|
+
# AgentGuard proxy is unreachable (the default). "allow" fails open —
|
|
65
|
+
# permitted as an explicit opt-in for agents whose threat model treats
|
|
66
|
+
# AgentGuard as best-effort; the caller is responsible for any resulting
|
|
67
|
+
# safety implications.
|
|
68
68
|
FAIL_MODE_DENY = "deny"
|
|
69
69
|
FAIL_MODE_ALLOW = "allow"
|
|
70
70
|
_VALID_FAIL_MODES = (FAIL_MODE_DENY, FAIL_MODE_ALLOW)
|
|
@@ -95,16 +95,16 @@ class CheckResult:
|
|
|
95
95
|
# --- Typed exceptions raised by the @guarded decorator ---
|
|
96
96
|
#
|
|
97
97
|
# All three extend PermissionError so callers that already catch
|
|
98
|
-
# PermissionError
|
|
99
|
-
#
|
|
100
|
-
#
|
|
98
|
+
# PermissionError keep working. New callers can catch the specific
|
|
99
|
+
# subclass and read structured fields (result, approval_id,
|
|
100
|
+
# approval_url) instead of parsing error strings.
|
|
101
101
|
|
|
102
102
|
class AgentGuardError(PermissionError):
|
|
103
103
|
"""Base class for AgentGuard-raised permission failures.
|
|
104
104
|
|
|
105
105
|
Carries the originating :class:`CheckResult` (if any) so callers can
|
|
106
106
|
inspect the decision, matched rule, and approval metadata without
|
|
107
|
-
re-running the check. Messages preserve
|
|
107
|
+
re-running the check. Messages preserve a stable string format so
|
|
108
108
|
existing regex/text matchers continue to work.
|
|
109
109
|
"""
|
|
110
110
|
|
|
@@ -156,8 +156,8 @@ class AgentGuardAuthError(AgentGuardError):
|
|
|
156
156
|
``/v1/audit``).
|
|
157
157
|
|
|
158
158
|
Distinguishes "API key wrong / expired" from "approval poll timed
|
|
159
|
-
out" so callers can surface the right operator-facing error.
|
|
160
|
-
|
|
159
|
+
out" so callers can surface the right operator-facing error.
|
|
160
|
+
Extends :class:`AgentGuardError` so existing
|
|
161
161
|
``except PermissionError:`` handlers still catch it.
|
|
162
162
|
"""
|
|
163
163
|
|
|
@@ -171,15 +171,21 @@ class AgentGuardAuthError(AgentGuardError):
|
|
|
171
171
|
self.status = status
|
|
172
172
|
|
|
173
173
|
|
|
174
|
-
# AgentGuardTimeoutError is an alias for AgentGuardApprovalTimeout
|
|
175
|
-
#
|
|
176
|
-
#
|
|
177
|
-
# downstream `except AgentGuardApprovalTimeout:` handlers.
|
|
174
|
+
# AgentGuardTimeoutError is an alias for AgentGuardApprovalTimeout
|
|
175
|
+
# kept for backwards compatibility — `except AgentGuardApprovalTimeout:`
|
|
176
|
+
# handlers still match.
|
|
178
177
|
AgentGuardTimeoutError = AgentGuardApprovalTimeout
|
|
179
178
|
|
|
180
179
|
|
|
181
180
|
class Guard:
|
|
182
|
-
"""Client for the AgentGuard
|
|
181
|
+
"""Client for the AgentGuard server — the SDK enforcement layer.
|
|
182
|
+
|
|
183
|
+
Call ``guard.check(scope, ...)`` before every gated action, or use the
|
|
184
|
+
``@guarded`` decorator / framework adapters in ``agentguard.adapters``
|
|
185
|
+
for higher-level integration. For wire-level enforcement that needs no
|
|
186
|
+
agent code change, see the ``agentguard-mcp-gateway`` and
|
|
187
|
+
``agentguard-llm-proxy`` binaries.
|
|
188
|
+
"""
|
|
183
189
|
|
|
184
190
|
def __init__(
|
|
185
191
|
self,
|
|
@@ -199,26 +205,20 @@ class Guard:
|
|
|
199
205
|
timeout: HTTP timeout in seconds for individual calls.
|
|
200
206
|
api_key: Bearer token for /v1/approve, /v1/deny, /v1/status.
|
|
201
207
|
Falls back to AGENTGUARD_API_KEY.
|
|
202
|
-
fail_mode: Behavior when the proxy is unreachable. "deny"
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
tenant_id: Optional tenant identifier (v0.5+). When set to a
|
|
208
|
+
fail_mode: Behavior when the proxy is unreachable. "deny"
|
|
209
|
+
(the default) returns a DENY result so the agent fails
|
|
210
|
+
closed. "allow" returns an ALLOW result — use only when
|
|
211
|
+
the threat model treats AgentGuard as best-effort and
|
|
212
|
+
the caller accepts the safety trade-off. An invalid
|
|
213
|
+
value raises ValueError at construction.
|
|
214
|
+
tenant_id: Optional tenant identifier. When set to a
|
|
210
215
|
non-empty value other than ``"local"``, every HTTP call is
|
|
211
216
|
routed through the tenant-aware ``/v1/t/{tenant_id}/...``
|
|
212
|
-
URL family instead of the legacy ``/v1/...`` path.
|
|
213
|
-
or ``"local"`` selects the legacy URLs
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
v0.5.0 parity note: the TypeScript SDK already honors `failMode`,
|
|
219
|
-
default `"deny"`. v0.5.0 will align both SDKs on explicit fail-mode
|
|
220
|
-
documentation. Adding `fail_mode` now is purely forward-compatible;
|
|
221
|
-
omitting it preserves v0.4.0 semantics exactly.
|
|
217
|
+
URL family instead of the legacy ``/v1/...`` path.
|
|
218
|
+
``None`` or ``"local"`` selects the legacy URLs. Falls
|
|
219
|
+
back to ``AGENTGUARD_TENANT_ID``. The bundled
|
|
220
|
+
FilePolicyProvider only recognises ``"local"``;
|
|
221
|
+
multi-tenant providers can register others.
|
|
222
222
|
"""
|
|
223
223
|
if fail_mode not in _VALID_FAIL_MODES:
|
|
224
224
|
raise ValueError(
|
|
@@ -314,13 +314,12 @@ class Guard:
|
|
|
314
314
|
|
|
315
315
|
try:
|
|
316
316
|
with request.urlopen(req, timeout=self.timeout) as resp:
|
|
317
|
-
# --- Honest response validation
|
|
318
|
-
#
|
|
319
|
-
#
|
|
320
|
-
#
|
|
321
|
-
#
|
|
322
|
-
#
|
|
323
|
-
# `{` would slip through. We now positively assert:
|
|
317
|
+
# --- Honest response validation ---
|
|
318
|
+
# A misconfigured reverse proxy serving an HTML error
|
|
319
|
+
# page or a chunked plaintext body would silently produce
|
|
320
|
+
# a JSONDecodeError that we *do* catch — but 200-OK plus
|
|
321
|
+
# a non-JSON body that happened to start with `{` would
|
|
322
|
+
# slip through. We positively assert:
|
|
324
323
|
# 1. status in 2xx
|
|
325
324
|
# 2. Content-Type is application/json (charset suffix ok)
|
|
326
325
|
# 3. body is a dict carrying a `decision` field
|
|
@@ -386,9 +385,9 @@ class Guard:
|
|
|
386
385
|
approval_url=body.get("approval_url", ""),
|
|
387
386
|
)
|
|
388
387
|
except (error.URLError, OSError, json.JSONDecodeError) as e:
|
|
389
|
-
# Transport failure. fail_mode picks the
|
|
390
|
-
#
|
|
391
|
-
#
|
|
388
|
+
# Transport failure. fail_mode picks the default: "deny"
|
|
389
|
+
# fails closed; "allow" is an opt-in for callers whose
|
|
390
|
+
# threat model treats AgentGuard as advisory.
|
|
392
391
|
#
|
|
393
392
|
# We catch three classes here:
|
|
394
393
|
# - URLError: urlopen() connection-phase failures (connect
|
|
@@ -411,7 +410,7 @@ class Guard:
|
|
|
411
410
|
|
|
412
411
|
Centralizes the fail-mode dispatch so transport failures and HTTP
|
|
413
412
|
contract violations (bad status, wrong content type, malformed
|
|
414
|
-
body) all flow through one decision point.
|
|
413
|
+
body) all flow through one decision point.
|
|
415
414
|
"""
|
|
416
415
|
decision = DECISION_ALLOW if self.fail_mode == FAIL_MODE_ALLOW else DECISION_DENY
|
|
417
416
|
return CheckResult(decision=decision, reason=reason)
|
|
@@ -459,19 +458,17 @@ class Guard:
|
|
|
459
458
|
Sends the API key on every poll because /v1/status is now auth-gated
|
|
460
459
|
on servers configured with --api-key.
|
|
461
460
|
|
|
462
|
-
|
|
461
|
+
Behavior:
|
|
463
462
|
- Polling is jittered to ``[0.8, 1.2] * poll_interval`` to avoid
|
|
464
463
|
synchronized retries from many clients waiting on the same
|
|
465
464
|
approval bursting the proxy at exact ``poll_interval`` boundaries.
|
|
466
|
-
``time.sleep`` is retained — it is the documented pattern for
|
|
467
|
-
this kind of polling loop and consistent with the v0.4.x SDK.
|
|
468
465
|
- HTTP 401 / 403 from the status endpoint raise
|
|
469
466
|
:class:`AgentGuardAuthError` immediately. Continuing to poll
|
|
470
467
|
would just spin until ``timeout`` elapsed and return a
|
|
471
468
|
synthetic "Approval timed out" DENY, masking the real cause
|
|
472
|
-
(wrong/expired API key). Other HTTPErrors and URLErrors
|
|
473
|
-
|
|
474
|
-
|
|
469
|
+
(wrong/expired API key). Other HTTPErrors and URLErrors are
|
|
470
|
+
swallowed and retried so transient network blips do not abort
|
|
471
|
+
a long-running approval wait.
|
|
475
472
|
"""
|
|
476
473
|
deadline = time.time() + timeout
|
|
477
474
|
while time.time() < deadline:
|
|
@@ -519,9 +516,9 @@ class Guard:
|
|
|
519
516
|
# ``Guard.check``. Anything outside this set is almost always a typo
|
|
520
517
|
# (e.g. ``agent="x"`` instead of ``meta={"agent":"x"}``) — and silently
|
|
521
518
|
# forwarding it to ``Guard.check`` would either be dropped on the floor
|
|
522
|
-
# (``check`` only inspects keyword args it knows about) or raise a
|
|
523
|
-
# TypeError much later in the call chain.
|
|
524
|
-
#
|
|
519
|
+
# (``check`` only inspects keyword args it knows about) or raise a
|
|
520
|
+
# confusing TypeError much later in the call chain. Reject at the
|
|
521
|
+
# decorator boundary so the typo surfaces at definition time.
|
|
525
522
|
_GUARDED_VALID_CHECK_KWARGS = frozenset({
|
|
526
523
|
"action",
|
|
527
524
|
"command",
|
|
@@ -570,17 +567,16 @@ def guarded(
|
|
|
570
567
|
decision: ALLOW runs the function, DENY raises :class:`AgentGuardDenied`,
|
|
571
568
|
a synthetic "Approval timed out" raises :class:`AgentGuardApprovalTimeout`.
|
|
572
569
|
With ``wait_for_approval=False`` (default), the wrapper raises
|
|
573
|
-
:class:`AgentGuardApprovalRequired` immediately
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
All raised exceptions extend :class:`PermissionError`, so existing
|
|
577
|
-
``except PermissionError:`` handlers continue to work unchanged.
|
|
570
|
+
:class:`AgentGuardApprovalRequired` immediately. That class extends
|
|
571
|
+
``PermissionError``, so existing ``except PermissionError:`` handlers
|
|
572
|
+
continue to work unchanged.
|
|
578
573
|
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
574
|
+
Unknown ``**check_kwargs`` raise ``TypeError`` at decoration time.
|
|
575
|
+
Only the keyword arguments :meth:`Guard.check` understands
|
|
576
|
+
(``action``, ``command``, ``path``, ``domain``, ``url``,
|
|
582
577
|
``session_id``, ``est_cost``, ``meta``) are accepted; a typo like
|
|
583
|
-
``agent="x"``
|
|
578
|
+
``agent="x"`` is rejected at definition time rather than silently
|
|
579
|
+
dropped.
|
|
584
580
|
"""
|
|
585
581
|
unknown = set(check_kwargs) - _GUARDED_VALID_CHECK_KWARGS
|
|
586
582
|
if unknown:
|
|
@@ -623,7 +619,8 @@ def guarded(
|
|
|
623
619
|
result=resolved,
|
|
624
620
|
)
|
|
625
621
|
raise AgentGuardApprovalRequired(
|
|
626
|
-
#
|
|
622
|
+
# Stable message text — text-matchers in caller code
|
|
623
|
+
# depend on it.
|
|
627
624
|
f"Action requires approval. Approve at: {result.approval_url}",
|
|
628
625
|
result=result,
|
|
629
626
|
approval_id=result.approval_id,
|
|
@@ -34,9 +34,10 @@ Design notes
|
|
|
34
34
|
the methods listed in :data:`_GATED_METHODS` plus a small allowlist of
|
|
35
35
|
read-only properties via :data:`_ALLOWED_PASSTHROUGH`.
|
|
36
36
|
- **Default deny on attribute access.** Anything not on the allowlist
|
|
37
|
-
raises ``AttributeError`` with a security explanation.
|
|
38
|
-
|
|
39
|
-
``self._page.click(...)
|
|
37
|
+
raises ``AttributeError`` with a security explanation. A permissive
|
|
38
|
+
``__getattr__`` would silently fall through to
|
|
39
|
+
``self._page.click(...)`` and leave every action method un-gated, so
|
|
40
|
+
the wrapper never adds one.
|
|
40
41
|
- **Form values are redacted before transmission.** ``check_form_input``
|
|
41
42
|
passes the field NAME through (operators need it for audit context)
|
|
42
43
|
but rewrites long values to ``<redacted; len=N>`` so audit logs do not
|
|
@@ -44,9 +45,8 @@ Design notes
|
|
|
44
45
|
through :func:`agentguard.adapters.mcp._redact` for the standard secret
|
|
45
46
|
patterns (Bearer tokens, AWS keys, ``secret=...`` pairs, etc.).
|
|
46
47
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
pkg/policy/engine_data_test.go for the engine-side close).
|
|
48
|
+
The data-scope contract is pinned by pkg/policy/engine_data_test.go
|
|
49
|
+
on the engine side.
|
|
50
50
|
"""
|
|
51
51
|
|
|
52
52
|
from typing import Any, Optional
|
|
@@ -215,11 +215,10 @@ class GuardedPage:
|
|
|
215
215
|
|
|
216
216
|
Gated methods are explicit attributes on this class (see the bodies
|
|
217
217
|
below). Anything else is rejected via ``__getattr__`` against
|
|
218
|
-
:data:`_ALLOWED_PASSTHROUGH`.
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
raw Page without ever consulting the policy.
|
|
218
|
+
:data:`_ALLOWED_PASSTHROUGH`. A permissive ``__getattr__`` that
|
|
219
|
+
proxied every attribute access would let ``page.click(...)``,
|
|
220
|
+
``page.fill(...)``, ``page.evaluate(...)`` flow through to the raw
|
|
221
|
+
Page without ever consulting the policy — so we never add one.
|
|
223
222
|
|
|
224
223
|
Read-only properties listed in :data:`_ALLOWED_PASSTHROUGH` (url,
|
|
225
224
|
title, content, viewport_size, is_closed, main_frame, context) are
|
|
@@ -529,10 +528,10 @@ class GuardedPage:
|
|
|
529
528
|
|
|
530
529
|
Anything else raises ``AttributeError`` with a security
|
|
531
530
|
explanation. The default-deny posture is intentional: silently
|
|
532
|
-
proxying unknown attributes to ``self._page``
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
531
|
+
proxying unknown attributes to ``self._page`` would let modern
|
|
532
|
+
Playwright APIs (route, expose_function, ...) skip every gate.
|
|
533
|
+
Adding a new method to the gated surface is preferable to
|
|
534
|
+
widening the allowlist.
|
|
536
535
|
"""
|
|
537
536
|
# Avoid infinite recursion when our own internals are accessed
|
|
538
537
|
# before __init__ completes.
|