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.
- agentguardproxy-0.2.0/PKG-INFO +142 -0
- agentguardproxy-0.2.0/README.md +106 -0
- agentguardproxy-0.2.0/agentguard/__init__.py +197 -0
- agentguardproxy-0.2.0/agentguard/adapters/__init__.py +8 -0
- agentguardproxy-0.2.0/agentguard/adapters/browseruse.py +128 -0
- agentguardproxy-0.2.0/agentguard/adapters/crewai.py +134 -0
- agentguardproxy-0.2.0/agentguard/adapters/langchain.py +167 -0
- agentguardproxy-0.2.0/agentguard/adapters/mcp.py +299 -0
- agentguardproxy-0.2.0/agentguardproxy.egg-info/PKG-INFO +142 -0
- agentguardproxy-0.2.0/agentguardproxy.egg-info/SOURCES.txt +13 -0
- agentguardproxy-0.2.0/agentguardproxy.egg-info/dependency_links.txt +1 -0
- agentguardproxy-0.2.0/agentguardproxy.egg-info/requires.txt +16 -0
- agentguardproxy-0.2.0/agentguardproxy.egg-info/top_level.txt +1 -0
- agentguardproxy-0.2.0/pyproject.toml +44 -0
- agentguardproxy-0.2.0/setup.cfg +4 -0
|
@@ -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)
|