Skip to content

Commit 3773ffd

Browse files
1 parent 2576073 commit 3773ffd

1 file changed

Lines changed: 55 additions & 0 deletions

File tree

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
{
2+
"schema_version": "1.4.0",
3+
"id": "GHSA-vqvg-86cc-cg83",
4+
"modified": "2026-03-30T18:59:16Z",
5+
"published": "2026-03-30T18:59:16Z",
6+
"aliases": [],
7+
"summary": "OpenClaw: Mutating internal `/allowlist` chat commands missed `operator.admin` scope enforcement",
8+
"details": "> Fixed in OpenClaw 2026.3.24, the current shipping release.\n\n**Title** \nMutating internal `/allowlist` chat commands missed `operator.admin` scope enforcement\n\n**CWE** \nCWE-862 Missing Authorization\n\n**CVSS v3.1** \nCVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:N \nBase score: **6.5 (Medium)**\n\n**Severity Assessment** \nMedium. 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`.\n\n**Impact** \nAn 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.\n\nThis 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`.\n\n**Affected Component** \nVerified against the latest published GitHub release tag `v2026.3.23` (`ccfeecb6887cd97937e33a71877ad512741e82b2`), published `2026-03-23T23:15:50Z`.\n\nExact vulnerable path on the shipped tag:\n- `src/auto-reply/reply/commands-allowlist.ts:251-254`\n - `/allowlist` authorization uses only `rejectUnauthorizedCommand(...)`.\n- `src/auto-reply/reply/commands-allowlist.ts:386-524`\n - mutating config and pairing-store writes happen here, but there is no `requireGatewayClientScopeForInternalChannel(..., operator.admin, ...)`.\n\nReachability and scope model:\n- `src/gateway/method-scopes.ts:94-109`\n - `chat.send` is a write-scoped method.\n- `src/gateway/server.chat.gateway-server-chat.test.ts:539-559`\n - existing runtime coverage proves `chat.send` routes slash commands without an agent run.\n- `src/auto-reply/command-auth.ts:574-577`\n - internal callers become `senderIsOwner` only when `GatewayClientScopes` includes `operator.admin`.\n\nComparable internal mutating command paths already enforce `operator.admin`:\n- `src/auto-reply/reply/commands-config.ts:64-73`\n- `src/auto-reply/reply/commands-mcp.ts:89-96`\n- `src/auto-reply/reply/commands-plugins.ts:387-394`\n- `src/auto-reply/reply/commands-acp.ts:98-106`\n\nVersion history:\n- Introduced by commit `555b2578a8cc6e1b93f717496935ead97bfbed8b` (`feat: add /allowlist command`)\n- Earliest released affected tag found: `v2026.1.20`\n- Latest released affected tag verified: `v2026.3.23`\n\n**Technical Reproduction** \n1. Check out the shipped release tag `v2026.3.23`.\n2. Use an internal command context with:\n - `Provider = \"webchat\"`\n - `Surface = \"webchat\"`\n - `GatewayClientScopes = [\"operator.write\"]`\n - `params.command.channel = \"webchat\"`\n3. Route a slash command through `chat.send`.\n4. Execute either of these mutating commands:\n - `/allowlist add dm channel=telegram 789`\n - `/allowlist add dm --store channel=telegram 789`\n5. Confirm the command context is authorized but not owner-equivalent:\n - `isAuthorizedSender === true`\n - `senderIsOwner === false`\n6. Observe that the commands still succeed and perform persistent writes.\n\n**Demonstrated Impact** \nThe vulnerable handler performs real state mutation for a low-scope internal caller:\n- Config-backed mutation path:\n - `src/auto-reply/reply/commands-allowlist.ts:398-503`\n - reads the config snapshot, applies the edit, validates, and writes the updated config to disk.\n- Store-backed mutation path:\n - `src/auto-reply/reply/commands-allowlist.ts:479-485`\n - `src/auto-reply/reply/commands-allowlist.ts:513-518`\n - updates the pairing-store allowlist without any admin-scope gate.\n\nThe result is successful persistence, not just a misleading success message.\n\n**Environment** \n- Product: OpenClaw\n- Verified shipped tag: `v2026.3.23`\n- Shipped tag commit: `ccfeecb6887cd97937e33a71877ad512741e82b2`\n- Published GitHub release time: `2026-03-23T23:15:50Z`\n- Verification date: `2026-03-24`\n\n**Duplicate Check** \nThis is not a duplicate of:\n- `GHSA-pjvx-rx66-r3fg`\n - that advisory covered cross-account scoping in `/allowlist ... --store`, not missing internal `operator.admin` enforcement.\n- `GHSA-hfpr-jhpq-x4rm`\n - that advisory covered `/config` writes through `chat.send`, not `/allowlist`.\n- `GHSA-3w6x-gv34-mqpf`\n - same authorization class, but different command path (`/acp`, not `/allowlist`).\n\n**In Scope Check** \nThis report is in scope under `SECURITY.md` because:\n- it does **not** rely on adversarial operators sharing one gateway host or config;\n- it does **not** target the HTTP compatibility endpoints that `SECURITY.md` explicitly treats as full operator-access surfaces;\n- it demonstrates a real authorization mismatch inside OpenClaw’s own internal control-plane scope model (`operator.write` vs `operator.admin`);\n- 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.\n\nThis is therefore a concrete authorization bug, not a trusted-operator hardening suggestion.\n\n**Remediation Advice** \n1. Add `requireGatewayClientScopeForInternalChannel(..., allowedScopes: [\"operator.admin\"], ...)` to the mutating internal `/allowlist` paths.\n2. Add regression coverage for both mutation modes:\n - internal `operator.write` must be rejected;\n - internal `operator.admin` must be allowed.\n3. Cover both config-backed and store-backed writes.\n4. Audit other mutating internal chat-command paths for the same missing-scope pattern.",
9+
"severity": [
10+
{
11+
"type": "CVSS_V3",
12+
"score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:N"
13+
}
14+
],
15+
"affected": [
16+
{
17+
"package": {
18+
"ecosystem": "npm",
19+
"name": "openclaw"
20+
},
21+
"ranges": [
22+
{
23+
"type": "ECOSYSTEM",
24+
"events": [
25+
{
26+
"introduced": "0"
27+
},
28+
{
29+
"fixed": "2026.3.24"
30+
}
31+
]
32+
}
33+
]
34+
}
35+
],
36+
"references": [
37+
{
38+
"type": "WEB",
39+
"url": "https://github.com/openclaw/openclaw/security/advisories/GHSA-vqvg-86cc-cg83"
40+
},
41+
{
42+
"type": "PACKAGE",
43+
"url": "https://github.com/openclaw/openclaw"
44+
}
45+
],
46+
"database_specific": {
47+
"cwe_ids": [
48+
"CWE-862"
49+
],
50+
"severity": "MODERATE",
51+
"github_reviewed": true,
52+
"github_reviewed_at": "2026-03-30T18:59:16Z",
53+
"nvd_published_at": null
54+
}
55+
}

0 commit comments

Comments
 (0)