🧰 Tooling Guide

OpenClaw Security Audit Guide: --deep, --fix, Suppressions, and Reading checkIds

•7 min read

openclaw security audit --fix can finish with a shorter report and leave every serious problem exactly where it was. Most of what it fixed was chmod.

The pages ranking for this command cover the flags well. They list what --fix refuses to touch, show some jq, and suggest a crontab line. What they skip is the part you need in week three, when the same two warnings appear on every run and you start ignoring the whole report. That part is suppressions. This guide covers running the audit, reading what it returns, the narrow job --fix does, and how to accept a finding on purpose without going blind to its cousins.

Plain Audit First, --deep When the Gateway Is Up

openclaw security audit
openclaw security audit --deep
openclaw security audit --deep --token <token>
openclaw security audit --auth password --password <password>

A plain run stays on the cold path. It reads config and the filesystem and does not load plugin runtimes, which keeps it quick enough to run after every config change (the docs recommend exactly that, plus once before you expose any network surface).

--deep adds best-effort live Gateway probes (the gateway.probe_* checks) and lets plugins run their own audit collectors. It also scans the code of installed plugins and skills, and those scans have budgets. Each plugin or skill gets up to 500 source files, anything over 1 MiB is skipped, and traversal stops at 100,000 directory entries. If you see plugins.code_safety.scan_truncated, read it literally: some files went unchecked. It says nothing about whether those files are dangerous.

If your Gateway password only exists as a startup argument, pass it with --auth password --password so the audit can compare it against hooks.token. Those flags apply to that one invocation and never rewrite config. SecretRefs resolve read-only; one that cannot resolve shows up under secretDiagnostics instead of crashing the run.

Reading a Finding

Every finding carries a checkId, and the prefix tells you where to look. fs.* is permissions. gateway.* is bind, auth, Tailscale and the Control UI. security.exposure.* is the interesting family, because it crosses who can reach the agent with what the agent can do. Channel checks are generated per channel, so channels.<provider>.dm.open arrives as channels.telegram.dm.open in real output.

Severity is info, warn or critical, and the catalog marks some checks warn/critical. Same ID, different weight depending on config, and the docs' own example is whether the Gateway is remotely exposed. A flag on a loopback-only box and the same flag on a LAN bind do not deserve the same evening of your time.

What --fix Actually Touches

openclaw security audit --fix
openclaw security audit --fix --json | jq '{fix: .fix.ok, summary: .report.summary}'

The audit checks catalog has an auto-fix column. Every row marked yes is an fs.* permission check. Of the 29 checks the table lists as critical-only, seven are auto-fixable, and all seven are file modes on ~/.openclaw, the config, include files, credentials or auth profiles. --fix sets files to 600 and directories to 700 (Windows gets icacls resets). Outside the catalog column it also flips groupPolicy="open" to allowlist and, for WhatsApp, seeds groupAllowFrom from your stored allowFrom list.

It does not rotate a single token. It will not disable exec or touch your bind. Every gateway.* critical is yours to fix by hand.

Run it anyway, first, every time. Permissions are boring and the fix is safe, and clearing them makes the leftover list honest. One caution: older copies of the reference (and pages built from them) say --fix also turns logging.redactSensitive from off to tools. The current reference does not list that, so check the setting yourself; the logging guide covers redaction. If a remediation fails, the command exits 1 before the audit even runs. Rerun the plain audit to see where you stand.

The Order I Fix Things In

The docs give a priority order, and I agree with the top of it. Anything open that also has tools comes first: channels.<provider>.dm.open and security.exposure.open_groups_with_elevated are both critical, because a stranger's message becomes a prompt that can reach a shell. Put the channel back on pairing or an allowlist, then narrow tools with the allow and deny lists.

Network exposure is second. gateway.bind_no_auth and gateway.tailscale_funnel mean the internet may be talking to your agent, and if you meant to share over a tailnet the Tailscale guide shows Serve without Funnel.

Then hooks.token_reuse_gateway_token, which I would move up the list if I could: your webhook token also unlocks the Gateway. openclaw doctor --fix rotates a persisted reused hook token (the doctor guide has the rest), and afterwards every external sender needs the new value.

My one disagreement with the docs is fs.synced_dir. It is only a warn, and it has no auto-fix, but state sitting in iCloud or Dropbox copies your transcripts and tokens to every device on that account. Move it before you worry about plugin pinning.

One Gateway, One Operator

If security.trust_model.multi_user_heuristic fires because two people who do not trust each other share your gateway, no setting in this report will satisfy it, and the docs' answer is a second gateway with its own credentials.

Suppressions: Accept a Finding Without Going Blind

Some warnings describe choices. tools.exec.security_full_configured is the usual one: the docs call security="full" on its own a broad posture warning, the chosen default for trusted-operator setups. If you made that choice deliberately (the exec approvals guide walks the tradeoff), seeing it on every run trains you to skim. Suppress it.

{
  "security": {
    "audit": {
      "suppressions": [
        {
          "checkId": "plugins.tools_reachable_permissive_policy",
          "detailIncludes": "Enabled extension plugins: gbrain",
          "reason": "trusted local operator plugin"
        }
      ]
    }
  }
}

The checkId must match exactly. titleIncludes and detailIncludes narrow it with case-insensitive substrings, and I would not write a suppression without one of them. A bare checkId also hides the next instance of that check, the one caused by a plugin you install in December and forget about.

Suppressed findings leave summary and findings but stay in the JSON under suppressedFindings. While any suppression is configured, the report also carries security.audit.suppressions.active, an info finding you cannot suppress. Dangerous flags are emitted one per finding, so accepting one dangerously* setting does not quietly cover a second one.

Write the reason for whoever reads the file next year. Add suppressions with a patch file, as described in the config CLI guide, and never suppress a critical. Also know the boundary: an agent running a shell command that edits suppressions goes through ordinary exec approval, and the docs say plainly that command-text inspection is not a config-write protection.

A Gate for Scripts

# fail the step if any active finding is critical
openclaw security audit --json \
  | jq -e '[.findings[] | select(.severity=="critical")] | length == 0'

# how much have we accepted?
openclaw security audit --json | jq '.suppressedFindings | length'

jq -e exits non-zero when the expression is false, which gives you a gate without depending on the audit's own exit code. Watch the second number. If it only goes up, the suppressions file has become a place where findings go to be forgotten. Schedule both with the cron setup you already have.

Common Failure Modes

--fix ran, criticals remain

Expected. Only permission checks auto-fix. Gateway, hook and exposure findings need config changes.

fs.config.symlink keeps coming back

No auto-fix exists. Replace the symlink with a regular file or point OPENCLAW_CONFIG_PATH at the real one.

Plain audit clean, --deep is not

Code scans and live probes only run with --deep. Plugin findings there come from checks a plain run never performs.

Sandbox browser hash label warnings

Old containers predate current labels. Run openclaw sandbox recreate --browser --all; the sandbox guide explains what that resets.

Final Verdict

Run the plain audit after every config change and --deep weekly with the gateway up. Let --fix handle permissions, then treat what remains as a to-do list ordered by exposure. Suppress deliberate choices narrowly, with a reason. The audit is good at telling you that you drifted. Deciding what counts as drift is still your job.

⚡

Ready to build?

Get the OpenClaw Starter Kit — config templates, 5 production-ready skills, deployment checklist. Go from zero to running in under an hour.

$14 $6.99

Get the Starter Kit →

Also in the OpenClaw store

🗂️
Executive Assistant Config
Buy
Calendar, email, daily briefings on autopilot.
$6.99
🔍
Business Research Pack
Buy
Competitor tracking and market intelligence.
$5.99
⚡
Content Factory Workflow
Buy
Turn 1 post into 30 pieces of content.
$6.99
📬
Sales Outreach Skills
Buy
Automated lead research and personalized outreach.
$5.99

Get the free OpenClaw quickstart guide

Step-by-step setup. Plain English. No jargon.