Fix Docker OpenClaw HMAC persistence and privacy-core build warnings.

Persist OPENCLAW_HMAC_SECRET to data/openclaw.env so empty Docker env vars no longer block auth after UI bootstrap. Add verify_hmac.py, silence Rust warnings (#423), and document Docker signing (#424).

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
BigBodyCobainandCursor committed 2026-06-30 20:38:44 -06:00
1 parent 47faa1e488
commit c45b91c06c
7 files changed
+221 -5

No files matched your search

+13
View File
@@ -88,6 +88,19 @@ For compatibility with older snippets, `SHADOWBROKER_KEY` is also accepted by
the client as the same HMAC signing secret. Prefer `SHADOWBROKER_HMAC_SECRET`
for new setups.
**Docker Compose:** host-side agents (`localhost:8000` from the Kali/macOS host)
are not loopback inside the backend container — HMAC is required. After
**Connect OpenClaw → Bootstrap → Reveal**, the secret is persisted under
`data/openclaw.env` on the `backend_data` volume. Restart the backend once,
then verify with:
```bash
python openclaw-skills/shadowbroker/verify_hmac.py
```
Hand-rolled signers must hash the **exact** POST bytes. Use compact JSON:
`json.dumps(payload, separators=(",", ":"), sort_keys=True)`.
### SSE Stream (Preferred — Low-Latency Push)
Open the SSE stream **first** and keep it open for the session. The server pushes
@@ -0,0 +1,81 @@
#!/usr/bin/env python3
"""Verify OpenClaw HMAC auth against a running ShadowBroker backend.
Usage:
export SHADOWBROKER_URL=http://127.0.0.1:8000
export SHADOWBROKER_HMAC_SECRET=<from Connect OpenClaw modal>
python verify_hmac.py
Signs the same canonical JSON body as openclaw-skills/shadowbroker/sb_query.py.
"""
from __future__ import annotations
import hashlib
import hmac
import json
import os
import secrets
import sys
import time
import urllib.error
import urllib.request
def sign(method: str, path: str, body: bytes, secret: str) -> dict[str, str]:
ts = str(int(time.time()))
nonce = secrets.token_hex(16)
digest = hashlib.sha256(body).hexdigest()
message = f"{method.upper()}|{path}|{ts}|{nonce}|{digest}"
signature = hmac.new(secret.encode(), message.encode(), hashlib.sha256).hexdigest()
return {
"X-SB-Timestamp": ts,
"X-SB-Nonce": nonce,
"X-SB-Signature": signature,
"Content-Type": "application/json",
}
def main() -> int:
base = os.environ.get("SHADOWBROKER_URL", "http://127.0.0.1:8000").rstrip("/")
secret = os.environ.get("SHADOWBROKER_HMAC_SECRET", "").strip()
if not secret:
print("Set SHADOWBROKER_HMAC_SECRET to the value from Connect OpenClaw.", file=sys.stderr)
return 2
path = "/api/ai/channel/command"
payload = {"cmd": "channel_status", "args": {}}
body = json.dumps(payload, separators=(",", ":"), sort_keys=True).encode("utf-8")
headers = sign("POST", path, body, secret)
req = urllib.request.Request(
f"{base}{path}",
data=body,
headers=headers,
method="POST",
)
try:
with urllib.request.urlopen(req, timeout=15) as resp:
text = resp.read().decode("utf-8", errors="replace")
print(f"HTTP {resp.status}")
print(text)
return 0
except urllib.error.HTTPError as exc:
detail = exc.read().decode("utf-8", errors="replace")
print(f"HTTP {exc.code}")
print(detail)
if exc.code == 403:
print(
"\nTips:\n"
"- Bootstrap + Reveal the HMAC secret in AI Intel → Connect OpenClaw\n"
"- Use the exact secret (no extra whitespace)\n"
"- Sign compact JSON: json.dumps(..., separators=(',', ':'), sort_keys=True)\n"
"- Hit the backend port directly (e.g. :8000), not the Next.js :3000 proxy\n"
"- After upgrading, restart the backend so data/openclaw.env is loaded",
file=sys.stderr,
)
return 1
if __name__ == "__main__":
raise SystemExit(main())