Published on

Validating Clawdbot Security with Shodan

Authors

Clawdbot 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

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

shodan search "clawdbot-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("clawdbot-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:

╔══════════════════════════════════════════════════════════════╗
CLAWDBOT 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 Clawdbot community has good security practices. Every instance tested either blocks external access or requires authentication. No unauthenticated instances were found.

Resources