bloom-openclaw-skill 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.
- bloom_openclaw_skill-1.0.0/PKG-INFO +91 -0
- bloom_openclaw_skill-1.0.0/README.md +56 -0
- bloom_openclaw_skill-1.0.0/bloom_openclaw_skill.egg-info/PKG-INFO +91 -0
- bloom_openclaw_skill-1.0.0/bloom_openclaw_skill.egg-info/SOURCES.txt +9 -0
- bloom_openclaw_skill-1.0.0/bloom_openclaw_skill.egg-info/dependency_links.txt +1 -0
- bloom_openclaw_skill-1.0.0/bloom_openclaw_skill.egg-info/requires.txt +5 -0
- bloom_openclaw_skill-1.0.0/bloom_openclaw_skill.egg-info/top_level.txt +1 -0
- bloom_openclaw_skill-1.0.0/bloom_proxy.py +336 -0
- bloom_openclaw_skill-1.0.0/pyproject.toml +46 -0
- bloom_openclaw_skill-1.0.0/setup.cfg +4 -0
- bloom_openclaw_skill-1.0.0/setup.py +45 -0
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: bloom-openclaw-skill
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Secure proxy skill for OpenClaw agents
|
|
5
|
+
Home-page: https://github.com/bloomtechnologies/bloom-openclaw-skill
|
|
6
|
+
Author: Bloom Technologies
|
|
7
|
+
Author-email: Bloom Technologies <support@bloomtechnologies.app>
|
|
8
|
+
License: MIT
|
|
9
|
+
Project-URL: Homepage, https://bloomtechnologies.app
|
|
10
|
+
Project-URL: Documentation, https://docs.bloomtechnologies.app
|
|
11
|
+
Project-URL: Repository, https://github.com/bloomtechnologies/bloom-openclaw-skill
|
|
12
|
+
Project-URL: Issues, https://github.com/bloomtechnologies/bloom-openclaw-skill/issues
|
|
13
|
+
Keywords: openclaw,security,proxy,iam,authentication,ai-agent
|
|
14
|
+
Classifier: Development Status :: 4 - Beta
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
24
|
+
Classifier: Topic :: Security
|
|
25
|
+
Classifier: Topic :: Internet :: Proxy Servers
|
|
26
|
+
Requires-Python: >=3.8
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
Provides-Extra: dev
|
|
29
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
30
|
+
Requires-Dist: black>=23.0.0; extra == "dev"
|
|
31
|
+
Requires-Dist: mypy>=1.0.0; extra == "dev"
|
|
32
|
+
Dynamic: author
|
|
33
|
+
Dynamic: home-page
|
|
34
|
+
Dynamic: requires-python
|
|
35
|
+
|
|
36
|
+
# Bloom Secure Proxy for OpenClaw
|
|
37
|
+
|
|
38
|
+
Secure your OpenClaw agent in 3 minutes with Bloom's IAM layer.
|
|
39
|
+
|
|
40
|
+
## Quick Start
|
|
41
|
+
|
|
42
|
+
### 1. Get Your Bloom Token
|
|
43
|
+
|
|
44
|
+
1. Sign up at [platform.bloomtechnologies.app](https://platform.bloomtechnologies.app)
|
|
45
|
+
2. Create an agent in the dashboard
|
|
46
|
+
3. Get your API key from Profile > API Keys
|
|
47
|
+
4. Combine as: `bloom_<api_key>_agent_<agent_id>`
|
|
48
|
+
|
|
49
|
+
### 2. Install the Skill
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
# Clone to your OpenClaw skills directory
|
|
53
|
+
cd ~/.openclaw/skills
|
|
54
|
+
git clone https://github.com/bloomtechnologies/bloom-openclaw-skill bloom-secure-proxy
|
|
55
|
+
|
|
56
|
+
# Or install via pip
|
|
57
|
+
pip install bloom-openclaw-skill
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### 3. Configure
|
|
61
|
+
|
|
62
|
+
Add to your `.env`:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
BLOOM_AGENT_TOKEN=bloom_xxx_agent_yyy
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### 4. Use It
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
from bloom_proxy import bloom_get, bloom_post
|
|
72
|
+
|
|
73
|
+
# All requests now go through Bloom's secure proxy
|
|
74
|
+
response = bloom_get("https://api.github.com/user")
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Features
|
|
78
|
+
|
|
79
|
+
- **Zero-config security** - Just set one environment variable
|
|
80
|
+
- **Granular permissions** - Control access at method + path level
|
|
81
|
+
- **Full audit trail** - Every request logged in Bloom dashboard
|
|
82
|
+
- **Instant kill switch** - Halt agent immediately if compromised
|
|
83
|
+
- **Prompt injection protection** - Block malicious payloads
|
|
84
|
+
|
|
85
|
+
## Documentation
|
|
86
|
+
|
|
87
|
+
See [SKILL.md](./SKILL.md) for full documentation.
|
|
88
|
+
|
|
89
|
+
## License
|
|
90
|
+
|
|
91
|
+
MIT
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Bloom Secure Proxy for OpenClaw
|
|
2
|
+
|
|
3
|
+
Secure your OpenClaw agent in 3 minutes with Bloom's IAM layer.
|
|
4
|
+
|
|
5
|
+
## Quick Start
|
|
6
|
+
|
|
7
|
+
### 1. Get Your Bloom Token
|
|
8
|
+
|
|
9
|
+
1. Sign up at [platform.bloomtechnologies.app](https://platform.bloomtechnologies.app)
|
|
10
|
+
2. Create an agent in the dashboard
|
|
11
|
+
3. Get your API key from Profile > API Keys
|
|
12
|
+
4. Combine as: `bloom_<api_key>_agent_<agent_id>`
|
|
13
|
+
|
|
14
|
+
### 2. Install the Skill
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
# Clone to your OpenClaw skills directory
|
|
18
|
+
cd ~/.openclaw/skills
|
|
19
|
+
git clone https://github.com/bloomtechnologies/bloom-openclaw-skill bloom-secure-proxy
|
|
20
|
+
|
|
21
|
+
# Or install via pip
|
|
22
|
+
pip install bloom-openclaw-skill
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### 3. Configure
|
|
26
|
+
|
|
27
|
+
Add to your `.env`:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
BLOOM_AGENT_TOKEN=bloom_xxx_agent_yyy
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### 4. Use It
|
|
34
|
+
|
|
35
|
+
```python
|
|
36
|
+
from bloom_proxy import bloom_get, bloom_post
|
|
37
|
+
|
|
38
|
+
# All requests now go through Bloom's secure proxy
|
|
39
|
+
response = bloom_get("https://api.github.com/user")
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Features
|
|
43
|
+
|
|
44
|
+
- **Zero-config security** - Just set one environment variable
|
|
45
|
+
- **Granular permissions** - Control access at method + path level
|
|
46
|
+
- **Full audit trail** - Every request logged in Bloom dashboard
|
|
47
|
+
- **Instant kill switch** - Halt agent immediately if compromised
|
|
48
|
+
- **Prompt injection protection** - Block malicious payloads
|
|
49
|
+
|
|
50
|
+
## Documentation
|
|
51
|
+
|
|
52
|
+
See [SKILL.md](./SKILL.md) for full documentation.
|
|
53
|
+
|
|
54
|
+
## License
|
|
55
|
+
|
|
56
|
+
MIT
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: bloom-openclaw-skill
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Secure proxy skill for OpenClaw agents
|
|
5
|
+
Home-page: https://github.com/bloomtechnologies/bloom-openclaw-skill
|
|
6
|
+
Author: Bloom Technologies
|
|
7
|
+
Author-email: Bloom Technologies <support@bloomtechnologies.app>
|
|
8
|
+
License: MIT
|
|
9
|
+
Project-URL: Homepage, https://bloomtechnologies.app
|
|
10
|
+
Project-URL: Documentation, https://docs.bloomtechnologies.app
|
|
11
|
+
Project-URL: Repository, https://github.com/bloomtechnologies/bloom-openclaw-skill
|
|
12
|
+
Project-URL: Issues, https://github.com/bloomtechnologies/bloom-openclaw-skill/issues
|
|
13
|
+
Keywords: openclaw,security,proxy,iam,authentication,ai-agent
|
|
14
|
+
Classifier: Development Status :: 4 - Beta
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
24
|
+
Classifier: Topic :: Security
|
|
25
|
+
Classifier: Topic :: Internet :: Proxy Servers
|
|
26
|
+
Requires-Python: >=3.8
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
Provides-Extra: dev
|
|
29
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
30
|
+
Requires-Dist: black>=23.0.0; extra == "dev"
|
|
31
|
+
Requires-Dist: mypy>=1.0.0; extra == "dev"
|
|
32
|
+
Dynamic: author
|
|
33
|
+
Dynamic: home-page
|
|
34
|
+
Dynamic: requires-python
|
|
35
|
+
|
|
36
|
+
# Bloom Secure Proxy for OpenClaw
|
|
37
|
+
|
|
38
|
+
Secure your OpenClaw agent in 3 minutes with Bloom's IAM layer.
|
|
39
|
+
|
|
40
|
+
## Quick Start
|
|
41
|
+
|
|
42
|
+
### 1. Get Your Bloom Token
|
|
43
|
+
|
|
44
|
+
1. Sign up at [platform.bloomtechnologies.app](https://platform.bloomtechnologies.app)
|
|
45
|
+
2. Create an agent in the dashboard
|
|
46
|
+
3. Get your API key from Profile > API Keys
|
|
47
|
+
4. Combine as: `bloom_<api_key>_agent_<agent_id>`
|
|
48
|
+
|
|
49
|
+
### 2. Install the Skill
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
# Clone to your OpenClaw skills directory
|
|
53
|
+
cd ~/.openclaw/skills
|
|
54
|
+
git clone https://github.com/bloomtechnologies/bloom-openclaw-skill bloom-secure-proxy
|
|
55
|
+
|
|
56
|
+
# Or install via pip
|
|
57
|
+
pip install bloom-openclaw-skill
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### 3. Configure
|
|
61
|
+
|
|
62
|
+
Add to your `.env`:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
BLOOM_AGENT_TOKEN=bloom_xxx_agent_yyy
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### 4. Use It
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
from bloom_proxy import bloom_get, bloom_post
|
|
72
|
+
|
|
73
|
+
# All requests now go through Bloom's secure proxy
|
|
74
|
+
response = bloom_get("https://api.github.com/user")
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Features
|
|
78
|
+
|
|
79
|
+
- **Zero-config security** - Just set one environment variable
|
|
80
|
+
- **Granular permissions** - Control access at method + path level
|
|
81
|
+
- **Full audit trail** - Every request logged in Bloom dashboard
|
|
82
|
+
- **Instant kill switch** - Halt agent immediately if compromised
|
|
83
|
+
- **Prompt injection protection** - Block malicious payloads
|
|
84
|
+
|
|
85
|
+
## Documentation
|
|
86
|
+
|
|
87
|
+
See [SKILL.md](./SKILL.md) for full documentation.
|
|
88
|
+
|
|
89
|
+
## License
|
|
90
|
+
|
|
91
|
+
MIT
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
bloom_proxy.py
|
|
3
|
+
pyproject.toml
|
|
4
|
+
setup.py
|
|
5
|
+
bloom_openclaw_skill.egg-info/PKG-INFO
|
|
6
|
+
bloom_openclaw_skill.egg-info/SOURCES.txt
|
|
7
|
+
bloom_openclaw_skill.egg-info/dependency_links.txt
|
|
8
|
+
bloom_openclaw_skill.egg-info/requires.txt
|
|
9
|
+
bloom_openclaw_skill.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
bloom_proxy
|
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Bloom Secure Proxy Skill for OpenClaw
|
|
4
|
+
|
|
5
|
+
Routes all outbound API requests through Bloom's IAM proxy for
|
|
6
|
+
authentication, authorization, audit logging, and security scanning.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import os
|
|
10
|
+
import json
|
|
11
|
+
import hashlib
|
|
12
|
+
import time
|
|
13
|
+
from typing import Optional, Dict, Any, List
|
|
14
|
+
from urllib.request import Request, urlopen
|
|
15
|
+
from urllib.error import HTTPError, URLError
|
|
16
|
+
|
|
17
|
+
# Configuration
|
|
18
|
+
BLOOM_PROXY_URL = os.environ.get(
|
|
19
|
+
"BLOOM_PROXY_URL",
|
|
20
|
+
"https://iam.bloomtechnologies.app"
|
|
21
|
+
)
|
|
22
|
+
BLOOM_AGENT_TOKEN = os.environ.get("BLOOM_AGENT_TOKEN")
|
|
23
|
+
BLOOM_ORG_ID = os.environ.get("BLOOM_ORG_ID")
|
|
24
|
+
|
|
25
|
+
# Timeouts
|
|
26
|
+
REQUEST_TIMEOUT = int(os.environ.get("BLOOM_TIMEOUT", "30"))
|
|
27
|
+
MAX_RETRIES = int(os.environ.get("BLOOM_RETRIES", "3"))
|
|
28
|
+
|
|
29
|
+
# Cache (optional, in-memory)
|
|
30
|
+
_response_cache: Dict[str, tuple] = {}
|
|
31
|
+
CACHE_ENABLED = os.environ.get("BLOOM_CACHE_ENABLED", "false").lower() == "true"
|
|
32
|
+
CACHE_TTL = int(os.environ.get("BLOOM_CACHE_TTL", "300"))
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class BloomProxyError(Exception):
|
|
36
|
+
"""Raised when Bloom proxy returns an error."""
|
|
37
|
+
def __init__(self, status_code: int, message: str, details: Optional[dict] = None):
|
|
38
|
+
self.status_code = status_code
|
|
39
|
+
self.message = message
|
|
40
|
+
self.details = details or {}
|
|
41
|
+
super().__init__(f"Bloom Proxy Error {status_code}: {message}")
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class BloomSecurityBlock(Exception):
|
|
45
|
+
"""Raised when Bloom blocks a request for security reasons."""
|
|
46
|
+
def __init__(self, reason: str, threat_type: str):
|
|
47
|
+
self.reason = reason
|
|
48
|
+
self.threat_type = threat_type
|
|
49
|
+
super().__init__(f"Request blocked: {reason} (threat: {threat_type})")
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class BloomKillSwitchActive(Exception):
|
|
53
|
+
"""Raised when the agent has been killed."""
|
|
54
|
+
def __init__(self, agent_id: str, killed_at: str):
|
|
55
|
+
self.agent_id = agent_id
|
|
56
|
+
self.killed_at = killed_at
|
|
57
|
+
super().__init__(f"Agent {agent_id} has been killed at {killed_at}")
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _get_cache_key(method: str, url: str, body: Optional[str]) -> str:
|
|
61
|
+
"""Generate cache key for a request."""
|
|
62
|
+
content = f"{method}:{url}:{body or ''}"
|
|
63
|
+
return hashlib.sha256(content.encode()).hexdigest()
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _check_cache(cache_key: str) -> Optional[dict]:
|
|
67
|
+
"""Check if response is cached and not expired."""
|
|
68
|
+
if not CACHE_ENABLED:
|
|
69
|
+
return None
|
|
70
|
+
|
|
71
|
+
if cache_key in _response_cache:
|
|
72
|
+
response, timestamp = _response_cache[cache_key]
|
|
73
|
+
if time.time() - timestamp < CACHE_TTL:
|
|
74
|
+
return response
|
|
75
|
+
else:
|
|
76
|
+
del _response_cache[cache_key]
|
|
77
|
+
return None
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _store_cache(cache_key: str, response: dict):
|
|
81
|
+
"""Store response in cache."""
|
|
82
|
+
if CACHE_ENABLED:
|
|
83
|
+
_response_cache[cache_key] = (response, time.time())
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def bloom_request(
|
|
87
|
+
method: str,
|
|
88
|
+
url: str,
|
|
89
|
+
headers: Optional[Dict[str, str]] = None,
|
|
90
|
+
body: Optional[Any] = None,
|
|
91
|
+
timeout: Optional[int] = None,
|
|
92
|
+
skip_cache: bool = False
|
|
93
|
+
) -> Dict[str, Any]:
|
|
94
|
+
"""
|
|
95
|
+
Route an HTTP request through Bloom's secure proxy.
|
|
96
|
+
|
|
97
|
+
Args:
|
|
98
|
+
method: HTTP method (GET, POST, PUT, DELETE, etc.)
|
|
99
|
+
url: Target URL
|
|
100
|
+
headers: Optional headers to include
|
|
101
|
+
body: Optional request body (will be JSON-encoded if dict)
|
|
102
|
+
timeout: Optional timeout override
|
|
103
|
+
skip_cache: Skip cache lookup/storage
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
dict with 'status_code', 'headers', 'body' keys
|
|
107
|
+
|
|
108
|
+
Raises:
|
|
109
|
+
BloomProxyError: If proxy returns an error
|
|
110
|
+
BloomSecurityBlock: If request is blocked for security
|
|
111
|
+
BloomKillSwitchActive: If agent has been killed
|
|
112
|
+
"""
|
|
113
|
+
if not BLOOM_AGENT_TOKEN:
|
|
114
|
+
raise BloomProxyError(
|
|
115
|
+
401,
|
|
116
|
+
"BLOOM_AGENT_TOKEN not set. Please configure your Bloom credentials."
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
# Serialize body
|
|
120
|
+
body_str = None
|
|
121
|
+
if body is not None:
|
|
122
|
+
if isinstance(body, (dict, list)):
|
|
123
|
+
body_str = json.dumps(body)
|
|
124
|
+
else:
|
|
125
|
+
body_str = str(body)
|
|
126
|
+
|
|
127
|
+
# Check cache
|
|
128
|
+
cache_key = _get_cache_key(method, url, body_str)
|
|
129
|
+
if not skip_cache:
|
|
130
|
+
cached = _check_cache(cache_key)
|
|
131
|
+
if cached:
|
|
132
|
+
return cached
|
|
133
|
+
|
|
134
|
+
# Build proxy URL - use the direct pass-through format
|
|
135
|
+
# e.g., https://iam.bloomtechnologies.app/https://api.openai.com/v1/chat/completions
|
|
136
|
+
proxy_url = f"{BLOOM_PROXY_URL}/{url}"
|
|
137
|
+
|
|
138
|
+
proxy_headers = {
|
|
139
|
+
"Content-Type": headers.get("Content-Type", "application/json") if headers else "application/json",
|
|
140
|
+
"Authorization": f"Bearer {BLOOM_AGENT_TOKEN}",
|
|
141
|
+
"X-Bloom-Agent-Version": "1.0.0",
|
|
142
|
+
"X-Bloom-Skill": "bloom-openclaw-skill"
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
# Forward original headers (except Authorization which Bloom will inject)
|
|
146
|
+
if headers:
|
|
147
|
+
for key, value in headers.items():
|
|
148
|
+
if key.lower() not in ["authorization", "content-type"]:
|
|
149
|
+
proxy_headers[key] = value
|
|
150
|
+
|
|
151
|
+
if BLOOM_ORG_ID:
|
|
152
|
+
proxy_headers["X-Bloom-Org-ID"] = BLOOM_ORG_ID
|
|
153
|
+
|
|
154
|
+
# Make request with retries
|
|
155
|
+
last_error = None
|
|
156
|
+
for attempt in range(MAX_RETRIES):
|
|
157
|
+
try:
|
|
158
|
+
req = Request(
|
|
159
|
+
proxy_url,
|
|
160
|
+
data=body_str.encode("utf-8") if body_str else None,
|
|
161
|
+
headers=proxy_headers,
|
|
162
|
+
method=method.upper()
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
with urlopen(req, timeout=timeout or REQUEST_TIMEOUT) as resp:
|
|
166
|
+
response_body = resp.read().decode("utf-8")
|
|
167
|
+
|
|
168
|
+
result = {
|
|
169
|
+
"status_code": resp.status,
|
|
170
|
+
"headers": dict(resp.headers),
|
|
171
|
+
"body": response_body
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
# Try to parse JSON response
|
|
175
|
+
try:
|
|
176
|
+
result["json"] = json.loads(response_body)
|
|
177
|
+
except json.JSONDecodeError:
|
|
178
|
+
result["json"] = None
|
|
179
|
+
|
|
180
|
+
# Cache successful GET responses
|
|
181
|
+
if method.upper() == "GET" and result["status_code"] == 200:
|
|
182
|
+
_store_cache(cache_key, result)
|
|
183
|
+
|
|
184
|
+
return result
|
|
185
|
+
|
|
186
|
+
except HTTPError as e:
|
|
187
|
+
error_body = e.read().decode("utf-8")
|
|
188
|
+
try:
|
|
189
|
+
error_data = json.loads(error_body)
|
|
190
|
+
except json.JSONDecodeError:
|
|
191
|
+
error_data = {"message": error_body}
|
|
192
|
+
|
|
193
|
+
# Check for kill switch
|
|
194
|
+
if e.code == 403 and error_data.get("error") == "Agent killed":
|
|
195
|
+
raise BloomKillSwitchActive(
|
|
196
|
+
error_data.get("agent_id", "unknown"),
|
|
197
|
+
error_data.get("killed_at", "unknown")
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
# Check for security block
|
|
201
|
+
if e.code == 403 and error_data.get("security_block"):
|
|
202
|
+
raise BloomSecurityBlock(
|
|
203
|
+
error_data.get("reason", "Request blocked"),
|
|
204
|
+
error_data.get("threat_type", "unknown")
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
# Don't retry 4xx errors (except 429)
|
|
208
|
+
if 400 <= e.code < 500 and e.code != 429:
|
|
209
|
+
raise BloomProxyError(e.code, error_data.get("message", str(e)), error_data)
|
|
210
|
+
|
|
211
|
+
last_error = BloomProxyError(e.code, error_data.get("message", str(e)), error_data)
|
|
212
|
+
|
|
213
|
+
except URLError as e:
|
|
214
|
+
last_error = BloomProxyError(0, f"Network error: {e.reason}")
|
|
215
|
+
|
|
216
|
+
# Exponential backoff
|
|
217
|
+
if attempt < MAX_RETRIES - 1:
|
|
218
|
+
time.sleep(2 ** attempt)
|
|
219
|
+
|
|
220
|
+
raise last_error
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def bloom_get(url: str, headers: Optional[Dict[str, str]] = None, **kwargs) -> Dict[str, Any]:
|
|
224
|
+
"""Convenience wrapper for GET requests."""
|
|
225
|
+
return bloom_request("GET", url, headers=headers, **kwargs)
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def bloom_post(url: str, body: Any, headers: Optional[Dict[str, str]] = None, **kwargs) -> Dict[str, Any]:
|
|
229
|
+
"""Convenience wrapper for POST requests."""
|
|
230
|
+
return bloom_request("POST", url, headers=headers, body=body, **kwargs)
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def bloom_put(url: str, body: Any, headers: Optional[Dict[str, str]] = None, **kwargs) -> Dict[str, Any]:
|
|
234
|
+
"""Convenience wrapper for PUT requests."""
|
|
235
|
+
return bloom_request("PUT", url, headers=headers, body=body, **kwargs)
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def bloom_delete(url: str, headers: Optional[Dict[str, str]] = None, **kwargs) -> Dict[str, Any]:
|
|
239
|
+
"""Convenience wrapper for DELETE requests."""
|
|
240
|
+
return bloom_request("DELETE", url, headers=headers, **kwargs)
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def bloom_patch(url: str, body: Any, headers: Optional[Dict[str, str]] = None, **kwargs) -> Dict[str, Any]:
|
|
244
|
+
"""Convenience wrapper for PATCH requests."""
|
|
245
|
+
return bloom_request("PATCH", url, headers=headers, body=body, **kwargs)
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
# Health check
|
|
249
|
+
def check_bloom_connection() -> Dict[str, Any]:
|
|
250
|
+
"""Verify connection to Bloom proxy and get agent status."""
|
|
251
|
+
try:
|
|
252
|
+
req = Request(
|
|
253
|
+
f"{BLOOM_PROXY_URL}/health",
|
|
254
|
+
headers={"Authorization": f"Bearer {BLOOM_AGENT_TOKEN}"} if BLOOM_AGENT_TOKEN else {}
|
|
255
|
+
)
|
|
256
|
+
with urlopen(req, timeout=5) as resp:
|
|
257
|
+
data = json.loads(resp.read().decode("utf-8"))
|
|
258
|
+
return {
|
|
259
|
+
"connected": True,
|
|
260
|
+
"proxy_status": data.get("status", "unknown"),
|
|
261
|
+
"proxy_url": BLOOM_PROXY_URL,
|
|
262
|
+
"agent_token_set": bool(BLOOM_AGENT_TOKEN)
|
|
263
|
+
}
|
|
264
|
+
except Exception as e:
|
|
265
|
+
return {
|
|
266
|
+
"connected": False,
|
|
267
|
+
"error": str(e),
|
|
268
|
+
"proxy_url": BLOOM_PROXY_URL,
|
|
269
|
+
"agent_token_set": bool(BLOOM_AGENT_TOKEN)
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def get_agent_status() -> Dict[str, Any]:
|
|
274
|
+
"""Get the current agent's status from Bloom."""
|
|
275
|
+
if not BLOOM_AGENT_TOKEN:
|
|
276
|
+
return {"error": "BLOOM_AGENT_TOKEN not set"}
|
|
277
|
+
|
|
278
|
+
try:
|
|
279
|
+
# Extract agent ID from token if it's in the format bloom_xxx_agent_yyy
|
|
280
|
+
agent_id = None
|
|
281
|
+
if "_agent_" in BLOOM_AGENT_TOKEN:
|
|
282
|
+
agent_id = BLOOM_AGENT_TOKEN.split("_agent_")[1]
|
|
283
|
+
|
|
284
|
+
req = Request(
|
|
285
|
+
f"{BLOOM_PROXY_URL}/health",
|
|
286
|
+
headers={"Authorization": f"Bearer {BLOOM_AGENT_TOKEN}"}
|
|
287
|
+
)
|
|
288
|
+
with urlopen(req, timeout=5) as resp:
|
|
289
|
+
return {
|
|
290
|
+
"status": "active",
|
|
291
|
+
"agent_id": agent_id,
|
|
292
|
+
"proxy_reachable": True
|
|
293
|
+
}
|
|
294
|
+
except HTTPError as e:
|
|
295
|
+
if e.code == 403:
|
|
296
|
+
return {
|
|
297
|
+
"status": "killed",
|
|
298
|
+
"agent_id": agent_id if 'agent_id' in dir() else None,
|
|
299
|
+
"proxy_reachable": True
|
|
300
|
+
}
|
|
301
|
+
return {
|
|
302
|
+
"status": "error",
|
|
303
|
+
"error": str(e),
|
|
304
|
+
"proxy_reachable": True
|
|
305
|
+
}
|
|
306
|
+
except Exception as e:
|
|
307
|
+
return {
|
|
308
|
+
"status": "error",
|
|
309
|
+
"error": str(e),
|
|
310
|
+
"proxy_reachable": False
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
if __name__ == "__main__":
|
|
315
|
+
# Self-test
|
|
316
|
+
print("Bloom Secure Proxy - Connection Test")
|
|
317
|
+
print("=" * 40)
|
|
318
|
+
|
|
319
|
+
status = check_bloom_connection()
|
|
320
|
+
|
|
321
|
+
if status["connected"]:
|
|
322
|
+
print(f" Connected: Yes")
|
|
323
|
+
print(f" Proxy URL: {status['proxy_url']}")
|
|
324
|
+
print(f" Proxy Status: {status['proxy_status']}")
|
|
325
|
+
print(f" Agent Token Set: {status['agent_token_set']}")
|
|
326
|
+
else:
|
|
327
|
+
print(f" Connected: No")
|
|
328
|
+
print(f" Error: {status.get('error', 'Unknown')}")
|
|
329
|
+
print(f" Proxy URL: {status['proxy_url']}")
|
|
330
|
+
print(f" Agent Token Set: {status['agent_token_set']}")
|
|
331
|
+
|
|
332
|
+
print()
|
|
333
|
+
|
|
334
|
+
if not status['agent_token_set']:
|
|
335
|
+
print("Set BLOOM_AGENT_TOKEN environment variable to enable proxy.")
|
|
336
|
+
print("Get your token at: https://platform.bloomtechnologies.app/dashboard")
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "bloom-openclaw-skill"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
description = "Secure proxy skill for OpenClaw agents"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = {text = "MIT"}
|
|
11
|
+
requires-python = ">=3.8"
|
|
12
|
+
authors = [
|
|
13
|
+
{name = "Bloom Technologies", email = "support@bloomtechnologies.app"}
|
|
14
|
+
]
|
|
15
|
+
keywords = ["openclaw", "security", "proxy", "iam", "authentication", "ai-agent"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 4 - Beta",
|
|
18
|
+
"Intended Audience :: Developers",
|
|
19
|
+
"License :: OSI Approved :: MIT License",
|
|
20
|
+
"Operating System :: OS Independent",
|
|
21
|
+
"Programming Language :: Python :: 3",
|
|
22
|
+
"Programming Language :: Python :: 3.8",
|
|
23
|
+
"Programming Language :: Python :: 3.9",
|
|
24
|
+
"Programming Language :: Python :: 3.10",
|
|
25
|
+
"Programming Language :: Python :: 3.11",
|
|
26
|
+
"Programming Language :: Python :: 3.12",
|
|
27
|
+
"Topic :: Security",
|
|
28
|
+
"Topic :: Internet :: Proxy Servers",
|
|
29
|
+
]
|
|
30
|
+
dependencies = []
|
|
31
|
+
|
|
32
|
+
[project.optional-dependencies]
|
|
33
|
+
dev = [
|
|
34
|
+
"pytest>=7.0.0",
|
|
35
|
+
"black>=23.0.0",
|
|
36
|
+
"mypy>=1.0.0",
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
[project.urls]
|
|
40
|
+
Homepage = "https://bloomtechnologies.app"
|
|
41
|
+
Documentation = "https://docs.bloomtechnologies.app"
|
|
42
|
+
Repository = "https://github.com/bloomtechnologies/bloom-openclaw-skill"
|
|
43
|
+
Issues = "https://github.com/bloomtechnologies/bloom-openclaw-skill/issues"
|
|
44
|
+
|
|
45
|
+
[tool.setuptools]
|
|
46
|
+
py-modules = ["bloom_proxy"]
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
from setuptools import setup, find_packages
|
|
2
|
+
|
|
3
|
+
with open("README.md", "r", encoding="utf-8") as fh:
|
|
4
|
+
long_description = fh.read()
|
|
5
|
+
|
|
6
|
+
setup(
|
|
7
|
+
name="bloom-openclaw-skill",
|
|
8
|
+
version="1.0.0",
|
|
9
|
+
author="Bloom Technologies",
|
|
10
|
+
author_email="support@bloomtechnologies.app",
|
|
11
|
+
description="Secure proxy skill for OpenClaw agents",
|
|
12
|
+
long_description=long_description,
|
|
13
|
+
long_description_content_type="text/markdown",
|
|
14
|
+
url="https://github.com/bloomtechnologies/bloom-openclaw-skill",
|
|
15
|
+
py_modules=["bloom_proxy"],
|
|
16
|
+
classifiers=[
|
|
17
|
+
"Development Status :: 4 - Beta",
|
|
18
|
+
"Intended Audience :: Developers",
|
|
19
|
+
"License :: OSI Approved :: MIT License",
|
|
20
|
+
"Operating System :: OS Independent",
|
|
21
|
+
"Programming Language :: Python :: 3",
|
|
22
|
+
"Programming Language :: Python :: 3.8",
|
|
23
|
+
"Programming Language :: Python :: 3.9",
|
|
24
|
+
"Programming Language :: Python :: 3.10",
|
|
25
|
+
"Programming Language :: Python :: 3.11",
|
|
26
|
+
"Programming Language :: Python :: 3.12",
|
|
27
|
+
"Topic :: Security",
|
|
28
|
+
"Topic :: Internet :: Proxy Servers",
|
|
29
|
+
],
|
|
30
|
+
python_requires=">=3.8",
|
|
31
|
+
install_requires=[], # No external dependencies - uses stdlib only
|
|
32
|
+
extras_require={
|
|
33
|
+
"dev": [
|
|
34
|
+
"pytest>=7.0.0",
|
|
35
|
+
"black>=23.0.0",
|
|
36
|
+
"mypy>=1.0.0",
|
|
37
|
+
],
|
|
38
|
+
},
|
|
39
|
+
keywords="openclaw, security, proxy, iam, authentication, ai-agent",
|
|
40
|
+
project_urls={
|
|
41
|
+
"Bug Reports": "https://github.com/bloomtechnologies/bloom-openclaw-skill/issues",
|
|
42
|
+
"Documentation": "https://docs.bloomtechnologies.app",
|
|
43
|
+
"Source": "https://github.com/bloomtechnologies/bloom-openclaw-skill",
|
|
44
|
+
},
|
|
45
|
+
)
|