You landed here because you typed “how to secure OpenClaw” into a search bar. Same. Last month I migrated three production agents from a pet server under my desk to a public VPS and needed a no-nonsense hardening recipe. I couldn’t find one, so I wrote the steps down while they were still fresh. Everything below has been run on Ubuntu 22.04 LTS with OpenClaw v2.9.1 (Node 22.2.0). The checklist format is intentional—copy, paste, adapt.
Skill prerequisites and threat model
You don’t need to be a security engineer, but you should:
- Know how to
sshinto a box and edit files withnanoorvim. - Understand basic UNIX permissions (
chmod,chown). - Be comfortable restarting a systemd service or a Docker container.
The goal is to keep an attacker who compromises an agent prompt from pivoting into the rest of the machine—or worse, the rest of your infrastructure.
Use a dedicated non-root user
OpenClaw does not need root. Running the gateway as root multiplies the blast radius if the LLM hallucinates a malicious shell command.
- Create a system account:
sudo adduser --system --group --home /opt/openclaw oc
- Move your install tree:
sudo mv /path/where/you/extracted/openclaw /opt/openclaw/gateway
sudo chown -R oc:oc /opt/openclaw
- Systemd unit (save as
/etc/systemd/system/openclaw.service):
[Unit]
Description=OpenClaw Gateway
After=network.target
[Service]
Type=simple
User=oc
Group=oc
WorkingDirectory=/opt/openclaw/gateway
ExecStart=/usr/bin/node --no-warnings index.js
Restart=always
[Install]
WantedBy=multi-user.target
- Enable and start:
sudo systemctl daemon-reload
sudo systemctl enable --now openclaw
If you only do one thing on this page, do this.
Read-only file access for messages and memory
By default the gateway writes everything under its working directory. We can leverage UNIX ACLs to make most of the tree read-only and keep only the dynamic bits writable.
- Create writable sub-dirs:
sudo -u oc mkdir -p /opt/openclaw/gateway/{logs,memory,tmp}
- Lock the rest:
sudo chown -R root:root /opt/openclaw/gateway
sudo chmod -R 755 /opt/openclaw/gateway
sudo setfacl -m u:oc:rwx /opt/openclaw/gateway/logs
sudo setfacl -m u:oc:rwx /opt/openclaw/gateway/memory
sudo setfacl -m u:oc:rwx /opt/openclaw/gateway/tmp
The agent can still mutate memory and temporary files, but any attempted code write or dependency injection fails fast.
Container isolation with Docker (optional but nice)
If you prefer reproducibility over raw performance, wrap the whole thing in a minimal container. The official image is ghcr.io/openclaw/openclaw:2.9.1, but rolling your own means you can drop Linux capabilities you don’t need.
Minimal Dockerfile
FROM node:22-alpine as base
RUN apk add --no-cache dumb-init
WORKDIR /app
COPY . ./
RUN npm ci --omit=dev && npm cache clean --force
EXPOSE 3000
USER node
ENTRYPOINT ["/usr/bin/dumb-init", "node", "index.js"]
docker-compose.yml with hardening flags
version: "3.9"
services:
openclaw:
build: .
restart: always
ports:
- "127.0.0.1:3000:3000" # front with nginx, no direct WAN
read_only: true
tmpfs:
- /tmp
cap_drop:
- ALL
security_opt:
- no-new-privileges:true
environment:
- NODE_ENV=production
The read_only: true flag pairs with a tmpfs mount so you still get /tmp. No capabilities, no problem. I run this behind nginx with mutual-TLS to keep bots out of the gateway port.
Run scanner.py after every deploy
OpenClaw ships with an opinionated audit script under tools/scanner.py. It inspects:
- Stale dev dependencies
- Writable files in code paths
- Missing .env vars marked as
required - Integration tokens with overly broad scopes (via the Composio API)
Usage
cd /opt/openclaw/gateway
python3 tools/scanner.py --output report.html
Read the HTML report in a browser, fix the reds, re-run until green. The script exits non-zero if any criticals remain, so you can wire it into CI:
- name: OpenClaw audit
run: python3 tools/scanner.py --output /tmp/report.html
We have a GitHub Action openclaw/audit@v1 internally that does this on pull request—but the plain script works anywhere.
Install the “vaccine” memory plug-in
Memory injection is the OpenClaw feature that scares most ops people: an LLM can store arbitrary strings that later get executed via shell or forwarded to third-party tools. The core team ships @openclaw/memory-vaccine, a policies-first drop-in that whitelists schema, strips executable code, and enforces size limits.
Installation
# still as oc user
yarn add @openclaw/memory-vaccine
Gateway config snippet
// gateway.config.js
module.exports = {
…
memory: {
driver: "vaccine",
limit: "1MB",
blockPatterns: ["/\\b(rm|unlink|curl|wget)\\b/"],
allowHTML: false
}
};
This blocks path traversal attempts that started showing up in the public demo channels last week (tracked as GHSA-claw-2024-04).
Lock down direct-message pairing policies
Every integration (Slack, Discord, Telegram, WhatsApp) has its own idea of “DM”. Agents that respond to anyone with the link are great for demos but terrible for prod.
- Create an allowlist:
openclaw gateway dm-pairing --allow user:U12345 --allow role:admin --deny-by-default
- Audit once a week:
openclaw gateway dm-pairing --list | grep -v "allowed" && \
echo "⚠️ unexpected users have DM access"
Slack Enterprise Grid users can merge this with SCIM to auto-revoke employees on exit. Community users on GitHub started a script for Telegram Chat IDs here.
Run openclaw doctor after patching
The CLI now ships a diagnostic command inspired by brew doctor. It checks:
- Node.js version >= 22
- Environment variables present and non-empty
- File permissions on known sensitive paths
- Whether you’re on the latest patch release
Sample output
$ openclaw doctor
✔ Node 22.2.0 — good
✔ .env loaded — 17 vars
✖ Writable file detected: /opt/openclaw/gateway/index.js
✖ Version 2.9.1 — 2.9.3 available
2 issues found — run 'openclaw doctor --fix' to auto-patch
I run doctor in a daily cron and send anything non-zero to PagerDuty:
0 3 * * * /usr/local/bin/openclaw doctor --quiet || cat /var/log/openclaw/doctor.log | mail -s "OpenClaw issues" ops@company.com
The quick-glance checklist
Print this, tape it near your monitor:
- [ ] Dedicated
ocuser, no root - [ ] Read-only app tree with writable
logs/ memory/ tmp/ - [ ] Docker container with
cap_drop: ALLandread_only: true(if you use Docker) - [ ] Passes
scanner.pywith 0 criticals - [ ]
memory-vaccinemodule enabled, 1 MB cap - [ ] DM pairing allowlist, default deny
- [ ] Daily
openclaw doctorcron
Next step: automate it
Humans forget. Machines don’t. Throw the above into Terraform, Ansible, or a GitHub Actions workflow so new nodes boot hardened by default. If you publish your scripts, drop a link on the #security channel in the OpenClaw Discord—there’s always someone testing the edge cases.