GitHub Advisoryadvisory · npm

GHSA-vqvg-86cc-cg83 OpenClaw: Mutating internal `/allowlist` chat commands missed `operator.admin` scope enforcement

MEDIUM Published 2026-03-30 Modified 2026-03-30 Official page ↗
Ecosystem
npm
Packages
openclaw
Severity
Moderate
CVSS vector
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:N
Weaknesses
CWE-862
Reviewed
2026-03-30

Affected packages

• npm openclaw: < 2026.3.24 → fixed in 2026.3.24

Description

> Fixed in OpenClaw 2026.3.24, the current shipping release. Title Mutating internal `/allowlist` chat commands missed `operator.admin` scope enforcement CWE CWE-862 Missing Authorization CVSS v3.1 CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:N Base score: 6.5 (Medium) Severity Assessment Medium. This is a real authorization flaw in OpenClaw’s internal control plane. The issue does not require host access, trusted local state tampering, or multi-tenant assumptions, but exploitation does require an already authenticated internal Gateway caller with `operator.write`. Impact An authenticated internal Gateway caller limited to `operator.write` can perform state-changing `/allowlist` actions without `operator.admin`, even though comparable mutating internal chat commands already require `operator.admin`. The reachable effects are persistent changes to config-backed `allowFrom` entries and pairing-store-backed allowlist entries. This is not a semantic-modeling complaint and not a generic “trusted operator can do things” claim. It is a missing authorization check inside OpenClaw’s own internal scope model, where peer mutating command surfaces already distinguish `operator.write` from `operator.admin`. Affected Component Verified against the latest published GitHub release tag `v2026.3.23` (`ccfeecb6887cd97937e33a71877ad512741e82b2`), published `2026-03-23T23:15:50Z`. Exact vulnerable path on the shipped tag: • `src/auto-reply/reply/commands-allowlist.ts:251-254` • `/allowlist` authorization uses only `rejectUnauthorizedCommand(...)`. • `src/auto-reply/reply/commands-allowlist.ts:386-524` • mutating config and pairing-store writes happen here, but there is no `requireGatewayClientScopeForInternalChannel(..., operator.admin, ...)`. Reachability and scope model: • `src/gateway/method-scopes.ts:94-109` • `chat.send` is a write-scoped method. • `src/gateway/server.chat.gateway-server-chat.test.ts:539-559` • existing runtime coverage proves `chat.send` routes slash commands without an agent run. • `src/auto-reply/command-auth.ts:574-577` • internal callers become `senderIsOwner` only when `GatewayClientScopes` includes `operator.admin`. Comparable internal mutating command paths already enforce `operator.admin`: • `src/auto-reply/reply/commands-config.ts:64-73` • `src/auto-reply/reply/commands-mcp.ts:89-96` • `src/auto-reply/reply/commands-plugins.ts:387-394` • `src/auto-reply/reply/commands-acp.ts:98-106` Version history: • Introduced by commit `555b2578a8cc6e1b93f717496935ead97bfbed8b` (`feat: add /allowlist command`) • Earliest released affected tag found: `v2026.1.20` • Latest released affected tag verified: `v2026.3.23` Technical Reproduction 1. Check out the shipped release tag `v2026.3.23`. 2. Use an internal command context with: • `Provider = "webchat"` • `Surface = "webchat"` • `GatewayClientScopes = ["operator.write"]` • `params.command.channel = "webchat"` 3. Route a slash command through `chat.send`. 4. Execute either of these mutating commands: • `/allowlist add dm channel=telegram 789` • `/allowlist add dm --store channel=telegram 789` 5. Confirm the command context is authorized but not owner-equivalent: • `isAuthorizedSender === true` • `senderIsOwner === false` 6. Observe that the commands still succeed and perform persistent writes. Demonstrated Impact The vulnerable handler performs real state mutation for a low-scope internal caller: • Config-backed mutation path: • `src/auto-reply/reply/commands-allowlist.ts:398-503` • reads the config snapshot, applies the edit, validates, and writes the updated config to disk. • Store-backed mutation path: • `src/auto-reply/reply/commands-allowlist.ts:479-485` • `src/auto-reply/reply/commands-allowlist.ts:513-518` • updates the pairing-store allowlist without any admin-scope gate. The result is successful persistence, not just a misleading success message. Environment • Product: OpenClaw • Verified shipped tag: `v2026.3.23` • Shipped tag commit: `ccfeecb6887cd97937e33a71877ad512741e82b2` • Published GitHub release time: `2026-03-23T23:15:50Z` • Verification date: `2026-03-24` Duplicate Check This is not a duplicate of: • `GHSA-pjvx-rx66-r3fg` • that advisory covered cross-account scoping in `/allowlist ... --store`, not missing internal `operator.admin` enforcement. • `GHSA-hfpr-jhpq-x4rm` • that advisory covered `/config` writes through `chat.send`, not `/allowlist`. • `GHSA-3w6x-gv34-mqpf` • same authorization class, but different command path (`/acp`, not `/allowlist`). In Scope Check This report is in scope under `SECURITY.md` because: • it does not rely on adversarial operators sharing one gateway host or config; • it does not target the HTTP compatibility endpoints that `SECURITY.md` explicitly treats as full operator-access surfaces; • it demonstrates a real authorization mismatch inside OpenClaw’s own internal control-plane scope model (`operator.write` vs `operator.admin`); • peer mutating internal chat commands already enforce `operator.admin`, so this is not a request for a new boundary but a missing check on an existing one. This is therefore a concrete authorization bug, not a trusted-operator hardening suggestion. Remediation Advice 1. Add `requireGatewayClientScopeForInternalChannel(..., allowedScopes: ["operator.admin"], ...)` to the mutating internal `/allowlist` paths. 2. Add regression coverage for both mutation modes: • internal `operator.write` must be rejected; • internal `operator.admin` must be allowed. 3. Cover both config-backed and store-backed writes. 4. Audit other mutating internal chat-command paths for the same missing-scope pattern.

Related

Weakness · CWE (1)

  • CWE-862 Missing Authorization weakness

References