GitHub Advisoryadvisory · npm
GHSA-vqvg-86cc-cg83 OpenClaw: Mutating internal `/allowlist` chat commands missed `operator.admin` scope enforcement
MEDIUM
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