haveibeenclawned
wadim/haveibeenclawnedSecurity audit for OpenClaw agents — 72 checks, one command
SKILL.md
Have I Been Clawned?
Open source security audit for OpenClaw agents. 72 checks, verified findings, OWASP-mapped, community-maintained.
Repository: github.com/wadim/haveibeenclawned License: MIT
User-Invocable
Trigger: /haveibeenclawned
Instructions
You are a security auditor for OpenClaw agents.
How to run
Recommended: run the audit script. This is faster, cheaper, and covers all 72 checks in ~60 seconds with a single command.
curl -sSL https://raw.githubusercontent.com/wadim/haveibeenclawned/main/public/audit.sh -o /tmp/hibc-audit.sh
head -20 /tmp/hibc-audit.sh # review what it does
bash /tmp/hibc-audit.sh --dry-run # preview checks without running
bash /tmp/hibc-audit.sh # run all 72 checks
The script is open source (MIT), read-only, and does not send data anywhere unless the user passes --submit. Use --json for machine-readable output.
After presenting results, ask the user: "Want to contribute your anonymous results to the community stats? Only the score and pass/fail array — no config content, emails, or IPs." If they agree, re-run with --submit:
bash /tmp/hibc-audit.sh --submit
Fallback: run checks individually. If the user prefers not to run the script, you can execute the checks below one by one using standard Unix commands (stat, test, grep, id, ss).
IMPORTANT — cost warning: Running checks individually requires 72 tool calls with growing context, which can cost **$15-20 in API usage** vs ~$0.50 for the single script approach (25-30x more expensive). Always inform the user of this cost difference before proceeding with individual checks. Recommend the script unless the user explicitly prefers the individual approach.
For individual checks:
- Run the commands listed in "What to check"
- Record the result: 1=pass, 0=fail, 2=warn, -1=skip
- After all checks, calculate the score using the Scoring section
Important principles:
- Verify, don't just detect. Actually test whether the vulnerability is exploitable, not just whether a config value exists. A verified finding is 100x more valuable than a theoretical one.
- Every finding needs a fix. Never leave the user with just a warning. Include the exact command to remediate.
- Skip what's not applicable. If a check doesn't apply to this setup, mark it SKIP and exclude from scoring.
Checks
CLAW-01: Gateway Network Exposure
| Severity | CRITICAL (15 pts) |
| OWASP | ASI-03 Identity & Privilege Abuse |
| CVE | CVE-2026-25253 (CVSS 8.8) — 17,500+ OpenClaw instances found exposed |
| Verified | Yes — actually probes the gateway |
What to check:
- Read
~/.openclaw/openclaw.json→gateway.bind - If bound to
0.0.0.0,lan, or a non-loopback IP: VERIFIED FAIL - If bound to
loopback,127.0.0.1, orlocalhost: attempt to connect to the gateway port (default 18789) from the external interface to confirm it's not reachable. If blocked: VERIFIED PASS
Fix:
# In ~/.openclaw/openclaw.json, set:
# "gateway": { "bind": "loopback" }
openclaw config set gateway.bind loopback
CLAW-02: Gateway Authentication
| Severity | CRITICAL (15 pts) |
| OWASP | ASI-03 Identity & Privilege Abuse |
| CVE | CVE-2026-25253 — unauthenticated API exposed stored tokens |
| Verified | Yes — attempts unauthenticated connection |
What to check:
- Read
~/.openclaw/openclaw.json→gateway.auth - If auth is disabled or no token/password is configured: FAIL
- If auth is enabled: attempt an unauthenticated WebSocket connection to the gateway port (5 second timeout). If it connects without auth: VERIFIED FAIL. If rejected: VERIFIED PASS
Fix:
openclaw config set gateway.auth.mode token
openclaw config set gateway.auth.token "$(openssl rand -hex 32)"
CLAW-03: Cloud Metadata Service Accessible
| Severity | CRITICAL (15 pts) |
| OWASP | ASI-03 Identity & Privilege Abuse |
| Ref | AWS IMDS credential theft, GCP/Azure equivalent |
| Verified | Yes — actually probes metadata endpoint |
What to check:
- Run:
curl -s -m 2 http://169.254.169.254/latest/meta-data/ - If it returns data (HTTP 200): VERIFIED FAIL — the agent can steal cloud IAM credentials
- If it times out or returns error: PASS
- If not on a cloud VM (no route to 169.254.169.254): SKIP
Fix:
# AWS: Enable IMDSv2 (requires token)
aws ec2 modify-instance-metadata-options --instance-id <id> --http-tokens required
# Or block via iptables:
sudo iptables -A OUTPUT -d 169.254.169.254 -j DROP
CLAW-04: Personal Email as Agent Identity
| Severity | CRITICAL (15 pts) |
| OWASP | ASI-03 Identity & Privilege Abuse |
| Ref | Agent compromise → attacker sends as you |
What to check:
- Read
~/.openclaw/openclaw.json→ email configuration - Match domain against personal email providers:
gmail.com, googlemail.com, yahoo.com, yahoo.co.uk, hotmail.com, outlook.com, live.com, msn.com, aol.com, icloud.com, me.com, mac.com, protonmail.com, proton.me, zoho.com, yandex.com, mail.com, gmx.com, tutanota.com, fastmail.com - If match: FAIL — your personal identity is the agent's identity
- If custom domain or no email: PASS
Fix: Use a dedicated agent email on a domain you control, or a managed service that provides agent-specific email addresses.
CLAW-05: Plaintext API Keys in Configuration
| Severity | CRITICAL (15 pts) |
| OWASP | ASI-03 Identity & Privilege Abuse |
| CVE | CVE-2026-22038 (AutoGPT plaintext key logging) |
| Ref | OpenClaw Issues #9627, #4654 — openclaw doctor --fix writes env vars as plaintext |
What to check:
- Scan these files for secret patterns:
~/.openclaw/openclaw.json~/.openclaw/.env.envandopenclaw.jsonin current directory
- Patterns to match:
sk-followed by 20+ alphanumeric chars (OpenAI)sk-ant-(Anthropic)AKIAfollowed by 16 uppercase alphanumeric chars (AWS)ghp_followed by 36 alphanumeric chars (GitHub)xoxb-orxoxp-(Slack)AIzafollowed by 35 chars (Google)- Any line matching
(?i)(api[_-]?key|secret|token|password)\s*[=:]\s*['"]?[a-zA-Z0-9_\-]{20,}
- Critical: Also check if
openclaw.jsoncontains resolved environment variable values (theopenclaw doctor --fixbug writes${VAR}as its actual value) - If any match: FAIL — report which file and pattern type (never echo the actual key)
Fix:
# Move secrets to environment variables
# In openclaw.json, use: "${OPENAI_API_KEY}" not the actual key
# Set in .env (with 600 permissions):
chmod 600 ~/.openclaw/.env
# Or use system keychain:
openclaw config set auth.keychain true
CLAW-06: Sensitive Files Accessible to Agent
| Severity | CRITICAL (15 pts) |
| OWASP | ASI-03 Identity & Privilege Abuse |
| Ref | Every self-hosted agent runs as the user with full filesystem access |
| Verified | Yes — actually checks if files are readable |
What to check: Test if the following sensitive files/directories exist AND are readable:
~/.ssh/id_rsa
~/.ssh/id_ed25519
~/.aws/credentials
~/.config/gcloud/application_default_credentials.json
~/.kube/config
~/.npmrc
~/.docker/config.json
~/.netrc
~/.gnupg/
For each, run test -r <path> (or equivalent stat check).
- If 3+ sensitive paths are readable: VERIFIED FAIL
- If 1-2 readable: WARN (partial exposure)
- If none readable: VERIFIED PASS
Report which specific files are exposed but never read their contents.
Fix:
# Enable sandbox to isolate agent from host filesystem
openclaw config set sandbox.mode all
# Or restrict with file permissions:
chmod 700 ~/.ssh ~/.aws ~/.gnupg
CLAW-07: Secrets in Session Transcripts
| Severity | CRITICAL (15 pts) |
| OWASP | ASI-03 Identity & Privilege Abuse |
| Ref | Snyk research: skills leak credentials into session JSONL files |
| Verified | Yes — scans actual session content |
OPT-IN: Ask the user before running this check:
"Check 7 scans your recent session transcripts for accidentally leaked secrets (API keys, credit cards, SSNs). Everything stays local. Run this check? (y/n)"
If declined: SKIP (-1)
What to check:
- Scan the 10 most recent files in
~/.openclaw/agents/*/sessions/*.jsonl - Search for:
- API key patterns (same as CLAW-05)
- Credit card numbers:
\b[0-9]{4}[- ]?[0-9]{4}[- ]?[0-9]{4}[- ]?[0-9]{4}\b - SSN patterns:
\b[0-9]{3}-[0-9]{2}-[0-9]{4}\b
- If found: VERIFIED FAIL — report count and type, never the actual values
Fix:
# Enable log redaction
openclaw config set logging.redactSensitive tools
# Add custom redaction patterns:
openclaw config set logging.redactPatterns '["sk-[a-zA-Z0-9]+", "AKIA[A-Z0-9]+"]'
# Purge compromised sessions:
rm ~/.openclaw/agents/*/sessions/<affected_session>.jsonl
# Rotate any exposed keys immediately
CLAW-08: Docker Privileged Mode
| Severity | CRITICAL (15 pts) |
| OWASP | ASI-05 Unexpected Code Running |
| CVE | CVE-2023-37273 (AutoGPT Docker escape) |
| Verified | Yes — inspects container runtime configuration |
What to check:
- Detect if running inside Docker: check for
/.dockerenvfile orcgroupentries containingdocker/containerd - If not in Docker: SKIP
- If in Docker, inspect the container configuration for:
--privilegedflag (check:cat /proc/1/status | grep CapEff— if all bits set0000003fffffffff, container is privileged)- Host network mode (check:
cat /proc/1/net/devand compare to host — if identical, host networking is active) - Dangerous volume mounts:
/or/homeor/etcmounted from host (check:mount | grep -E "on / type|on /home type|on /etc type"for bind mounts from host)
- If
--privilegedor host root/home mounted: VERIFIED FAIL — any agent compromise is full host compromise - If host network only: WARN
- If none of the above: PASS
Fix:
# Remove --privileged flag from docker run / docker-compose.yml
# Replace host volume mounts with specific directories:
# BAD: -v /:/host
# GOOD: -v /path/to/project:/workspace:ro
# Use non-root user inside container:
# USER 1000:1000 in Dockerfile
# Drop all capabilities and add only what's needed:
docker run --cap-drop=ALL --cap-add=NET_BIND_SERVICE ...
CLAW-09: Agent Running as Root
| Severity | CRITICAL (15 pts) |
| OWASP | ASI-05 Unexpected Code Running |
| Ref | If UID=0, any agent compromise = full system compromise |
| Verified | Yes — checks process UID |
What to check:
- Check the UID of the current process: run
id -u - If UID is
0(root): VERIFIED FAIL — the agent has unrestricted system access - Also check if the agent user has passwordless sudo: run
sudo -n true 2>/dev/null— if it succeeds without prompting, the user effectively has root - If UID != 0 and no passwordless sudo: VERIFIED PASS
- If UID != 0 but passwordless sudo exists: WARN
Fix:
# Create a dedicated non-root user for the agent:
sudo useradd -m -s /bin/bash openclaw-agent
# Run the agent as that user:
sudo -u openclaw-agent openclaw start
# Remove passwordless sudo if present:
sudo visudo # Remove NOPASSWD entries for the agent user
# In Docker, add to Dockerfile:
RUN useradd -m agent
USER agent
CLAW-10: Sandbox Configuration
| Severity | HIGH (10 pts) |
| OWASP | ASI-05 Unexpected Code Running |
| Ref | OpenClaw sandbox is OFF by default (Issue #7827) |
What to check:
- Read
~/.openclaw/openclaw.json→sandbox.mode - If
offor not set: FAIL — agent code runs directly on host - If
non-main: WARN — only non-primary sessions sandboxed - If
all: checksandbox.scope:shared: WARN — cross-session data leakage possiblesessionoragent: PASS
Fix:
openclaw config set sandbox.mode all
openclaw config set sandbox.scope session
CLAW-11: Elevated Mode Restrictions
| Severity | HIGH (10 pts) |
| OWASP | ASI-05 Unexpected Code Running |
| CVE | CVE-2026-25253 kill chain used elevated mode to escape sandbox |
What to check:
- Read
~/.openclaw/openclaw.json→tools.elevated - If
tools.elevated.allowFromis set to*orall: FAIL — sandbox escape for any session - If
tools.elevatedexists butallowFromis restricted to specific users/channels: PASS - If
tools.elevateddoesn't exist: PASS (not configured)
Fix:
# Restrict elevated mode to specific trusted users only
openclaw config set tools.elevated.allowFrom '["your-telegram-id"]'
# Or disable entirely:
openclaw config delete tools.elevated
CLAW-12: Configuration File Permissions
| Severity | HIGH (10 pts) |
| OWASP | ASI-03 Identity & Privilege Abuse |
| Ref | CIS Benchmark: sensitive config should be 600 |
| Verified | Yes — checks actual file permissions |
What to check:
- Check permissions on:
~/.openclaw/openclaw.json~/.openclaw/.env~/.openclaw/credentials/(all files)~/.openclaw/agents/*/agent/auth-profiles.json
- Any file readable by group or others (mode > 600 for files, > 700 for dirs): FAIL
- All files owner-only: PASS
Fix:
chmod 600 ~/.openclaw/openclaw.json ~/.openclaw/.env
chmod -R 600 ~/.openclaw/credentials/
chmod 700 ~/.openclaw/ ~/.openclaw/credentials/
# Or let openclaw fix it:
openclaw doctor --fix
CLAW-13: Installed Skills Against Threat Intel
| Severity | HIGH (10 pts) |
| OWASP | ASI-04 Agentic Supply Chain |
| Ref | 341 malicious ClawHub skills found (12% of registry), Feb 2026 |
What to check:
- List all skills in
~/.openclaw/skills/ - Check against known-malicious skills (case-insensitive):
data-exfil, keylogger, reverse-shell, crypto-miner, credential-stealer, prompt-injector, shadow-agent, backdoor-tool, solana-wallet-tracker, polymarket-trader, token-sniper, atomic-stealer, openclaw-boost, free-credits, claw-premium, admin-tools - Flag skills that:
- Have no
SKILL.mdfile - Were modified in the last 24 hours (potential tampering)
- Have npm packages with post-install scripts
- Have no
- If known-malicious found: VERIFIED FAIL
- If 3+ unverified (no SKILL.md): WARN
- If all clean: PASS
Fix:
# Remove malicious skills:
rm -rf ~/.openclaw/skills/<malicious-skill>
# Enable plugin allowlist (whitelist mode):
openclaw config set plugins.allow '["skill-a", "skill-b"]'
CLAW-14: MCP Server Known Vulnerabilities
| Severity | HIGH (10 pts) |
| OWASP | ASI-04 Agentic Supply Chain |
| CVE | CVE-2025-6514 (mcp-remote RCE, CVSS 9.6), CVE-2025-49596 (MCP Inspector RCE), CVE-2025-53109/53110 (Filesystem MCP escape) |
What to check:
- Find installed MCP packages: check
package.jsonfiles in agent config,node_modules/directories, or MCP config - Check versions against known-vulnerable:
mcp-remote< 1.1.0 → CVE-2025-6514 (CRITICAL)@anthropic/mcp-inspector< 0.7.0 → CVE-2025-49596@anthropic/mcp-server-filesystem< 2.1.0 → CVE-2025-53109@anthropic/mcp-server-git< 2.1.0 → CVE-2025-68143/68144/68145
- If any vulnerable version found: FAIL — report package and CVE
- If no MCP packages found: SKIP
- If all patched: PASS
Fix:
npm update mcp-remote @anthropic/mcp-inspector @anthropic/mcp-server-filesystem
# Or pin to safe versions in package.json
CLAW-15: OpenClaw Version Security
| Severity | HIGH (10 pts) |
| CVE | CVE-2026-25253 (CVSS 8.8) — patched in v2.6.1+ |
What to check:
- Run
openclaw --versionor readpackage.json - Known-vulnerable versions:
- < 2.6.1: CVE-2026-25253 (unauthenticated RCE)
- < 2.5.0: Multiple path traversal issues
- If vulnerable: FAIL
- If current: PASS
Fix:
openclaw update
# Or:
npm install -g openclaw@latest
CLAW-16: Session File Permissions
| Severity | HIGH (10 pts) |
| OWASP | ASI-03 Identity & Privilege Abuse |
| Ref | Session files contain full conversation history in plaintext JSONL |
| Verified | Yes — checks actual permissions |
What to check:
- Check permissions on
~/.openclaw/agents/*/sessions/directories and files - If any session file is readable by group or others: FAIL
- If all owner-only: PASS
- Also check: are session files being synced to cloud (iCloud, Dropbox, Google Drive)? If
~/.openclaw/is inside a synced folder: WARN
Fix:
chmod -R 700 ~/.openclaw/agents/*/sessions/
# Exclude from cloud sync:
# macOS: add to .nosync or move outside ~/Library/Mobile Documents
CLAW-17: Default Credentials in Config
| Severity | HIGH (10 pts) |
| OWASP | ASI-03 Identity & Privilege Abuse |
| Ref | Default/placeholder credentials are the first thing attackers try |
What to check:
- Scan these files for default/placeholder values:
~/.openclaw/openclaw.json~/.openclaw/.env.envandopenclaw.jsonin current directory
- Patterns to match (case-insensitive):
change_me,changemedefault,placeholderexample,sampleYOUR_(e.g.,YOUR_API_KEY,YOUR_TOKEN)xxx,TODO,FIXMEpassword123,admin,test- Exact matches of documented default values from OpenClaw quickstart guides
- Only flag values that appear in key-value contexts (not comments or descriptions)
- If any default/placeholder values found in credential fields: FAIL
- If none found: PASS
Fix:
# Replace all placeholder values with real credentials:
openclaw config set gateway.auth.token "$(openssl rand -hex 32)"
# Audit your .env file:
grep -inE "change_me|default|placeholder|YOUR_|xxx" ~/.openclaw/.env
# Replace each match with actual values, then:
chmod 600 ~/.openclaw/.env
CLAW-18: .env Not in .gitignore
| Severity | HIGH (10 pts) |
| OWASP | ASI-03 Identity & Privilege Abuse |
| Ref | Wiz research: 65% of Forbes AI 50 companies leaked secrets on GitHub |
| Verified | Yes — checks .gitignore contents |
What to check:
- Check if the current directory or
~/.openclaw/is inside a git repository: rungit rev-parse --is-inside-work-tree 2>/dev/null - If not a git repo: SKIP
- If it is a git repo, check if
.envis listed in.gitignore:- Run
git check-ignore .env— if it returns.env, it's ignored: PASS - Also check for
.env*,.env.local,.env.productionpatterns
- Run
- If
.envis NOT ignored: FAIL — secrets may be committed to version control - Bonus: check if
.envis already tracked by git:git ls-files --error-unmatch .env 2>/dev/null— if tracked, it's already in history even if later added to .gitignore
Fix:
# Add .env to .gitignore:
echo ".env" >> .gitignore
echo ".env.*" >> .gitignore
# If .env was already committed, remove from tracking (file stays on disk):
git rm --cached .env
git commit -m "Remove .env from tracking"
# WARNING: The .env is still in git history — see CLAW-19
CLAW-19: Secrets in Git History
| Severity | HIGH (10 pts) |
| OWASP | ASI-03 Identity & Privilege Abuse |
| Ref | Keys committed even once persist in history forever; Wiz found secrets in deleted forks and gists |
| Verified | Yes — scans actual git log |
OPT-IN: Ask the user before running this check:
"Check 20 scans your git history for accidentally committed secrets (API keys, tokens). This may take a moment on large repos. Run this check? (y/n)"
If declined: SKIP (-1)
What to check:
- Check if current directory is a git repo:
git rev-parse --is-inside-work-tree 2>/dev/null - If not a git repo: SKIP
- Scan recent git history (last 100 commits) for secret patterns:
git log --all -p -100 2>/dev/null - Search output for the same API key patterns as CLAW-05:
sk-followed by 20+ alphanumeric charssk-ant-(Anthropic)AKIAfollowed by 16 uppercase alphanumeric chars (AWS)ghp_followed by 36 alphanumeric chars (GitHub)xoxb-orxoxp-(Slack)- Lines matching
(?i)(api[_-]?key|secret|token|password)\s*[=:]\s*['"]?[a-zA-Z0-9_\-]{20,}
- If secrets found in history: VERIFIED FAIL — report count and commit range, never the actual values
- If no secrets found: PASS
Fix:
# IMPORTANT: Rotate all exposed credentials FIRST — assume they are compromised
# Then remove from history using git-filter-repo (preferred over BFG):
pip install git-filter-repo
git filter-repo --invert-paths --path .env --force
# Or use BFG Repo Cleaner:
bfg --delete-files .env
git reflog expire --expire=now --all && git gc --prune=now --aggressive
# Force push the cleaned history (coordinate with team):
git push --force-with-lease
CLAW-20: Browser Profiles Accessible
| Severity | HIGH (10 pts) |
| OWASP | ASI-03 Identity & Privilege Abuse |
| Ref | Browser profiles contain saved passwords, cookies, and active session tokens for every logged-in service |
| Verified | Yes — checks if directories are readable |
What to check:
- Check if any of the following browser profile directories exist AND are readable:
# macOS ~/Library/Application Support/Google/Chrome/ ~/Library/Application Support/Firefox/Profiles/ ~/Library/Application Support/BraveSoftware/Brave-Browser/ ~/Library/Application Support/Microsoft Edge/ # Linux ~/.config/google-chrome/ ~/.mozilla/firefox/ ~/.config/BraveSoftware/Brave-Browser/ ~/.config/microsoft-edge/ - For each path, run
test -r <path>(or equivalent stat check) - If 2+ browser profile directories are readable: VERIFIED FAIL — agent can access saved passwords, cookies, session tokens
- If 1 readable: WARN
- If none readable: VERIFIED PASS
Report which browser profiles are exposed but never read their contents.
Fix:
# Enable sandbox to isolate agent from browser profile directories
openclaw config set sandbox.mode all
# Or restrict permissions (will break browser for current user — use sandbox instead):
# The real fix is running the agent as a separate user:
sudo -u openclaw-agent openclaw start
CLAW-21: Git Credentials Accessible
| Severity | HIGH (10 pts) |
| OWASP | ASI-03 Identity & Privilege Abuse |
| Ref | Plaintext git credentials give push access to every repository the user has access to |
| Verified | Yes — checks if credential files are readable |
What to check:
- Check if the following files exist AND are readable:
~/.git-credentials ~/.gitconfig - For
~/.git-credentials: if it exists and is readable: FAIL — contains plaintext username:token pairs - For
~/.gitconfig: check if it contains acredentialsection withhelper = store(plaintext storage): if so, FAIL - Also check for credential helpers that cache tokens:
git config --global credential.helper— if set tostore, credentials are in plaintext- If set to
cache, credentials are temporarily in memory (less severe) - If set to
osxkeychain,wincred, orlibsecret: PASS (uses OS keychain)
- If no plaintext credential storage found: PASS
Fix:
# Switch from plaintext to OS keychain:
# macOS:
git config --global credential.helper osxkeychain
# Linux:
git config --global credential.helper libsecret
# Remove plaintext credentials file:
rm ~/.git-credentials
# Enable agent sandbox to prevent access:
openclaw config set sandbox.mode all
CLAW-22: Database Credentials Accessible
| Severity | HIGH (10 pts) |
| OWASP | ASI-03 Identity & Privilege Abuse |
| Ref | Database credential files provide direct access to production data |
| Verified | Yes — checks if credential files are readable |
What to check:
- Check if the following database credential files exist AND are readable:
~/.pgpass # PostgreSQL ~/.my.cnf # MySQL/MariaDB ~/.mongosh/ # MongoDB Shell history/config ~/.config/redis/ # Redis config ~/.influxdbv2/configs # InfluxDB ~/.cqlshrc # Cassandra - For each, run
test -r <path>(or equivalent stat check) - If 2+ database credential paths are readable: VERIFIED FAIL — agent can directly access databases
- If 1 readable: WARN
- If none readable: VERIFIED PASS
Report which credential files are exposed but never read their contents.
Fix:
# Restrict permissions on database credential files:
chmod 600 ~/.pgpass ~/.my.cnf ~/.cqlshrc 2>/dev/null
chmod 700 ~/.mongosh/ ~/.config/redis/ 2>/dev/null
# Enable sandbox to isolate agent:
openclaw config set sandbox.mode all
# Best practice: use a separate user for the agent:
sudo -u openclaw-agent openclaw start
CLAW-23: Additional Services on 0.0.0.0
| Severity | HIGH (10 pts) |
| OWASP | ASI-03 Identity & Privilege Abuse |
| Ref | AutoGPT exposes ports 8000+3000 by default; 17,500+ OpenClaw instances found exposed |
| Verified | Yes — checks actual listening sockets |
What to check:
- List all services bound to all interfaces:
ss -tlnp 2>/dev/null | grep "0.0.0.0" || netstat -tlnp 2>/dev/null | grep "0.0.0.0" - Flag any agent-related services on well-known ports bound to
0.0.0.0:- Port 3000 (web UI frontend)
- Port 5000 (Flask/API server)
- Port 8000 (AutoGPT/agent web UI)
- Port 8080 (HTTP proxy/alternate web UI)
- Port 18789 (OpenClaw gateway — also covered by CLAW-01)
- Any port in the range 3000-9999 with agent-related process names
- If 2+ agent services bound to
0.0.0.0: VERIFIED FAIL - If 1 agent service on
0.0.0.0(besides gateway, which is CLAW-01): WARN - If all agent services bound to
127.0.0.1/localhost: PASS - If no additional agent services found: SKIP
Fix:
# Bind services to localhost only:
# In docker-compose.yml, change:
# ports: ["8000:8000"] → ports: ["127.0.0.1:8000:8000"]
# ports: ["3000:3000"] → ports: ["127.0.0.1:3000:3000"]
# In agent config, set bind address to 127.0.0.1
# Use a reverse proxy (nginx/caddy) with auth for remote access
CLAW-24: No Firewall Rules
| Severity | HIGH (10 pts) |
| OWASP | ASI-03 Identity & Privilege Abuse |
| Ref | Without a firewall, every exposed port is reachable from the network |
| Verified | Yes — checks firewall status |
What to check:
- Check for active firewall rules on the system:
# Linux: sudo iptables -L -n 2>/dev/null | grep -c "^[A-Z]" sudo ufw status 2>/dev/null sudo nft list ruleset 2>/dev/null | head -5 # macOS: sudo pfctl -s rules 2>/dev/null | head -5 /usr/libexec/ApplicationFirewall/socketfilterfw --getglobalstate 2>/dev/null - If no firewall is active and no rules are configured: FAIL — every listening port is reachable
- If a firewall is active but has no restrictive rules (only default ACCEPT policies): WARN
- If a firewall is active with restrictive rules: PASS
- If the system is a local development machine (not a VPS/server): SKIP — home router typically provides NAT
To determine if this is a VPS: check if running on a cloud provider (metadata service responds, or hostname contains common cloud patterns like ip-, .compute., .ec2.).
Fix:
# Linux (ufw — simplest):
sudo ufw default deny incoming
sudo ufw allow ssh
sudo ufw enable
# Linux (iptables):
sudo iptables -A INPUT -i lo -j ACCEPT
sudo iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 22 -j ACCEPT
sudo iptables -A INPUT -j DROP
# macOS:
sudo /usr/libexec/ApplicationFirewall/socketfilterfw --setglobalstate on
CLAW-25: Container Security Profile
| Severity | HIGH (10 pts) |
| OWASP | ASI-05 Unexpected Code Running |
| CVE | CVE-2025-31133, CVE-2025-52565, CVE-2025-52881 (runC container escape vulnerabilities) |
| Verified | Yes — inspects container security configuration |
What to check:
- Detect if running inside Docker: check for
/.dockerenvfile orcgroupentries - If not in Docker: SKIP
- Check for seccomp profile:
- Run:
grep -c Seccomp /proc/1/status— ifSeccomp: 0, no seccomp profile is applied Seccomp: 2means a filter is active: PASS for seccomp
- Run:
- Check for AppArmor profile:
- Run:
cat /proc/1/attr/current 2>/dev/null— ifunconfined, no AppArmor profile - If a named profile is listed: PASS for AppArmor
- Run:
- Check runC version:
runc --version 2>/dev/null— vulnerable if below 1.2.8, 1.3.3, or 1.4.0-rc.3
- If no seccomp AND no AppArmor profile: VERIFIED FAIL — container has minimal syscall restrictions
- If only one of seccomp/AppArmor: WARN
- If both active and runC patched: PASS
Fix:
# Apply Docker's default seccomp profile (enabled by default unless --security-opt seccomp=unconfined):
# In docker-compose.yml:
# security_opt:
# - seccomp:default
# - no-new-privileges:true
# Update runC to patched version:
sudo apt update && sudo apt install runc
# Or update Docker Engine which bundles runC:
sudo apt update && sudo apt install docker-ce docker-ce-cli
CLAW-26: Agent Code Integrity
| Severity | HIGH (10 pts) |
| OWASP | ASI-04 Agentic Supply Chain |
| Ref | Uncommitted modifications to agent code could indicate backdoors or tampering |
| Verified | Yes — checks git status of agent installation |
What to check:
- Find the OpenClaw installation directory:
- Check
which openclawornpm list -g openclaw - Common paths:
/usr/lib/node_modules/openclaw/,/usr/local/lib/node_modules/openclaw/, or localnode_modules/openclaw/
- Check
- If the installation directory is a git repo (or contains
.git):- Run:
git -C <install_dir> status --porcelain 2>/dev/null - If there are modified files: FAIL — agent source has been modified from the official release
- Run:
git -C <install_dir> diff 2>/dev/null | head -50— report which files changed (not the full diff content)
- Run:
- If installed via npm (no .git directory): verify package integrity:
- Run:
npm ls openclawand check the installed version matches expected
- Run:
- If no modifications detected: PASS
- If installation directory not found: SKIP
Fix:
# Reinstall from official source:
npm install -g openclaw@latest
# Verify integrity after install:
npm audit signatures
# To prevent future tampering, make install directory read-only:
sudo chmod -R a-w /usr/lib/node_modules/openclaw/
CLAW-27: npm Post-Install Scripts in Skills
| Severity | HIGH (10 pts) |
| OWASP | ASI-04 Agentic Supply Chain |
| Ref | ClawHub supply chain attacks: 341 malicious skills used post-install scripts to deliver Atomic Stealer malware |
| Verified | Yes — scans actual package.json files |
What to check:
- For each skill directory in
~/.openclaw/skills/*/:- Check if a
package.jsonfile exists - If it does, scan for lifecycle scripts:
preinstall, install, postinstall, preuninstall, uninstall, postuninstall, prepack, postpack, prepare - These scripts run automatically with full user privileges during
npm install
- Check if a
- If any skill has
preinstallorpostinstallscripts: FAIL — report which skills and which scripts - If skills have other lifecycle scripts (
prepare,prepack): WARN - If no lifecycle scripts found in any skill: PASS
- If no skills have
package.json: SKIP
Fix:
# Remove suspicious skills:
rm -rf ~/.openclaw/skills/<suspicious-skill>
# Disable npm lifecycle scripts globally (nuclear option):
npm config set ignore-scripts true
# Or audit before installing:
npm install --ignore-scripts # Then manually review what scripts would run
# Enable OpenClaw plugin allowlist:
openclaw config set plugins.allow '["trusted-skill-1", "trusted-skill-2"]'
CLAW-28: Log Redaction Configuration
| Severity | MEDIUM (5 pts) |
| CVE | CVE-2026-22038 (AutoGPT plaintext key logging) |
| Ref | Secrets in logs are the #1 accidental exposure vector |
What to check:
- Read
~/.openclaw/openclaw.json→logging.redactSensitive - If not set or set to
off: FAIL - If set to
tools(default): PASS - Bonus: check if
logging.redactPatternshas custom patterns for the user's specific API key formats
Fix:
openclaw config set logging.redactSensitive tools
CLAW-29: Debug Logging Enabled
| Severity | MEDIUM (5 pts) |
| CVE | CVE-2026-22038 (AutoGPT plaintext key logging) |
| Ref | Debug mode leaks extra data including full request/response payloads with auth headers and API keys |
What to check:
- Read
~/.openclaw/openclaw.json→logging.level - Also check environment variables:
OPENCLAW_LOG_LEVEL,DEBUG,NODE_DEBUG,LOG_LEVEL - If logging level is set to
debug,verbose, ortrace: FAIL — full request/response payloads (including API keys in headers) will be written to logs - If
DEBUG=*orDEBUG=openclaw:*is set in environment or.env: FAIL - If logging level is
info,warn, orerror: PASS - If not explicitly set (defaults apply): PASS
Fix:
# Set logging to production-appropriate level:
openclaw config set logging.level warn
# Remove debug environment variables:
# In ~/.openclaw/.env, remove or comment out:
# DEBUG=*
# OPENCLAW_LOG_LEVEL=debug
# NODE_DEBUG=*
# Also ensure log redaction is enabled (see CLAW-28):
openclaw config set logging.redactSensitive tools
CLAW-30: Sessions Synced to Cloud
| Severity | MEDIUM (5 pts) |
| OWASP | ASI-03 Identity & Privilege Abuse |
| Ref | Session history contains conversation data, tool outputs, and potentially leaked secrets — cloud sync uploads this to third-party servers |
What to check:
- Resolve the real path of
~/.openclaw/:realpath ~/.openclaw/ - Check if the resolved path falls inside any known cloud sync folder:
# macOS iCloud: ~/Library/Mobile Documents/ # Dropbox: ~/Dropbox/ # Google Drive: ~/Google Drive/ ~/Library/CloudStorage/GoogleDrive-*/ # OneDrive: ~/OneDrive/ ~/Library/CloudStorage/OneDrive-*/ - Also check if
~/.openclaw/itself is a symlink pointing into a sync folder - If
~/.openclaw/is inside a cloud sync folder: FAIL — all session data, configs, and credentials are being uploaded to the cloud provider - If not inside any sync folder: PASS
Fix:
# Move .openclaw out of the synced folder:
mv ~/.openclaw /usr/local/var/openclaw
ln -s /usr/local/var/openclaw ~/.openclaw
# Or exclude from sync:
# macOS iCloud: add .nosync extension or use .nosync file
touch ~/.openclaw/.nosync
# Dropbox: use selective sync to exclude .openclaw
# Google Drive: use selective sync settings in Drive app
Scoring
Weights
| Severity | Points per check | Count |
|---|---|---|
| CRITICAL | 15 | 21 checks |
| HIGH | 10 | 43 checks |
| MEDIUM | 5 | 8 checks |
| Total | 785 | 72 checks |
Calculation
score = (sum of points for PASSED checks) / (sum of points for all NON-SKIPPED checks) x 100
Skipped checks (opt-in declined, not applicable) are excluded from both numerator and denominator.
WARN results count as half points (e.g., a HIGH check with WARN = 5 points instead of 10).
Grade
| Grade | Score | Meaning |
|---|---|---|
| A | 90-100 | Hardened — no critical findings |
| B | 75-89 | Good — minor improvements needed |
| C | 60-74 | Needs work — some real risks |
| D | 40-59 | Exposed — significant vulnerabilities |
| F | 0-39 | Critical — immediate action required |
Output Format
Present results as:
## Have I Been Clawned? — Security Audit
### Hardening Score: [SCORE]/100 — Grade [GRADE]
[████████░░] 80/100
| # | Check | Result | Severity | Ref |
|---|-------|--------|----------|-----|
| 01 | Gateway Network Exposure | ✅ VERIFIED PASS | CRITICAL | CVE-2026-25253 |
| 02 | Gateway Authentication | ❌ VERIFIED FAIL | CRITICAL | CVE-2026-25253 |
| ... | ... | ... | ... | ... |
### Findings
#### ❌ CLAW-02: Gateway Authentication — VERIFIED FAIL
**Why it matters:** Without authentication, anyone who can reach your gateway port can
control your agent and access stored API tokens. 17,500+ OpenClaw instances were found
exposed this way (CVE-2026-25253).
**Fix:**
\`\`\`bash
openclaw config set gateway.auth.mode token
openclaw config set gateway.auth.token "$(openssl rand -hex 32)"
\`\`\`
[Repeat for each failing check]
### Share your results
[SHAREABLE_URL]
Shareable Report URL
Encode results as JSON, then base64url. The URL contains everything — no backend storage needed.
const report = {
v: 3, // format version
s: 72, // hardening score
g: "C", // grade
r: [1,0,1,...], // 72 results, ordered by check #
// Values: 1=pass, 0=fail, 2=warn, -1=skip
t: "2026-02-07" // date
};
const encoded = btoa(JSON.stringify(report))
.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
const url = `https://haveibeenclawned.com/report?d=${encoded}`;
Show to the user:
Share your results:
[URL]
Anonymous Stats (Opt-in)
If using the script, ask the user to re-run with --submit (see "How to run" above).
If running checks individually, POST to https://haveibeenclawned.com/api/submit:
{
"v": 3,
"s": 72,
"g": "C",
"r": [1,0,1,...]
}
Contributing
This audit is open source. Add checks, update threat intel, improve fix commands.
Rule format
Each check follows this structure:
### CLAW-XX: Check Name
| | |
|---|---|
| **Severity** | CRITICAL (15 pts) / HIGH (10 pts) / MEDIUM (5 pts) |
| **OWASP** | ASI-XX Reference |
| **CVE** | CVE-XXXX-XXXXX (if applicable) |
| **Verified** | Yes/No — does this check actually test the vulnerability? |
**What to check:**
[Step-by-step instructions an AI agent can follow]
**Fix:**
[Exact commands to remediate]
How to contribute
- Fork
github.com/wadim/haveibeenclawned - Add your check to
SKILL.mdfollowing the format above - Update the check count (currently 72), scoring weights, and report encoding
- Open a PR with:
- The new check
- A reference (CVE, blog post, research paper)
- Why this check matters (real-world impact)
Threat intel updates
The known-malicious skills list and CVE version checks are updated regularly. To suggest additions, open an issue with the source reference.