Published on

Validating OpenClaw Security with Shodan

Authors

OpenClaw is an AI agent framework that runs Claude as an autonomous assistant. It uses a WebSocket gateway for communication, typically on port 18789. I wanted to validate that instances in the wild are properly secured.

Finding Instances with Shodan

OpenClaw uses mDNS for local service discovery, broadcasting as _OpenClaw-gw._tcp.local. Shodan indexes mDNS responses, so we can find instances with:

shodan search "OpenClaw-gw"

I wrote a scraper to collect and parse the data:

def parse_mdns_txt(banner_text: str) -> Dict[str, Any]:
    """Parse mDNS TXT records from Shodan banner"""
    data = {}
    for match in re.finditer(r'(\w+)=([^\n]+)', banner_text):
        key, value = match.groups()
        data[key.lower()] = value.strip()
    return data

def scrape_shodan():
    api = shodan.Shodan(SHODAN_API_KEY)
    instances = []

    for result in api.search_cursor("OpenClaw-gw"):
        instance = {
            "ip": result["ip_str"],
            "port": result["port"],
            "org": result.get("org"),
        }
        instance.update(parse_mdns_txt(result.get("data", "")))
        instances.append(instance)

    return instances

This found 200 instances, mostly on Hetzner (85%).

Testing WebSocket Authentication

Next, I probed each instance to verify authentication is required:

async def probe_websocket(ip: str, port: int) -> Dict[str, Any]:
    """Test if WebSocket requires authentication"""

    ssl_context = ssl.create_default_context()
    ssl_context.check_hostname = False
    ssl_context.verify_mode = ssl.CERT_NONE

    for url in [f"wss://{ip}:{port}/gw", f"ws://{ip}:{port}/gw"]:
        try:
            async with websockets.connect(url, ssl=ssl_context, open_timeout=10) as ws:
                msg = await asyncio.wait_for(ws.recv(), timeout=5)
                if "connect.challenge" in msg:
                    return {"status": "auth_required"}
                return {"status": "connected", "response": msg[:200]}
        except:
            continue

    return {"status": "unreachable"}

Results

StatusCountPercentage
Unreachable19296%
Auth Required84%
No Auth00%

All instances are properly secured. 96% have the port firewalled, and the remaining 4% require authentication via challenge-response:

{ "type": "event", "event": "connect.challenge", "payload": { "nonce": "..." } }

Self-Audit Script

I created a script to verify your own instance:

curl -sL https://gist.githubusercontent.com/AvasDream/020ca16d72226213aec94a3a7f2844e6/raw/self-audit.sh | bash

It checks gateway binding, external port access, mDNS status, authentication config, and file permissions:

╔══════════════════════════════════════════════════════════════╗
OpenClaw SECURITY SELF-AUDIT╚══════════════════════════════════════════════════════════════╝

1️⃣  GATEWAY BINDING
Gateway bound to localhost (127.0.0.1:18789)

2️⃣  EXTERNAL PORT ACCESS
Port 18789 closed externally

3️⃣  mDNS
Avahi not running

4️⃣  AUTHENTICATION
Gateway token configured

5️⃣  FILE PERMISSIONS
Config permissions: 600

SUMMARY:5 passed | ⚠️ 0 warnings |0 failed

Conclusion

The OpenClaw community has good security practices. Every instance tested either blocks external access or requires authentication. No unauthenticated instances were found.

Resources