Skip to content

Commit df84aa3

Browse files
authored
Merge pull request #5 from PredicateSystems/openclaw_adapter
openclaw adapter
2 parents fe3e7ea + e9d29cd commit df84aa3

File tree

9 files changed

+1071
-2
lines changed

9 files changed

+1071
-2
lines changed

.github/workflows/tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ jobs:
3333

3434
- name: Run security checks
3535
run: |
36-
python -m bandit -q -r src/predicate_secure/
36+
python -m bandit -q -r src/predicate_secure/ -c pyproject.toml
3737
3838
lint:
3939
runs-on: ubuntu-latest

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ secure_agent.run()
6464
| LangChain | Supported |
6565
| Playwright | Supported |
6666
| PydanticAI | Supported |
67-
| OpenClaw | Planned |
67+
| OpenClaw | Supported |
6868

6969
## Architecture
7070

docs/user-manual.md

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -482,6 +482,133 @@ secure_agent = SecureAgent(
482482

483483
---
484484

485+
### OpenClaw
486+
487+
[OpenClaw](https://github.com/openclaw/openclaw) is a local-first AI agent framework that connects to messaging platforms. SecureAgent integrates with OpenClaw CLI via HTTP proxy interception.
488+
489+
#### Architecture
490+
491+
The OpenClaw adapter works by:
492+
1. Starting an HTTP proxy server that intercepts OpenClaw skill calls
493+
2. Enforcing authorization policies before forwarding to actual skills
494+
3. Managing the OpenClaw CLI subprocess lifecycle
495+
496+
```
497+
Python SecureAgent
498+
499+
├─── HTTP Proxy (localhost:8788) ───┐
500+
│ ▼
501+
└─── spawns ──────► OpenClaw CLI ──► predicate-snapshot skill
502+
503+
└─► Browser actions (authorized)
504+
```
505+
506+
#### Basic Usage
507+
508+
```python
509+
from predicate_secure import SecureAgent
510+
from predicate_secure.openclaw_adapter import OpenClawConfig
511+
512+
# Create OpenClaw configuration
513+
openclaw_config = OpenClawConfig(
514+
cli_path="/usr/local/bin/openclaw", # Or None to use PATH
515+
skill_proxy_port=8788,
516+
skill_name="predicate-snapshot",
517+
)
518+
519+
# Or use a dict:
520+
# openclaw_config = {
521+
# "openclaw_cli_path": "/usr/local/bin/openclaw",
522+
# "skill_proxy_port": 8788,
523+
# }
524+
525+
# Wrap with SecureAgent
526+
secure_agent = SecureAgent(
527+
agent=openclaw_config,
528+
policy="policies/openclaw.yaml",
529+
mode="strict",
530+
)
531+
532+
# Run a task
533+
result = secure_agent.run(task="Navigate to example.com and take a snapshot")
534+
```
535+
536+
#### Policy Example for OpenClaw
537+
538+
```yaml
539+
# policies/openclaw.yaml
540+
rules:
541+
# Allow snapshot skill
542+
- action: "openclaw.skill.predicate-snapshot"
543+
resource: "*"
544+
effect: allow
545+
546+
# Allow clicking elements
547+
- action: "openclaw.skill.predicate-act.click"
548+
resource: "element:*"
549+
effect: allow
550+
551+
# Allow typing (but not in password fields)
552+
- action: "openclaw.skill.predicate-act.type"
553+
resource: "element:*"
554+
effect: allow
555+
conditions:
556+
- not_contains: ["password", "ssn"]
557+
558+
# Block scroll actions
559+
- action: "openclaw.skill.predicate-act.scroll"
560+
resource: "*"
561+
effect: deny
562+
563+
# Default deny
564+
- action: "*"
565+
resource: "*"
566+
effect: deny
567+
```
568+
569+
#### Proxy Configuration
570+
571+
The HTTP proxy intercepts requests to OpenClaw skills:
572+
573+
```python
574+
from predicate_secure.openclaw_adapter import create_openclaw_adapter, OpenClawConfig
575+
576+
config = OpenClawConfig(skill_proxy_port=8788)
577+
578+
# Custom authorizer function
579+
def my_authorizer(action: str, context: dict) -> bool:
580+
# Custom authorization logic
581+
if "snapshot" in action:
582+
return True
583+
print(f"Blocked: {action}")
584+
return False
585+
586+
adapter = create_openclaw_adapter(config, authorizer=my_authorizer)
587+
588+
# Start proxy
589+
adapter.start_proxy()
590+
print("Proxy running on http://localhost:8788")
591+
592+
# ... run OpenClaw tasks ...
593+
594+
# Cleanup
595+
adapter.cleanup()
596+
```
597+
598+
#### Environment Variables
599+
600+
Configure OpenClaw skill to use the proxy:
601+
602+
```bash
603+
export PREDICATE_PROXY_URL="http://localhost:8788"
604+
```
605+
606+
The OpenClaw adapter automatically sets this when starting the CLI subprocess.
607+
608+
**Full example:** [examples/openclaw_browser_automation.py](../examples/openclaw_browser_automation.py)
609+
610+
---
611+
485612
## Modes
486613

487614
SecureAgent supports four execution modes:
Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
"""
2+
Example: Secure OpenClaw agent with authorization and verification.
3+
4+
This example demonstrates how to wrap an OpenClaw CLI agent with predicate-secure
5+
to add pre-action authorization and audit logging for browser automation tasks.
6+
7+
Prerequisites:
8+
1. OpenClaw CLI installed (npm install -g openclaw)
9+
2. predicate-snapshot skill installed in OpenClaw
10+
3. Policy file (policies/openclaw_browser.yaml)
11+
"""
12+
13+
from pathlib import Path
14+
15+
from predicate_secure import SecureAgent
16+
from predicate_secure.openclaw_adapter import OpenClawConfig
17+
18+
# Example policy file content (create this as policies/openclaw_browser.yaml):
19+
EXAMPLE_POLICY = """
20+
rules:
21+
# Allow OpenClaw snapshot skill
22+
- action: "openclaw.skill.predicate-snapshot"
23+
resource: "*"
24+
effect: allow
25+
26+
# Allow clicking on known safe domains
27+
- action: "openclaw.skill.predicate-act.click"
28+
resource: "element:*"
29+
effect: allow
30+
conditions:
31+
# Only allow on safe domains (you'd check current URL in real policy)
32+
- domain_matches: ["*.example.com", "*.trusted-site.com"]
33+
34+
# Allow typing in form fields (with restrictions)
35+
- action: "openclaw.skill.predicate-act.type"
36+
resource: "element:*"
37+
effect: allow
38+
conditions:
39+
# Prevent entering sensitive data
40+
- not_contains: ["password", "ssn", "credit"]
41+
42+
# Block scrolling to prevent UI confusion
43+
- action: "openclaw.skill.predicate-act.scroll"
44+
resource: "*"
45+
effect: deny
46+
47+
# Default deny for safety
48+
- action: "*"
49+
resource: "*"
50+
effect: deny
51+
"""
52+
53+
54+
def main():
55+
"""Run OpenClaw agent with secure authorization."""
56+
# Create OpenClaw configuration
57+
openclaw_config = OpenClawConfig(
58+
cli_path="/usr/local/bin/openclaw", # Or None to use PATH
59+
skill_proxy_port=8788, # Port for HTTP proxy
60+
skill_name="predicate-snapshot",
61+
working_dir=str(Path.home() / ".openclaw"),
62+
)
63+
64+
# You could also use a dict instead of OpenClawConfig:
65+
# openclaw_config = {
66+
# "openclaw_cli_path": "/usr/local/bin/openclaw",
67+
# "skill_proxy_port": 8788,
68+
# "skill_name": "predicate-snapshot",
69+
# }
70+
71+
# Create policy file
72+
policy_dir = Path("policies")
73+
policy_dir.mkdir(exist_ok=True)
74+
policy_file = policy_dir / "openclaw_browser.yaml"
75+
76+
if not policy_file.exists():
77+
policy_file.write_text(EXAMPLE_POLICY)
78+
print(f"Created example policy at {policy_file}")
79+
80+
# Wrap OpenClaw with SecureAgent
81+
secure_agent = SecureAgent(
82+
agent=openclaw_config,
83+
policy=str(policy_file),
84+
mode="strict", # Fail-closed mode
85+
principal_id="openclaw-agent-01",
86+
trace_format="console",
87+
)
88+
89+
print(f"[predicate-secure] Detected framework: {secure_agent.framework.value}")
90+
print(f"[predicate-secure] Mode: {secure_agent.config.mode}")
91+
print(f"[predicate-secure] Policy: {secure_agent.config.effective_policy_path}")
92+
93+
# Example task
94+
task = "Navigate to example.com and take a snapshot"
95+
96+
print(f"\n[OpenClaw] Running task: {task}")
97+
print("[predicate-secure] Starting HTTP proxy for skill interception...")
98+
99+
try:
100+
# Run the OpenClaw task with authorization
101+
result = secure_agent.run(task=task)
102+
print("\n[OpenClaw] Task completed successfully")
103+
print(f"Return code: {result.get('returncode', 'N/A')}")
104+
print(f"Output: {result.get('stdout', '')[:200]}...")
105+
except Exception as e:
106+
print(f"\n[predicate-secure] Task failed: {e}")
107+
108+
109+
def example_with_debug_mode():
110+
"""Run OpenClaw agent in debug mode for troubleshooting."""
111+
openclaw_config = OpenClawConfig(skill_proxy_port=8789)
112+
113+
secure_agent = SecureAgent(
114+
agent=openclaw_config,
115+
mode="debug", # Human-readable trace output
116+
trace_format="console",
117+
trace_colors=True,
118+
)
119+
120+
print("\n[Debug Mode] Running OpenClaw agent with full tracing...")
121+
122+
task = "Check if example.com loads correctly"
123+
124+
try:
125+
result = secure_agent.run(task=task)
126+
print("\n[Debug] Task trace complete")
127+
print(f"Result: {result}")
128+
except Exception as e:
129+
print(f"\n[Debug] Error occurred: {e}")
130+
131+
132+
def example_with_manual_proxy():
133+
"""
134+
Example showing how to manually control the proxy lifecycle.
135+
136+
Useful when you want to keep the proxy running across multiple tasks.
137+
"""
138+
from predicate_secure.openclaw_adapter import create_openclaw_adapter
139+
140+
openclaw_config = OpenClawConfig(skill_proxy_port=8790)
141+
142+
# Create adapter manually
143+
def authorizer(action: str, context: dict) -> bool:
144+
"""Simple authorizer that allows snapshot but blocks act."""
145+
if "snapshot" in action:
146+
return True
147+
print(f"[Authorizer] Blocked action: {action}")
148+
return False
149+
150+
adapter = create_openclaw_adapter(openclaw_config, authorizer=authorizer)
151+
152+
try:
153+
# Start proxy (stays running)
154+
adapter.start_proxy()
155+
print("[Proxy] Started on http://localhost:8790")
156+
157+
# Run multiple tasks with same proxy
158+
tasks = [
159+
"Take snapshot of example.com",
160+
"Take snapshot of httpbin.org",
161+
]
162+
163+
for task in tasks:
164+
print(f"\n[Task] {task}")
165+
adapter.start_cli(task)
166+
# Process would run in background
167+
# In real usage, you'd wait for completion
168+
169+
finally:
170+
# Clean up
171+
adapter.cleanup()
172+
print("\n[Proxy] Stopped")
173+
174+
175+
if __name__ == "__main__":
176+
print("=" * 60)
177+
print("predicate-secure: OpenClaw Agent Example")
178+
print("=" * 60)
179+
180+
# Uncomment the example you want to run:
181+
182+
# Example 1: Basic usage with policy file
183+
# main()
184+
185+
# Example 2: Debug mode with full tracing
186+
# example_with_debug_mode()
187+
188+
# Example 3: Manual proxy control
189+
# example_with_manual_proxy()
190+
191+
print("\nNote: Uncomment one of the example functions in __main__ to run")
192+
print("Make sure OpenClaw CLI is installed and in your PATH")

0 commit comments

Comments
 (0)