# OpenClaw: Gateway operator.write Can Reach Admin-Class Channel Allowlist Persistence via chat.send

OpenClaw gateway clients scoped to operator.write could call chat.send to reach /allowlist and persist channel allowFrom/groupAllowFrom policy reserved for operator.admin.

- Payout: Research
- Severity: high
- Program: OpenClaw
- Found by (vendor credit): Peng Zhou (@zpbrent)
- Found by: zpbrent
- Disclosed: 2026-03-30
- CVE: CVE-2026-35621
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-94pw-c6m8-p9p9
- Library page: http://bugbountylibrary.com/r/ghsa%3AGHSA-94pw-c6m8-p9p9
- Tags: area:web domain:api impact:integrity impact:privesc kind:research platform:ghsa sev:high technique:incomplete-fix technique:source-review vuln:access-control vuln:auth-bypass vuln:misconfiguration vuln:privilege-escalation

**Target:** Gateway chat.send command pipeline and shared /allowlist config persistence
**Root cause:** The shared /allowlist command persists config via writeConfigFile without a sink-side operator.admin check, unlike /config and /plugins; chat.send builds an internal context with CommandAuthorized:true that resolveCommandAuthorization converts to isAuthorizedSender=true.
**Steps:**
1. Authenticate to the gateway with an `operator.write`-scoped client.
2. Call `chat.send` (allowed for that scope); it builds an internal message context with `CommandAuthorized: true` and carries `GatewayClientScopes`.
3. `resolveCommandAuthorization(...)` yields `isAuthorizedSender=true` when no stricter `commands.allowFrom` override exists.
4. Issue `/allowlist add dm ...` or `/allowlist add group ...`; handler clones config, calls `plugin.allowlist.applyConfigEdit(...)`, validates, then `writeConfigFile(validated.config)`.
5. Observe the persistent allowlist change succeeds, whereas `config.patch` for the same caller returns `missing scope: operator.admin`.
**How found:** Noticed the codebase already added sink-side internal admin checks to shared /config and /plugins writes, but /allowlist was left behind; traced chat.send's CommandAuthorized context through resolveCommandAuthorization into the allowlist config-write path.
**Exploitation:** An authenticated gateway client holding only operator.write sends chat.send commands invoking /allowlist add to widen DM or group allowFrom/groupAllowFrom policy, persisting first-party channel authorization changes that should require operator.admin.
**Insight:** When a codebase hardens some config-mutating sinks with admin checks, enumerate every other command sharing the same writeConfigFile sink — the missing one is the bug; generic command authorization is not scope enforcement.

