A hardened deployment wrapper that makes OpenClaw safe to self-host. Wraps an unmodified OpenClaw instance with authentication, skill scanning, prompt injection mitigation, pre-execution governance, webhook integrations, network isolation, and full audit logging β without changing a single line of OpenClaw code.
Why This Exists
OpenClaw is a powerful AI agent that can install and run third-party "skills" (JavaScript/TypeScript plugins). Running it on your infrastructure introduces real risks:
| Risk | What could happen | How we stop it |
|------|-------------------|----------------|
| Malicious skills | A skill runs dynamic code, spawns child processes, or exfiltrates data | AST-based scanner detects dangerous patterns before skills execute |
| Prompt injection | User input tricks the LLM into ignoring instructions | Regex sanitizer strips or rejects known injection patterns |
| Indirect injection | Tool results contain hidden instructions that hijack the agent | Plugin hook scans tool output before it enters agent context |
| Unauthorized access | Anyone on the network can use your OpenClaw instance | Bearer token auth on every request (constant-time comparison) |
| Data exfiltration | Skills phone home to attacker-controlled servers | DNS allowlisting blocks outbound traffic to non-approved domains |
| Uncontrolled tool execution | LLM executes dangerous tools without approval | Pre-execution governance with policy validation and human-in-the-loop approval |
| Webhook abuse | Attackers flood or spoof Telegram/WhatsApp endpoints | HMAC signature verification, replay protection, rate limiting, body-size limits |
| No audit trail | You can't tell what happened or when | Every security event logged to append-only JSON Lines |
Key principle: OpenClaw itself is never modified. The security stack operates as a reverse proxy in front with prompt sanitization, auth, governance, and audit logging.
How It Works
Main Proxy Pipeline
Every API request passes through a multi-stage security pipeline before reaching OpenClaw:
Auth Middleware (src/proxy/auth_middleware.py) β Validates Authorization: Bearer <token> using constant-time comparison. Public paths (/health) and registered webhook paths (which use their own HMAC auth) are exempt. Returns 401/403 on failure.
Governance Evaluation (src/governance/middleware.py) β For requests containing tool calls, classifies intent, generates an execution plan, validates against configurable policies, and decides: ALLOW, BLOCK, or REQUIRE_APPROVAL. High-risk operations (file writes, code execution, system commands) can require human approval before proceeding.
Prompt Sanitizer (src/sanitizer/sanitizer.py) β Scans request body for prompt injection patterns. Depending on the rule, either strips the offending text (cleaning it) or rejects the entire request with HTTP 400.
Forward to OpenClaw β The sanitized, governance-approved request is forwarded via httpx to the upstream OpenClaw instance. Streaming (SSE) is supported with a 5-minute timeout.
Response Scanner β Scans OpenClaw's response for indirect injection patterns (e.g., hidden instructions embedded in tool results). Findings are logged as audit events and flagged via the X-Prompt-Guard header.
Audit Logger (src/audit/logger.py) β Every security decision is recorded as an append-only JSON Lines event: auth success/failure, injection detections, governance decisions, webhook events, and more.
Webhook Pipeline
Telegram and WhatsApp messages go through a dedicated relay pipeline that mirrors the main proxy's security stages:
Body Size Check β Raw body size validated before JSON parsing to prevent OOM from oversized payloads (10MB limit, returns 413).
Sanitization β Same prompt injection scanner as the main pipeline.
Governance β Evaluates the message against governance policies. BLOCK returns 403, REQUIRE_APPROVAL returns 202.
Quarantine β Checks if the referenced skill (if any) is quarantined.
Forward & Response Scan β Translates to OpenAI-format request, forwards to OpenClaw, scans response.
Platform Reply β Sends the response back via the Telegram/WhatsApp API.
Plugin Hook System
β οΈ Essential Component: The prompt-guard plugin is automatically installed by install.sh and is required for protection against indirect prompt injection attacks.
The TypeScript plugin (plugins/prompt-guard/index.ts) runs inside OpenClaw itself as a defense-in-depth layer:
tool_result_persist hook β Scans tool results (web pages, API responses, files) for indirect injection patterns before they enter the agent's context window. Matching patterns are stripped or flagged based on rules in config/indirect-injection-rules.json.
before_tool_call hook β Verifies that governance headers (x-governance-plan-id, x-governance-token) are present before allowing high-risk tool calls (exec, shell, file_write). Falls back to local quarantine list enforcement.
Why it's essential: The proxy can only see direct user input. When OpenClaw autonomously calls tools (web search, file read), the plugin is the only layer that can intercept malicious instructions embedded in those results.
Offline Scanning Pipeline
Skills are scanned before they execute using tree-sitter AST analysis:
AST Parsing β tree-sitter parses JavaScript/TypeScript into a syntax tree
The audit checks: container hardening, network isolation, secret management, log integrity, skill security, and documentation completeness.
Troubleshooting
| Symptom | Cause | Resolution |
|---------|-------|------------|
| Stack won't start | Docker not running or wrong version | Check docker --version >= 20.10 and Docker daemon is running |
| 401 on all requests | Token mismatch between client and proxy | Verify Authorization: Bearer <token> matches OPENCLAW_TOKEN in .env; restart proxy after changes |
| 403 on tool call | Governance blocked the request | Check governance policies in config/governance-policies.json; approve via governance API if needed |
| 202 on webhook message | Governance requires approval | Approve the pending request via the governance approval API |
| DNS resolution fails | CoreDNS not healthy | Check docker compose logs egress-dns; verify config/allowlist.db exists |
| Certificate errors | Caddy can't issue cert | Verify domain DNS points to this host; check docker compose logs caddy |
| Skill quarantined unexpectedly | Scanner detected suspicious patterns | Review findings with uv run python -m src.scanner.cli quarantine list; override with quarantine override if false positive |
| Audit log not writing | Volume permissions | Check ./.local-volumes/proxy-data mount; ensure container UID 65534 can write |
| scripts/audit.py exits 2 | Docker/Podman not found | Install Docker >= 20.10 or Podman |
| 429 on webhook | Rate limit exceeded | Increase WEBHOOK_RATE_LIMIT in .env or wait for window to reset |
| 413 on webhook | Request body too large | Webhook payloads are limited to 10MB |
Maintenance / Rebuild Strategy
When to Rebuild
Monthly: Rebuild images to pick up base image security patches
On CVE: When a base image CVE is published affecting python:3.12-slim or coredns:1.11.1
On config change: After modifying config/scanner-rules.json or Dockerfile
How to Rebuild
# Full rebuild (no cache)
docker compose build --no-cache
# Ensure local bind-mount folders exist
mkdir -p .local-volumes/{proxy-data,openclaw-data,caddy-data,caddy-config}
# Restart services
docker compose up -d
Base Image Pinning
All Dockerfiles pin base images by SHA-256 digest. To update:
Pull the new image: docker pull python:3.12-slim
Get digest: docker inspect --format='{{index .RepoDigests 0}}' python:3.12-slim
Update the @sha256:... in the Dockerfile
Rebuild and test
Cloudflare Tunnel Deployment
For private server deployment with public HTTPS access without port forwarding, you can use Cloudflare Tunnel:
Benefits
No port forwarding needed β server stays on private network
Automatic HTTPS β Let's Encrypt certificates managed by Cloudflare
DDoS protection β traffic routes through Cloudflare's edge network
Hidden server IP β only outbound connections to Cloudflare
Zero trust security β WAF, rate limiting, and access control at the edge
Quick Start (Cloudflare Tunnel)
# 1. Add domain to Cloudflare and update nameservers
# 2. Install cloudflared on your server
curl -L https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64 -o /tmp/cloudflared
sudo install -m 755 /tmp/cloudflared /usr/local/bin/cloudflared
# 3. Authenticate with Cloudflare
cloudflared tunnel login
# 4. Create tunnel
cloudflared tunnel create openclaw
# 5. Configure tunnel (see detailed guide)
# 6. Route DNS
cloudflared tunnel route dns openclaw yourdomain.com
# 7. Install as service
sudo cloudflared service install
sudo systemctl enable --now cloudflared
# 8. Deploy OpenClaw (hybrid installer auto-configures tunnel)
cd /tmp/openclaw-secure-stack
sudo bash deploy/hybrid/install-hybrid.sh
Note: The hybrid installer includes integrated Cloudflare Tunnel setup! You can also run steps 2-7 manually if you prefer, then use the installer which will detect existing tunnel configuration.