agentguardproxy 0.9.0__tar.gz → 1.0.0__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 (37) hide show
  1. {agentguardproxy-0.9.0 → agentguardproxy-1.0.0}/PKG-INFO +1 -1
  2. {agentguardproxy-0.9.0 → agentguardproxy-1.0.0}/agentguard/adapters/_common.py +31 -5
  3. {agentguardproxy-0.9.0 → agentguardproxy-1.0.0}/agentguard/adapters/mcp.py +1 -1
  4. {agentguardproxy-0.9.0 → agentguardproxy-1.0.0}/agentguardproxy.egg-info/PKG-INFO +1 -1
  5. {agentguardproxy-0.9.0 → agentguardproxy-1.0.0}/agentguardproxy.egg-info/SOURCES.txt +1 -0
  6. {agentguardproxy-0.9.0 → agentguardproxy-1.0.0}/pyproject.toml +1 -1
  7. agentguardproxy-1.0.0/tests/test_action_parity.py +113 -0
  8. {agentguardproxy-0.9.0 → agentguardproxy-1.0.0}/README.md +0 -0
  9. {agentguardproxy-0.9.0 → agentguardproxy-1.0.0}/agentguard/__init__.py +0 -0
  10. {agentguardproxy-0.9.0 → agentguardproxy-1.0.0}/agentguard/adapters/__init__.py +0 -0
  11. {agentguardproxy-0.9.0 → agentguardproxy-1.0.0}/agentguard/adapters/browseruse.py +0 -0
  12. {agentguardproxy-0.9.0 → agentguardproxy-1.0.0}/agentguard/adapters/crewai.py +0 -0
  13. {agentguardproxy-0.9.0 → agentguardproxy-1.0.0}/agentguard/adapters/langchain.py +0 -0
  14. {agentguardproxy-0.9.0 → agentguardproxy-1.0.0}/agentguard/core.py +0 -0
  15. {agentguardproxy-0.9.0 → agentguardproxy-1.0.0}/agentguard/decorators.py +0 -0
  16. {agentguardproxy-0.9.0 → agentguardproxy-1.0.0}/agentguardproxy.egg-info/dependency_links.txt +0 -0
  17. {agentguardproxy-0.9.0 → agentguardproxy-1.0.0}/agentguardproxy.egg-info/requires.txt +0 -0
  18. {agentguardproxy-0.9.0 → agentguardproxy-1.0.0}/agentguardproxy.egg-info/top_level.txt +0 -0
  19. {agentguardproxy-0.9.0 → agentguardproxy-1.0.0}/setup.cfg +0 -0
  20. {agentguardproxy-0.9.0 → agentguardproxy-1.0.0}/tests/test_adapters.py +0 -0
  21. {agentguardproxy-0.9.0 → agentguardproxy-1.0.0}/tests/test_adapters_extended.py +0 -0
  22. {agentguardproxy-0.9.0 → agentguardproxy-1.0.0}/tests/test_browseruse.py +0 -0
  23. {agentguardproxy-0.9.0 → agentguardproxy-1.0.0}/tests/test_crewai.py +0 -0
  24. {agentguardproxy-0.9.0 → agentguardproxy-1.0.0}/tests/test_decorator.py +0 -0
  25. {agentguardproxy-0.9.0 → agentguardproxy-1.0.0}/tests/test_end_to_end_real_server.py +0 -0
  26. {agentguardproxy-0.9.0 → agentguardproxy-1.0.0}/tests/test_guard.py +0 -0
  27. {agentguardproxy-0.9.0 → agentguardproxy-1.0.0}/tests/test_integration.py +0 -0
  28. {agentguardproxy-0.9.0 → agentguardproxy-1.0.0}/tests/test_langchain.py +0 -0
  29. {agentguardproxy-0.9.0 → agentguardproxy-1.0.0}/tests/test_mcp.py +0 -0
  30. {agentguardproxy-0.9.0 → agentguardproxy-1.0.0}/tests/test_mcp_fuzz.py +0 -0
  31. {agentguardproxy-0.9.0 → agentguardproxy-1.0.0}/tests/test_mcp_gateway.py +0 -0
  32. {agentguardproxy-0.9.0 → agentguardproxy-1.0.0}/tests/test_polling_jitter.py +0 -0
  33. {agentguardproxy-0.9.0 → agentguardproxy-1.0.0}/tests/test_redactor_property.py +0 -0
  34. {agentguardproxy-0.9.0 → agentguardproxy-1.0.0}/tests/test_sdk_integration.py +0 -0
  35. {agentguardproxy-0.9.0 → agentguardproxy-1.0.0}/tests/test_sdk_polish.py +0 -0
  36. {agentguardproxy-0.9.0 → agentguardproxy-1.0.0}/tests/test_tenant_routing.py +0 -0
  37. {agentguardproxy-0.9.0 → agentguardproxy-1.0.0}/tests/test_wire_format.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agentguardproxy
3
- Version: 0.9.0
3
+ Version: 1.0.0
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
@@ -21,19 +21,45 @@ def domain_from_url(url: Any) -> str:
21
21
  return ""
22
22
 
23
23
 
24
+ # Filesystem-action verb groups. These MUST stay byte-identical to the Go
25
+ # transports' gateclient.InferFilesystemAction (pkg/internal/gateclient/
26
+ # gateclient.go): same verbs, same groups, same precedence (read, then
27
+ # write, then delete). Parity is pinned by two mirrored test tables —
28
+ # plugins/python/tests/test_action_parity.py and
29
+ # pkg/internal/gateclient/gateclient_test.go. Edit both sides together.
30
+ _READ_PREFIXES = ("read", "list", "get", "stat", "cat", "find", "glob")
31
+ _WRITE_PREFIXES = ("write", "edit", "create", "append", "save", "copy", "move")
32
+ _DELETE_PREFIXES = ("delete", "remove", "unlink", "rm")
33
+
34
+
24
35
  def infer_path_action(tool_name: Any) -> str:
25
36
  """Map a tool-name verb to the canonical filesystem action
26
- ("read"/"write"/"delete"), or "" when no verb matches. Mirrors the
27
- Go side's gateclient.InferFilesystemAction verb groups.
37
+ ("read"/"write"/"delete"), or "" when no verb matches.
38
+
39
+ Mirrors the Go transports' gateclient.InferFilesystemAction exactly:
40
+ a case-insensitive ``strings.HasPrefix`` (Python ``str.startswith``)
41
+ against these verb groups, checked in this order —
42
+
43
+ read = read, list, get, stat, cat, find, glob
44
+ write = write, edit, create, append, save, copy, move
45
+ delete = delete, remove, unlink, rm
46
+
47
+ Prefix (not substring) matching is deliberate: it avoids the false
48
+ positives substring matching produces that Go never makes — e.g.
49
+ "set_target" contains "get" but does not START with it, and
50
+ "undelete_x" contains "delete" but does not start with it. Both
51
+ correctly yield "" here, matching Go. Parity is pinned by
52
+ plugins/python/tests/test_action_parity.py, whose table mirrors
53
+ pkg/internal/gateclient/gateclient_test.go.
28
54
  """
29
55
  if not isinstance(tool_name, str):
30
56
  return ""
31
57
  name_lower = tool_name.lower()
32
- if "read" in name_lower or "get" in name_lower:
58
+ if name_lower.startswith(_READ_PREFIXES):
33
59
  return "read"
34
- if "write" in name_lower or "save" in name_lower or "create" in name_lower:
60
+ if name_lower.startswith(_WRITE_PREFIXES):
35
61
  return "write"
36
- if "delete" in name_lower or "remove" in name_lower:
62
+ if name_lower.startswith(_DELETE_PREFIXES):
37
63
  return "delete"
38
64
  return ""
39
65
 
@@ -41,7 +41,7 @@ from agentguard.adapters._common import extract_check_params
41
41
 
42
42
  # MCP protocol constants
43
43
  MCP_PROTOCOL_VERSION = "2024-11-05"
44
- SDK_VERSION = "0.9.0"
44
+ SDK_VERSION = "1.0.0"
45
45
 
46
46
  # Secret patterns mirrored from pkg/notify/notify.go's DefaultRedactor. The
47
47
  # MCP adapter forwards handler exception text back to the client as a
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agentguardproxy
3
- Version: 0.9.0
3
+ Version: 1.0.0
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
@@ -14,6 +14,7 @@ agentguardproxy.egg-info/SOURCES.txt
14
14
  agentguardproxy.egg-info/dependency_links.txt
15
15
  agentguardproxy.egg-info/requires.txt
16
16
  agentguardproxy.egg-info/top_level.txt
17
+ tests/test_action_parity.py
17
18
  tests/test_adapters.py
18
19
  tests/test_adapters_extended.py
19
20
  tests/test_browseruse.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "agentguardproxy"
7
- version = "0.9.0"
7
+ version = "1.0.0"
8
8
  description = "Python SDK for AgentGuard — the firewall for AI agents"
9
9
  readme = "README.md"
10
10
  license = "Apache-2.0"
@@ -0,0 +1,113 @@
1
+ """Filesystem-action inference parity: Python SDK <-> Go transports.
2
+
3
+ The Python adapters (infer_path_action in agentguard.adapters._common) and
4
+ the Go transports (gateclient.InferFilesystemAction) must map a tool name to
5
+ the SAME filesystem `action` ("read"/"write"/"delete"/""), otherwise an
6
+ `action:`-keyed filesystem policy rule fires on one transport and not the
7
+ other for the same tool call.
8
+
9
+ The table below is the SAME (tool_name -> action) table pinned on the Go
10
+ side in pkg/internal/gateclient/gateclient_test.go (TestInferFilesystemAction).
11
+ When you add or change a verb in one place, change it in the other and update
12
+ BOTH tables — this test and that Go test are the tripwire.
13
+
14
+ This module imports ONLY agentguard.adapters._common, so it runs without
15
+ langchain / crewai / mcp (or any framework dependency) installed.
16
+ """
17
+
18
+ from agentguard.adapters._common import infer_path_action
19
+
20
+
21
+ # Mirror of gateclient_test.go's TestInferFilesystemAction table. Keep in sync.
22
+ GO_PARITY_TABLE = {
23
+ "read_file": "read",
24
+ "list_files": "read",
25
+ "get_file": "read",
26
+ "stat_file": "read",
27
+ "cat": "read",
28
+ "find_files": "read",
29
+ "glob": "read",
30
+ "write_file": "write",
31
+ "edit_file": "write",
32
+ "create_dir": "write",
33
+ "append_file": "write",
34
+ "delete_file": "delete",
35
+ "remove_dir": "delete",
36
+ "unlink": "delete",
37
+ "unknown": "",
38
+ }
39
+
40
+ # Extra regression cases for verbs the substring implementation used to miss
41
+ # (edit/append/copy/move/unlink/rm*/list/stat/cat/find/glob). These are the
42
+ # tool names that previously sent NO action through the Python SDK while the
43
+ # Go transports DID send one.
44
+ REGRESSION_TABLE = {
45
+ "edit_file": "write",
46
+ "append_file": "write",
47
+ "copy_file": "write",
48
+ "move_file": "write",
49
+ "unlink": "delete",
50
+ "rmdir": "delete", # "rm" prefix
51
+ "rm": "delete",
52
+ "list_files": "read",
53
+ "stat_file": "read",
54
+ "cat": "read",
55
+ "find_files": "read",
56
+ "glob": "read",
57
+ }
58
+
59
+ # False-positive guards: substring matching (the old behavior) wrongly mapped
60
+ # these; prefix matching (Go's HasPrefix, our str.startswith) does not.
61
+ FALSE_POSITIVE_TABLE = {
62
+ "set_target": "", # contains "get" but does not START with it
63
+ "undelete_x": "", # contains "delete" but does not START with it
64
+ "forget": "", # contains "get"
65
+ "overwrite": "", # contains "write"
66
+ }
67
+
68
+
69
+ def test_go_parity_table():
70
+ """Every pinned Go case maps identically in Python."""
71
+ for tool_name, expected in GO_PARITY_TABLE.items():
72
+ assert infer_path_action(tool_name) == expected, (
73
+ f"infer_path_action({tool_name!r}) diverged from Go parity table"
74
+ )
75
+
76
+
77
+ def test_regression_cases_previously_missed_by_substring():
78
+ for tool_name, expected in REGRESSION_TABLE.items():
79
+ assert infer_path_action(tool_name) == expected, (
80
+ f"infer_path_action({tool_name!r}) regressed"
81
+ )
82
+
83
+
84
+ def test_false_positive_guards():
85
+ for tool_name, expected in FALSE_POSITIVE_TABLE.items():
86
+ assert infer_path_action(tool_name) == expected, (
87
+ f"infer_path_action({tool_name!r}) should be {expected!r} "
88
+ f"(prefix, not substring, matching)"
89
+ )
90
+
91
+
92
+ def test_non_string_input_is_empty():
93
+ for bad in (None, 123, ["read_file"], {"name": "read_file"}, object()):
94
+ assert infer_path_action(bad) == ""
95
+
96
+
97
+ def test_case_insensitive():
98
+ # HasPrefix is applied to strings.ToLower(toolName) on the Go side; we
99
+ # lowercase too, so mixed case still resolves.
100
+ assert infer_path_action("READ_File") == "read"
101
+ assert infer_path_action("Write_Thing") == "write"
102
+ assert infer_path_action("DELETE_it") == "delete"
103
+
104
+
105
+ if __name__ == "__main__":
106
+ # Allow a framework-free smoke run without pytest installed.
107
+ for table in (GO_PARITY_TABLE, REGRESSION_TABLE, FALSE_POSITIVE_TABLE):
108
+ for name, want in table.items():
109
+ got = infer_path_action(name)
110
+ assert got == want, f"{name!r}: got {got!r}, want {want!r}"
111
+ for bad in (None, 123, ["x"], {"a": 1}):
112
+ assert infer_path_action(bad) == ""
113
+ print("action-parity smoke OK")