agentguardproxy 0.2.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.
@@ -0,0 +1,142 @@
1
+ Metadata-Version: 2.4
2
+ Name: agentguardproxy
3
+ Version: 0.2.0
4
+ Summary: Python SDK for AgentGuard — the firewall for AI agents
5
+ Author: AgentGuard Contributors
6
+ License-Expression: Apache-2.0
7
+ Project-URL: Homepage, https://github.com/Caua-ferraz/AgentGuard
8
+ Project-URL: Repository, https://github.com/Caua-ferraz/AgentGuard
9
+ Project-URL: Documentation, https://github.com/Caua-ferraz/AgentGuard/blob/master/docs/SETUP.md
10
+ Project-URL: Issues, https://github.com/Caua-ferraz/AgentGuard/issues
11
+ Keywords: ai,agents,firewall,policy,guardrails,safety
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.8
16
+ Classifier: Programming Language :: Python :: 3.9
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Topic :: Security
22
+ Classifier: Topic :: Software Development :: Libraries
23
+ Requires-Python: >=3.8
24
+ Description-Content-Type: text/markdown
25
+ Provides-Extra: langchain
26
+ Requires-Dist: langchain>=0.1.0; extra == "langchain"
27
+ Provides-Extra: crewai
28
+ Requires-Dist: crewai>=0.1.0; extra == "crewai"
29
+ Provides-Extra: browser-use
30
+ Requires-Dist: browser-use>=0.1.0; extra == "browser-use"
31
+ Provides-Extra: mcp
32
+ Provides-Extra: all
33
+ Requires-Dist: langchain>=0.1.0; extra == "all"
34
+ Requires-Dist: crewai>=0.1.0; extra == "all"
35
+ Requires-Dist: browser-use>=0.1.0; extra == "all"
36
+
37
+ # AgentGuard Python SDK
38
+
39
+ Lightweight Python client for [AgentGuard](https://github.com/Caua-ferraz/AgentGuard) — the firewall for AI agents.
40
+
41
+ ## Install
42
+
43
+ ```bash
44
+ pip install agentguard
45
+
46
+ # With framework adapters
47
+ pip install agentguard[langchain]
48
+ pip install agentguard[crewai]
49
+ pip install agentguard[browser-use]
50
+ pip install agentguard[all]
51
+ ```
52
+
53
+ ## Quick Start
54
+
55
+ ```python
56
+ from agentguard import Guard
57
+
58
+ guard = Guard("http://localhost:8080", agent_id="my-agent")
59
+
60
+ # Check before executing
61
+ result = guard.check("shell", command="rm -rf ./old_data")
62
+
63
+ if result.allowed:
64
+ execute(command)
65
+ elif result.needs_approval:
66
+ print(f"Approve at: {result.approval_url}")
67
+ else:
68
+ print(f"Blocked: {result.reason}")
69
+ ```
70
+
71
+ ## Framework Adapters
72
+
73
+ ### LangChain
74
+
75
+ ```python
76
+ from agentguard.adapters.langchain import GuardedToolkit
77
+
78
+ toolkit = GuardedToolkit(
79
+ tools=my_tools,
80
+ guard_url="http://localhost:8080",
81
+ agent_id="langchain-agent",
82
+ )
83
+
84
+ agent = create_react_agent(llm, toolkit.tools, prompt)
85
+ ```
86
+
87
+ ### CrewAI
88
+
89
+ ```python
90
+ from agentguard.adapters.crewai import guard_crew_tools
91
+
92
+ guarded_tools = guard_crew_tools(
93
+ tools=my_crew_tools,
94
+ guard_url="http://localhost:8080",
95
+ agent_id="crew-agent",
96
+ )
97
+ ```
98
+
99
+ ### browser-use
100
+
101
+ ```python
102
+ from agentguard.adapters.browseruse import GuardedBrowser
103
+
104
+ browser = GuardedBrowser(guard_url="http://localhost:8080")
105
+
106
+ result = browser.check_navigation("https://example.com")
107
+ if result.allowed:
108
+ await page.goto("https://example.com")
109
+ ```
110
+
111
+ ### MCP
112
+
113
+ ```python
114
+ from agentguard.adapters.mcp import GuardedMCPServer
115
+
116
+ server = GuardedMCPServer(guard_url="http://localhost:8080")
117
+ server.add_tool("my_tool", "Description", handler=my_handler)
118
+ server.run() # Starts stdio MCP server
119
+ ```
120
+
121
+ ## API Reference
122
+
123
+ ### `Guard(base_url, agent_id="")`
124
+ - `check(scope, *, action, command, path, domain, url, meta)` — Check an action against policy
125
+ - `approve(approval_id)` — Approve a pending action
126
+ - `deny(approval_id)` — Deny a pending action
127
+ - `wait_for_approval(approval_id, timeout=300)` — Block until resolved
128
+
129
+ ### `CheckResult`
130
+ - `.allowed` — True if action is permitted
131
+ - `.denied` — True if action is blocked
132
+ - `.needs_approval` — True if human approval required
133
+ - `.decision` — Raw decision string
134
+ - `.reason` — Explanation
135
+ - `.approval_url` — URL to approve (when applicable)
136
+
137
+ ### `@guarded(scope, guard=None)` decorator
138
+ Wraps a function so it's checked before execution.
139
+
140
+ ## License
141
+
142
+ Apache 2.0
@@ -0,0 +1,106 @@
1
+ # AgentGuard Python SDK
2
+
3
+ Lightweight Python client for [AgentGuard](https://github.com/Caua-ferraz/AgentGuard) — the firewall for AI agents.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ pip install agentguard
9
+
10
+ # With framework adapters
11
+ pip install agentguard[langchain]
12
+ pip install agentguard[crewai]
13
+ pip install agentguard[browser-use]
14
+ pip install agentguard[all]
15
+ ```
16
+
17
+ ## Quick Start
18
+
19
+ ```python
20
+ from agentguard import Guard
21
+
22
+ guard = Guard("http://localhost:8080", agent_id="my-agent")
23
+
24
+ # Check before executing
25
+ result = guard.check("shell", command="rm -rf ./old_data")
26
+
27
+ if result.allowed:
28
+ execute(command)
29
+ elif result.needs_approval:
30
+ print(f"Approve at: {result.approval_url}")
31
+ else:
32
+ print(f"Blocked: {result.reason}")
33
+ ```
34
+
35
+ ## Framework Adapters
36
+
37
+ ### LangChain
38
+
39
+ ```python
40
+ from agentguard.adapters.langchain import GuardedToolkit
41
+
42
+ toolkit = GuardedToolkit(
43
+ tools=my_tools,
44
+ guard_url="http://localhost:8080",
45
+ agent_id="langchain-agent",
46
+ )
47
+
48
+ agent = create_react_agent(llm, toolkit.tools, prompt)
49
+ ```
50
+
51
+ ### CrewAI
52
+
53
+ ```python
54
+ from agentguard.adapters.crewai import guard_crew_tools
55
+
56
+ guarded_tools = guard_crew_tools(
57
+ tools=my_crew_tools,
58
+ guard_url="http://localhost:8080",
59
+ agent_id="crew-agent",
60
+ )
61
+ ```
62
+
63
+ ### browser-use
64
+
65
+ ```python
66
+ from agentguard.adapters.browseruse import GuardedBrowser
67
+
68
+ browser = GuardedBrowser(guard_url="http://localhost:8080")
69
+
70
+ result = browser.check_navigation("https://example.com")
71
+ if result.allowed:
72
+ await page.goto("https://example.com")
73
+ ```
74
+
75
+ ### MCP
76
+
77
+ ```python
78
+ from agentguard.adapters.mcp import GuardedMCPServer
79
+
80
+ server = GuardedMCPServer(guard_url="http://localhost:8080")
81
+ server.add_tool("my_tool", "Description", handler=my_handler)
82
+ server.run() # Starts stdio MCP server
83
+ ```
84
+
85
+ ## API Reference
86
+
87
+ ### `Guard(base_url, agent_id="")`
88
+ - `check(scope, *, action, command, path, domain, url, meta)` — Check an action against policy
89
+ - `approve(approval_id)` — Approve a pending action
90
+ - `deny(approval_id)` — Deny a pending action
91
+ - `wait_for_approval(approval_id, timeout=300)` — Block until resolved
92
+
93
+ ### `CheckResult`
94
+ - `.allowed` — True if action is permitted
95
+ - `.denied` — True if action is blocked
96
+ - `.needs_approval` — True if human approval required
97
+ - `.decision` — Raw decision string
98
+ - `.reason` — Explanation
99
+ - `.approval_url` — URL to approve (when applicable)
100
+
101
+ ### `@guarded(scope, guard=None)` decorator
102
+ Wraps a function so it's checked before execution.
103
+
104
+ ## License
105
+
106
+ Apache 2.0
@@ -0,0 +1,197 @@
1
+ """
2
+ AgentGuard Python SDK
3
+
4
+ Lightweight client for checking actions against AgentGuard policies.
5
+
6
+ Usage:
7
+ from agentguard import Guard
8
+
9
+ guard = Guard("http://localhost:8080")
10
+ result = guard.check("shell", command="rm -rf ./data")
11
+
12
+ if result.allowed:
13
+ execute(command)
14
+ elif result.needs_approval:
15
+ print(f"Approve at: {result.approval_url}")
16
+ else:
17
+ print(f"Blocked: {result.reason}")
18
+ """
19
+
20
+ import json
21
+ import time
22
+ from dataclasses import dataclass, field
23
+ from typing import Optional
24
+ from urllib import request, error
25
+
26
+
27
+ @dataclass
28
+ class CheckResult:
29
+ """Result of a policy check."""
30
+ decision: str
31
+ reason: str
32
+ matched_rule: str = ""
33
+ approval_id: str = ""
34
+ approval_url: str = ""
35
+
36
+ @property
37
+ def allowed(self) -> bool:
38
+ return self.decision == "ALLOW"
39
+
40
+ @property
41
+ def denied(self) -> bool:
42
+ return self.decision == "DENY"
43
+
44
+ @property
45
+ def needs_approval(self) -> bool:
46
+ return self.decision == "REQUIRE_APPROVAL"
47
+
48
+
49
+ class Guard:
50
+ """Client for the AgentGuard proxy."""
51
+
52
+ def __init__(self, base_url: str = "http://localhost:8080", agent_id: str = ""):
53
+ self.base_url = base_url.rstrip("/")
54
+ self.agent_id = agent_id
55
+
56
+ def check(
57
+ self,
58
+ scope: str,
59
+ *,
60
+ action: str = "",
61
+ command: str = "",
62
+ path: str = "",
63
+ domain: str = "",
64
+ url: str = "",
65
+ meta: Optional[dict] = None,
66
+ ) -> CheckResult:
67
+ """Check an action against the policy.
68
+
69
+ Args:
70
+ scope: The rule scope (filesystem, shell, network, browser, cost, data)
71
+ action: Action type (read, write, delete) — used with filesystem scope
72
+ command: Shell command string — used with shell scope
73
+ path: File path — used with filesystem scope
74
+ domain: Target domain — used with network/browser scope
75
+ url: Full URL — used with network scope
76
+ meta: Additional metadata
77
+
78
+ Returns:
79
+ CheckResult with the policy decision
80
+ """
81
+ payload = {
82
+ "scope": scope,
83
+ "agent_id": self.agent_id,
84
+ }
85
+ if action:
86
+ payload["action"] = action
87
+ if command:
88
+ payload["command"] = command
89
+ if path:
90
+ payload["path"] = path
91
+ if domain:
92
+ payload["domain"] = domain
93
+ if url:
94
+ payload["url"] = url
95
+ if meta:
96
+ payload["meta"] = meta
97
+
98
+ data = json.dumps(payload).encode("utf-8")
99
+ req = request.Request(
100
+ f"{self.base_url}/v1/check",
101
+ data=data,
102
+ headers={"Content-Type": "application/json"},
103
+ method="POST",
104
+ )
105
+
106
+ try:
107
+ with request.urlopen(req, timeout=5) as resp:
108
+ body = json.loads(resp.read())
109
+ return CheckResult(
110
+ decision=body.get("decision", "DENY"),
111
+ reason=body.get("reason", ""),
112
+ matched_rule=body.get("matched_rule", ""),
113
+ approval_id=body.get("approval_id", ""),
114
+ approval_url=body.get("approval_url", ""),
115
+ )
116
+ except error.URLError as e:
117
+ # If AgentGuard is unreachable, default to deny (fail closed)
118
+ return CheckResult(
119
+ decision="DENY",
120
+ reason=f"AgentGuard unreachable: {e}",
121
+ )
122
+
123
+ def approve(self, approval_id: str) -> bool:
124
+ """Approve a pending action."""
125
+ req = request.Request(
126
+ f"{self.base_url}/v1/approve/{approval_id}",
127
+ method="POST",
128
+ )
129
+ try:
130
+ with request.urlopen(req, timeout=5):
131
+ return True
132
+ except error.URLError:
133
+ return False
134
+
135
+ def deny(self, approval_id: str) -> bool:
136
+ """Deny a pending action."""
137
+ req = request.Request(
138
+ f"{self.base_url}/v1/deny/{approval_id}",
139
+ method="POST",
140
+ )
141
+ try:
142
+ with request.urlopen(req, timeout=5):
143
+ return True
144
+ except error.URLError:
145
+ return False
146
+
147
+ def wait_for_approval(self, approval_id: str, timeout: int = 300, poll_interval: int = 2) -> CheckResult:
148
+ """Block until a pending action is approved or denied (or timeout)."""
149
+ deadline = time.time() + timeout
150
+ while time.time() < deadline:
151
+ # Poll the status endpoint for resolution
152
+ req = request.Request(
153
+ f"{self.base_url}/v1/status/{approval_id}",
154
+ method="GET",
155
+ )
156
+ try:
157
+ with request.urlopen(req, timeout=5) as resp:
158
+ body = json.loads(resp.read())
159
+ if body.get("status") == "resolved" and body.get("decision") in ("ALLOW", "DENY"):
160
+ return CheckResult(
161
+ decision=body["decision"],
162
+ reason=body.get("reason", "resolved"),
163
+ )
164
+ except error.URLError:
165
+ pass
166
+ time.sleep(poll_interval)
167
+
168
+ return CheckResult(decision="DENY", reason="Approval timed out")
169
+
170
+
171
+ # Convenience decorator for guarding functions
172
+ def guarded(scope: str, guard: Optional[Guard] = None, **check_kwargs):
173
+ """Decorator that checks policy before executing a function.
174
+
175
+ Usage:
176
+ guard = Guard("http://localhost:8080")
177
+
178
+ @guarded("shell", guard=guard)
179
+ def run_command(cmd: str):
180
+ os.system(cmd)
181
+ """
182
+ def decorator(func):
183
+ def wrapper(*args, **kwargs):
184
+ g = guard or Guard()
185
+ # Try to extract meaningful info from args
186
+ cmd = args[0] if args else kwargs.get("command", kwargs.get("cmd", ""))
187
+ result = g.check(scope, command=str(cmd), **check_kwargs)
188
+ if result.allowed:
189
+ return func(*args, **kwargs)
190
+ elif result.needs_approval:
191
+ raise PermissionError(
192
+ f"Action requires approval. Approve at: {result.approval_url}"
193
+ )
194
+ else:
195
+ raise PermissionError(f"Action denied by AgentGuard: {result.reason}")
196
+ return wrapper
197
+ return decorator
@@ -0,0 +1,8 @@
1
+ """AgentGuard framework adapters.
2
+
3
+ Available adapters:
4
+ - langchain: Wraps LangChain tools with policy enforcement
5
+ - crewai: Wraps CrewAI tools with policy enforcement
6
+ - browseruse: Wraps browser-use actions with policy enforcement
7
+ - mcp: MCP-compatible server that enforces policies on tool calls
8
+ """
@@ -0,0 +1,128 @@
1
+ """
2
+ AgentGuard browser-use Adapter
3
+
4
+ Wraps browser-use actions so navigation, clicks, and form inputs pass through
5
+ AgentGuard policy checks before execution.
6
+
7
+ Usage:
8
+ from agentguard.adapters.browseruse import GuardedBrowser
9
+
10
+ browser = GuardedBrowser(
11
+ guard_url="http://localhost:8080",
12
+ agent_id="my-browser-agent",
13
+ )
14
+
15
+ # Check before navigating
16
+ result = browser.check_navigation("https://example.com")
17
+ if result.allowed:
18
+ await page.goto("https://example.com")
19
+ """
20
+
21
+ from typing import Any, Optional
22
+ from urllib.parse import urlparse
23
+ from agentguard import Guard, CheckResult
24
+
25
+
26
+ class GuardedBrowser:
27
+ """Policy-enforced wrapper for browser-use automation.
28
+
29
+ browser-use exposes a Browser/BrowserContext that agents drive. This class
30
+ provides guard methods that should be called before performing browser actions.
31
+ It can also wrap a browser-use Browser instance to intercept calls automatically.
32
+ """
33
+
34
+ def __init__(
35
+ self,
36
+ guard: Optional[Guard] = None,
37
+ guard_url: str = "http://localhost:8080",
38
+ agent_id: str = "",
39
+ browser: Any = None,
40
+ ):
41
+ self._guard = guard or Guard(guard_url, agent_id=agent_id)
42
+ self._browser = browser
43
+
44
+ def check_navigation(self, url: str) -> CheckResult:
45
+ """Check if navigation to a URL is allowed by policy."""
46
+ domain = ""
47
+ try:
48
+ parsed = urlparse(url)
49
+ domain = parsed.hostname or ""
50
+ except Exception:
51
+ pass
52
+
53
+ return self._guard.check("browser", url=url, domain=domain)
54
+
55
+ def check_action(self, action: str, target: str = "", meta: Optional[dict] = None) -> CheckResult:
56
+ """Check a browser action (click, type, etc.) against policy.
57
+
58
+ Args:
59
+ action: The action type (e.g., "click", "type", "screenshot")
60
+ target: The target selector or URL
61
+ meta: Additional context
62
+ """
63
+ return self._guard.check(
64
+ "browser",
65
+ command=f"{action} {target}".strip(),
66
+ meta=meta,
67
+ )
68
+
69
+ def check_form_input(self, url: str, field_name: str, value: str) -> CheckResult:
70
+ """Check if typing into a form field is allowed.
71
+
72
+ This is useful for preventing PII or credential leakage into web forms.
73
+ """
74
+ domain = ""
75
+ try:
76
+ parsed = urlparse(url)
77
+ domain = parsed.hostname or ""
78
+ except Exception:
79
+ pass
80
+
81
+ return self._guard.check(
82
+ "data",
83
+ domain=domain,
84
+ command=f"input:{field_name}",
85
+ meta={"field": field_name, "url": url},
86
+ )
87
+
88
+ def wrap_page(self, page: Any) -> "GuardedPage":
89
+ """Wrap a browser-use Page object with policy enforcement.
90
+
91
+ Returns a GuardedPage that intercepts goto() and other navigation methods.
92
+ """
93
+ return GuardedPage(page, self._guard)
94
+
95
+
96
+ class GuardedPage:
97
+ """Wraps a browser-use Page to enforce policies on navigation."""
98
+
99
+ def __init__(self, page: Any, guard: Guard):
100
+ self._page = page
101
+ self._guard = guard
102
+
103
+ async def goto(self, url: str, **kwargs) -> Any:
104
+ """Navigate to a URL after policy check."""
105
+ domain = ""
106
+ try:
107
+ parsed = urlparse(url)
108
+ domain = parsed.hostname or ""
109
+ except Exception:
110
+ pass
111
+
112
+ result = self._guard.check("browser", url=url, domain=domain)
113
+
114
+ if result.allowed:
115
+ return await self._page.goto(url, **kwargs)
116
+ elif result.needs_approval:
117
+ raise PermissionError(
118
+ f"[AgentGuard] Navigation requires approval. "
119
+ f"Approve at: {result.approval_url}"
120
+ )
121
+ else:
122
+ raise PermissionError(
123
+ f"[AgentGuard] Navigation denied: {result.reason}"
124
+ )
125
+
126
+ def __getattr__(self, name: str) -> Any:
127
+ """Proxy all other attributes to the wrapped page."""
128
+ return getattr(self._page, name)