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.
Files changed (35) hide show
  1. {agentguardproxy-0.5.0 → agentguardproxy-0.5.2}/PKG-INFO +2 -3
  2. {agentguardproxy-0.5.0 → agentguardproxy-0.5.2}/agentguard/__init__.py +61 -64
  3. {agentguardproxy-0.5.0 → agentguardproxy-0.5.2}/agentguard/adapters/browseruse.py +14 -15
  4. agentguardproxy-0.5.2/agentguard/adapters/crewai.py +610 -0
  5. agentguardproxy-0.5.2/agentguard/adapters/langchain.py +619 -0
  6. {agentguardproxy-0.5.0 → agentguardproxy-0.5.2}/agentguard/adapters/mcp.py +34 -36
  7. {agentguardproxy-0.5.0 → agentguardproxy-0.5.2}/agentguardproxy.egg-info/PKG-INFO +2 -3
  8. {agentguardproxy-0.5.0 → agentguardproxy-0.5.2}/pyproject.toml +8 -9
  9. {agentguardproxy-0.5.0 → agentguardproxy-0.5.2}/tests/test_adapters_extended.py +11 -12
  10. {agentguardproxy-0.5.0 → agentguardproxy-0.5.2}/tests/test_crewai.py +128 -78
  11. {agentguardproxy-0.5.0 → agentguardproxy-0.5.2}/tests/test_guard.py +1 -1
  12. {agentguardproxy-0.5.0 → agentguardproxy-0.5.2}/tests/test_langchain.py +96 -25
  13. agentguardproxy-0.5.0/agentguard/adapters/crewai.py +0 -596
  14. agentguardproxy-0.5.0/agentguard/adapters/langchain.py +0 -470
  15. {agentguardproxy-0.5.0 → agentguardproxy-0.5.2}/README.md +0 -0
  16. {agentguardproxy-0.5.0 → agentguardproxy-0.5.2}/agentguard/adapters/__init__.py +0 -0
  17. {agentguardproxy-0.5.0 → agentguardproxy-0.5.2}/agentguardproxy.egg-info/SOURCES.txt +0 -0
  18. {agentguardproxy-0.5.0 → agentguardproxy-0.5.2}/agentguardproxy.egg-info/dependency_links.txt +0 -0
  19. {agentguardproxy-0.5.0 → agentguardproxy-0.5.2}/agentguardproxy.egg-info/requires.txt +0 -0
  20. {agentguardproxy-0.5.0 → agentguardproxy-0.5.2}/agentguardproxy.egg-info/top_level.txt +0 -0
  21. {agentguardproxy-0.5.0 → agentguardproxy-0.5.2}/setup.cfg +0 -0
  22. {agentguardproxy-0.5.0 → agentguardproxy-0.5.2}/tests/test_adapters.py +0 -0
  23. {agentguardproxy-0.5.0 → agentguardproxy-0.5.2}/tests/test_browseruse.py +0 -0
  24. {agentguardproxy-0.5.0 → agentguardproxy-0.5.2}/tests/test_decorator.py +0 -0
  25. {agentguardproxy-0.5.0 → agentguardproxy-0.5.2}/tests/test_end_to_end_real_server.py +0 -0
  26. {agentguardproxy-0.5.0 → agentguardproxy-0.5.2}/tests/test_integration.py +0 -0
  27. {agentguardproxy-0.5.0 → agentguardproxy-0.5.2}/tests/test_mcp.py +0 -0
  28. {agentguardproxy-0.5.0 → agentguardproxy-0.5.2}/tests/test_mcp_fuzz.py +0 -0
  29. {agentguardproxy-0.5.0 → agentguardproxy-0.5.2}/tests/test_mcp_gateway.py +0 -0
  30. {agentguardproxy-0.5.0 → agentguardproxy-0.5.2}/tests/test_polling_jitter.py +0 -0
  31. {agentguardproxy-0.5.0 → agentguardproxy-0.5.2}/tests/test_redactor_property.py +0 -0
  32. {agentguardproxy-0.5.0 → agentguardproxy-0.5.2}/tests/test_sdk_integration.py +0 -0
  33. {agentguardproxy-0.5.0 → agentguardproxy-0.5.2}/tests/test_sdk_polish.py +0 -0
  34. {agentguardproxy-0.5.0 → agentguardproxy-0.5.2}/tests/test_tenant_routing.py +0 -0
  35. {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.0
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.9
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 introduced in v0.5 (worker A7).
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 v0.4.0 default and current v0.4.1
65
- # default). "allow" fails open — permitted as an explicit opt-in for agents
66
- # whose threat model treats AgentGuard as best-effort; the caller is
67
- # responsible for any resulting safety implications.
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 (the v0.4.0/v0.4.1 behavior) keep working unchanged.
99
- # New callers can catch the specific subclass and read structured fields
100
- # (result, approval_id, approval_url) instead of parsing error strings.
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 the v0.4.1 string format so
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. v0.5
160
- addition (R5 P9). Extends :class:`AgentGuardError` so existing
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 — the
175
- # v0.5 plan referred to it by that name, but the v0.4.1 SDK already shipped
176
- # AgentGuardApprovalTimeout. The alias gives the new name without breaking
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 proxy."""
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" (the
203
- v0.4.0 default, current v0.4.1 default) returns a DENY
204
- result so the agent fails closed. "allow" returns an ALLOW
205
- result — use only when the threat model treats AgentGuard
206
- as best-effort and the caller accepts the safety trade-off.
207
- An invalid value raises ValueError at construction so the
208
- bug surfaces at startup instead of mid-request.
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. ``None``
213
- or ``"local"`` selects the legacy URLs and is wire-compatible
214
- with v0.4.x servers. Falls back to ``AGENTGUARD_TENANT_ID``.
215
- v0.5 servers only recognise the literal ``"local"`` tenant;
216
- v0.6 will validate against a configured tenant registry.
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 (R5 E8 / S13) ---
318
- # The previous implementation decoded whatever the proxy
319
- # returned. A misconfigured reverse proxy serving an HTML
320
- # error page or a chunked plaintext body would silently
321
- # produce a JSONDecodeError that we *did* catch but
322
- # 200-OK plus a non-JSON body that happened to start with
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 safe default: "deny"
390
- # preserves v0.4.0 fail-closed semantics; "allow" is an opt-in
391
- # for callers whose threat model treats AgentGuard as advisory.
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. v0.5 addition (R5 E8).
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
- v0.5 changes (R5 E14, P9):
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 keep
473
- the v0.4.x behavior of swallowing-and-retrying so transient
474
- network blips do not abort a long-running approval wait.
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 confusing
523
- # TypeError much later in the call chain. v0.5 R5 E15 closes this footgun
524
- # at the decorator boundary.
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 preserving v0.4.1
574
- behavior exactly, because that class extends ``PermissionError``.
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
- v0.5 (R5 E15): unknown ``**check_kwargs`` raise ``TypeError`` at
580
- decoration time. Only the keyword arguments :meth:`Guard.check`
581
- understands (``action``, ``command``, ``path``, ``domain``, ``url``,
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"`` no longer silently disappears.
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
- # Preserve v0.4.1 message text so text-matchers still hit.
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. This catches the
38
- v0.4.x bypass where ``__getattr__`` silently fell through to
39
- ``self._page.click(...)``, leaving every action method un-gated.
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
- Closes audit findings R5 E4 (modern-API bypass via __getattr__),
48
- R5 E5 / R7 E3 (data scope unhandled by the engine — see
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`. This is deliberately stricter than the
219
- v0.4.x adapter, which proxied every attribute access that proxying
220
- was the bypass closed by audit finding R5 E4: ``page.click(...)``,
221
- ``page.fill(...)``, ``page.evaluate(...)`` all flowed through to the
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`` was the v0.4.x
533
- bypass that let modern Playwright APIs (route, expose_function,
534
- ...) skip every gate. Adding a new method to the gated surface
535
- is preferable to widening the allowlist.
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.