Moderate severityNVD Advisory· Published Mar 19, 2026· Updated Mar 25, 2026
OpenClaw 2026.2.22 < 2026.2.24 - Authorization Bypass in Synology Chat Plugin via Empty allowedUserIds
CVE-2026-31998
Description
OpenClaw versions 2026.2.22 and 2026.2.23 contain an authorization bypass vulnerability in the synology-chat channel plugin where dmPolicy set to allowlist with empty allowedUserIds fails open. Attackers with Synology sender access can bypass authorization checks and trigger unauthorized agent dispatch and downstream tool actions.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
openclawnpm | >= 2026.2.22, < 2026.2.24 | 2026.2.24 |
Affected products
1Patches
27655c0cb3a47docs(changelog): add synology-chat allowlist fail-closed note
1 file changed · +1 −0
CHANGELOG.md+1 −0 modified@@ -15,6 +15,7 @@ Docs: https://docs.openclaw.ai - Config/Plugins: treat stale removed `google-antigravity-auth` plugin references as compatibility warnings (not hard validation errors) across `plugins.entries`, `plugins.allow`, `plugins.deny`, and `plugins.slots.memory`, so startup no longer fails after antigravity removal. (#25538, #25862) Thanks @chilu18. - Security/Message actions: enforce local media root checks for `sendAttachment` and `setGroupIcon` when `sandboxRoot` is unset, preventing attachment hydration from reading arbitrary host files via local absolute paths. This ships in the next npm release. Thanks @GCXWLP for reporting. - Security/Workspace FS: normalize `@`-prefixed paths before workspace-boundary checks (including workspace-only read/write/edit and sandbox mount path guards), preventing absolute-path escape attempts from bypassing guard validation. This ships in the next npm release. Thanks @tdjackey for reporting. +- Security/Synology Chat: enforce fail-closed allowlist behavior for DM ingress so `dmPolicy: "allowlist"` with empty `allowedUserIds` rejects all senders instead of allowing unauthorized dispatch. This ships in the next npm release. Thanks @tdjackey for reporting. - Security/Native images: enforce `tools.fs.workspaceOnly` for native prompt image auto-load (including history refs), preventing out-of-workspace sandbox mounts from being implicitly ingested as vision input. This ships in the next npm release. Thanks @tdjackey for reporting. - Security/Exec approvals: bind `system.run` command display/approval text to full argv when shell-wrapper inline payloads carry positional argv values, and reject payload-only `rawCommand` mismatches for those wrapper-carrier forms, preventing hidden command execution under misleading approval text. This ships in the next npm release. Thanks @tdjackey for reporting. - Security/Exec: limit default safe-bin trusted directories to immutable system paths (`/bin`, `/usr/bin`) and require explicit opt-in (`tools.exec.safeBinTrustedDirs`) for package-manager/user bin paths (for example Homebrew), preventing writable-dir binary shadowing from auto-satisfying safe-bin allowlist checks. This ships in the next npm release. Thanks @tdjackey for reporting.
0ee30361b8f6fix(synology-chat): fail closed empty allowlist
7 files changed · +63 −13
docs/channels/synology-chat.md+1 −0 modified@@ -72,6 +72,7 @@ Config values override env vars. - `dmPolicy: "allowlist"` is the recommended default. - `allowedUserIds` accepts a list (or comma-separated string) of Synology user IDs. +- In `allowlist` mode, an empty `allowedUserIds` list blocks all senders (use `dmPolicy: "open"` for allow-all). - `dmPolicy: "open"` allows any sender. - `dmPolicy: "disabled"` blocks DMs. - Pairing approvals work with:
extensions/synology-chat/src/channel.test.ts+19 −0 modified@@ -183,6 +183,25 @@ describe("createSynologyChatPlugin", () => { expect(warnings.some((w: string) => w.includes("open"))).toBe(true); }); + it("warns when dmPolicy is allowlist and allowedUserIds is empty", () => { + const plugin = createSynologyChatPlugin(); + const account = { + accountId: "default", + enabled: true, + token: "t", + incomingUrl: "https://nas/incoming", + nasHost: "h", + webhookPath: "/w", + dmPolicy: "allowlist" as const, + allowedUserIds: [], + rateLimitPerMinute: 30, + botName: "Bot", + allowInsecureSsl: false, + }; + const warnings = plugin.security.collectWarnings({ account }); + expect(warnings.some((w: string) => w.includes("empty allowedUserIds"))).toBe(true); + }); + it("returns no warnings for fully configured account", () => { const plugin = createSynologyChatPlugin(); const account = {
extensions/synology-chat/src/channel.ts+5 −0 modified@@ -141,6 +141,11 @@ export function createSynologyChatPlugin() { '- Synology Chat: dmPolicy="open" allows any user to message the bot. Consider "allowlist" for production use.', ); } + if (account.dmPolicy === "allowlist" && account.allowedUserIds.length === 0) { + warnings.push( + '- Synology Chat: dmPolicy="allowlist" with empty allowedUserIds blocks all senders. Add users or set dmPolicy="open".', + ); + } return warnings; }, },
extensions/synology-chat/src/security.test.ts+2 −2 modified@@ -24,8 +24,8 @@ describe("validateToken", () => { }); describe("checkUserAllowed", () => { - it("allows any user when allowlist is empty", () => { - expect(checkUserAllowed("user1", [])).toBe(true); + it("rejects user when allowlist is empty", () => { + expect(checkUserAllowed("user1", [])).toBe(false); }); it("allows user in the allowlist", () => {
extensions/synology-chat/src/security.ts+1 −2 modified@@ -22,10 +22,9 @@ export function validateToken(received: string, expected: string): boolean { /** * Check if a user ID is in the allowed list. - * Empty allowlist = allow all users. + * Allowlist mode must be explicit; empty lists should not match any user. */ export function checkUserAllowed(userId: string, allowedUserIds: string[]): boolean { - if (allowedUserIds.length === 0) return true; return allowedUserIds.includes(userId); }
extensions/synology-chat/src/webhook-handler.test.ts+20 −0 modified@@ -156,6 +156,26 @@ describe("createWebhookHandler", () => { }); }); + it("returns 403 when allowlist policy is set with empty allowedUserIds", async () => { + const deliver = vi.fn(); + const handler = createWebhookHandler({ + account: makeAccount({ + dmPolicy: "allowlist", + allowedUserIds: [], + }), + deliver, + log, + }); + + const req = makeReq("POST", validBody); + const res = makeRes(); + await handler(req, res); + + expect(res._status).toBe(403); + expect(res._body).toContain("Allowlist is empty"); + expect(deliver).not.toHaveBeenCalled(); + }); + it("returns 403 when DMs are disabled", async () => { await expectForbiddenByPolicy({ account: { dmPolicy: "disabled" },
extensions/synology-chat/src/webhook-handler.ts+15 −9 modified@@ -138,20 +138,26 @@ export function createWebhookHandler(deps: WebhookHandlerDeps) { } // User allowlist check - if ( - account.dmPolicy === "allowlist" && - !checkUserAllowed(payload.user_id, account.allowedUserIds) - ) { - log?.warn(`Unauthorized user: ${payload.user_id}`); - respond(res, 403, { error: "User not authorized" }); - return; - } - if (account.dmPolicy === "disabled") { respond(res, 403, { error: "DMs are disabled" }); return; } + if (account.dmPolicy === "allowlist") { + if (account.allowedUserIds.length === 0) { + log?.warn("Synology Chat allowlist is empty while dmPolicy=allowlist; rejecting message"); + respond(res, 403, { + error: "Allowlist is empty. Configure allowedUserIds or use dmPolicy=open.", + }); + return; + } + if (!checkUserAllowed(payload.user_id, account.allowedUserIds)) { + log?.warn(`Unauthorized user: ${payload.user_id}`); + respond(res, 403, { error: "User not authorized" }); + return; + } + } + // Rate limit if (!rateLimiter.check(payload.user_id)) { log?.warn(`Rate limit exceeded for user: ${payload.user_id}`);
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
6- github.com/openclaw/openclaw/commit/0ee30361b8f6ef3f110f3a7b001da6dd3df96bb5ghsapatchWEB
- github.com/openclaw/openclaw/commit/7655c0cb3a47d0647cbbf5284e177f90b4b82ddbghsapatchWEB
- github.com/advisories/GHSA-gw85-xp4q-5gp9ghsaADVISORY
- github.com/openclaw/openclaw/security/advisories/GHSA-gw85-xp4q-5gp9ghsathird-party-advisoryWEB
- nvd.nist.gov/vuln/detail/CVE-2026-31998ghsaADVISORY
- www.vulncheck.com/advisories/openclaw-authorization-bypass-in-synology-chat-plugin-via-empty-alloweduseridsghsathird-party-advisoryWEB
News mentions
0No linked articles in our index yet.