If you only run one security command on your OpenClaw instance, make it openclaw doctor. The built-in audit walks your source checkout, running daemon, and gateway config to surface the ugly stuff that usually hides until an attacker finds it first. Below is exactly how I run the doctor on production and staging, how I read its sometimes noisy output, and the settings I change when it flags:
What the openclaw doctor command actually does
Before trusting any scanner you should know what it is doing under the hood. In 0.38.4 (current as I write) doctor runs three passes:
- Static analysis of
claw.config.json, environment variables, and file permissions. - Runtime probing against the local gateway REST API (
http://localhost:4221by default) and, if enabled, the daemon’s WebSocket port. - Dependency diff – it compares your installed Node modules against the project’s
security-advisories.json(sourced from GitHub-SecAlerts).
The tool never phones home. I double-checked with lsof -i and Wireshark; connections stay on 127.0.0.1. Good.
Running openclaw doctor on a live instance
You can point the doctor at any directory that contains claw.config.json. If you omit the path it walks the CWD. On a systemd-managed host I do:
# as the claw user
$ cd /srv/openclaw
$ NODE_ENV=production npx openclaw@0.38.4 doctor --format pretty
The flags I use most:
--format json|pretty|minimal(defaults to minimal)--severity low|medium|high(filters findings)--fix(experimental, modifies config in place; I avoid it in prod)
Expect the scan to finish in 10-20 seconds on a micro VPS and ~3 s on my M3 MacBook. CPU usage spikes to one full core because the dependency diff walks node_modules.
Reading the output without getting lost
The default minimal output is terse – too terse. I switch to pretty so I can see context:
Finding #2 [HIGH] Exposed gateway admin interface
Affected: http://0.0.0.0:4221
Remediation: Set GATEWAY_ADMIN_TOKEN or bind to 127.0.0.1
----------------------------------------
Every finding comes with six fields:
- ID – unique until restart.
- Severity – low, medium, high, or critical.
- Category – config, network, dependency, policy.
- Summary.
- Affected artifact.
- Remediation text.
If you pipe to --format json you can stuff it into SIEM. Example:
$ npx openclaw doctor --format json > /tmp/openclaw_audit.$(date +%s).json
Common issues the doctor flags and how I fix them
Below are the three findings I see most often in the field, plus the exact patches I apply.
1. Risky Direct-Message (DM) policies
OpenClaw ships with dmPolicy: "allow-first" so new users can DM the agent and automatically get sessions. Good for demos, bad for prod Slack or Discord workspaces where random joins happen daily.
Doctor flags it like:
Finding #4 [MEDIUM] Policy allows unrestricted one-to-one sessions
File: claw.config.json (line 42)
Value: "dmPolicy": "allow-first"
Fix: change to "invite-only" or "disabled"
I tighten policy:
{
"dmPolicy": "invite-only",
"allowedUsers": ["U02Q1ALPH4", "U08BRAVO9"]
}
Re-run doctor, the warning disappears.
Trade-off: New teammates need an explicit invite link or an admin to flip /openclaw invite @user. Slightly more overhead but worth it if your bot is wired into production deploy pipelines via Composio.
2. Misconfigured file system permissions
The installer npm i -g openclaw writes CLI binaries to wherever global NPM lives. People then run the gateway as root because they don’t want to mess with ports <1024. Doctor complains:
Finding #1 [HIGH] Process running as UID 0 (root)
Context: systemd unit openclaw-gateway.service
Fix: run as non-privileged user and use setcap or a reverse proxy
My fix is two-step:
- Create a user:
# useradd --system --home /srv/openclaw --shell /usr/sbin/nologin claw - Bind gateway to 127.0.0.1:4221 and let Caddy handle :443 TLS termination.
Unit file excerpt:
[Service]
User=claw
Group=claw
ExecStart=/usr/bin/env NODE_ENV=production /usr/bin/openclaw gateway --bind 127.0.0.1 --port 4221
AmbientCapabilities=CAP_NET_BIND_SERVICE
I give the binary capability with:
# setcap 'cap_net_bind_service=+ep' /usr/bin/node
Doctor becomes silent on UID once I restart the service.
3. Exposed admin interface
By default the admin panel is behind a random 32-char token printed on first boot. Too many users lose that token and simply set GATEWAY_ADMIN_TOKEN= to disable auth. Terrifying.
Doctor catches it:
Finding #2 [CRITICAL] Admin interface unauthenticated
Endpoint: http://0.0.0.0:4221/admin
Fix: export GATEWAY_ADMIN_TOKEN= or bind to 127.0.0.1
I fix with:
# generate a token
a=$(openssl rand -base64 32)
# persist
sudo tee -a /etc/openclaw/env <<EOF
GATEWAY_ADMIN_TOKEN="$a"
EOF
# restart
sudo systemctl restart openclaw-gateway
Optional: also set GATEWAY_ADMIN_IP_WHITELIST=10.0.0.0/24,127.0.0.1/32 for belt and suspenders.
Fixing dependency vulnerabilities flagged by doctor
OpenClaw depends on puppeteer, express, @composio/sdk, and ±184 other packages. Rather than parse the doctor-produced diff by hand I pipe to --format json and feed jq:
$ npx openclaw doctor --severity high --format json | jq -r '.findings[] | "\(.summary) => npm audit fix \(.metadata.package)@^\(.metadata.fixedVersion)"'
- Run
npm audit fixfor non-breaking patches. - For semver-major bumps I pin via
overridesinpackage.jsonand schedule a sandbox redeploy to run integration tests.
Once everything is green, I commit package-lock.json and push to the deploy branch. CI executes npx openclaw doctor as a gate.
Advanced: Automating doctor in CI/CD
Because the command doesn’t need Docker-level privileges, it’s safe in CI. My GitHub Actions snippet:
- name: Security audit (OpenClaw doctor)
run: |
npm ci --omit=optional
npx openclaw doctor --severity medium --format json > doctor.json
cat doctor.json | jq 'select(.findings | length > 0) and .findings[] | select(.severity == "high" or .severity == "critical")' && exit 1 || echo "No blocking findings"
The step fails if any high/critical remains. Engineers fix the PR or add // doctor:ignore FND-1023 comment next to the offending config line. (The parser strips comments before loading JSON, so they don’t leak to runtime.)
Checklist: what to verify after every scan
- No findings labelled critical or high.
- UID/GID is not 0.
- Admin interface bound to localhost or protected by strong token.
dmPolicyisinvite-onlyor stricter.shellAccessis disabled unless you absolutely need it (doctor flags this as medium).- Composio API keys rotated in the last 90 days (doctor reads the timestamp if you stored via
claw secrets).
Takeaway: make openclaw doctor part of muscle memory
The doctor command isn’t perfect—edge-case false positives for air-gapped hosts still happen—but it’s the fastest way to catch 90 % of self-inflicted wounds. Hook it into CI, keep an eye on new rules in each release, and your future self will have fewer 3 a.m. incident reviews.
If you hit an unclear finding, open an issue on GitHub (#security-label). The core team merges rule tweaks fast, and you’ll help make the scan better for the rest of us.
Next step: schedule a weekly cron job on your production boxes—npx openclaw doctor --format minimal | logger -t openclaw-doctor—and alert on anything above medium. Your audit trail will thank you.