High severity7.1NVD Advisory· Published Apr 28, 2026· Updated May 1, 2026
CVE-2026-41379
CVE-2026-41379
Description
OpenClaw before 2026.3.28 contains a privilege escalation vulnerability allowing authenticated operators with write permissions to access admin-class Talk Voice configuration persistence. Attackers with operator.write privileges can exploit the chat.send endpoint to reach and modify sensitive voice configuration settings intended for administrators only.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
openclawnpm | < 2026.3.28 | 2026.3.28 |
Affected products
1Patches
1e34694733fc6fix(talk-voice): enforce operator.admin scope on /voice set config writes (#54461)
2 files changed · +95 −1
extensions/talk-voice/index.test.ts+87 −1 modified@@ -27,12 +27,17 @@ function createHarness(config: Record<string, unknown>) { return { command, runtime }; } -function createCommandContext(args: string, channel: string = "discord") { +function createCommandContext( + args: string, + channel: string = "discord", + gatewayClientScopes?: string[], +) { return { args, channel, channelId: channel, isAuthorizedSender: true, + gatewayClientScopes, commandBody: args ? `/voice ${args}` : "/voice", config: {}, requestConversationBinding: vi.fn(), @@ -200,6 +205,87 @@ describe("talk-voice plugin", () => { }); }); + it("rejects /voice set from gateway client with only operator.write scope", async () => { + const { command, runtime } = createHarness({ + talk: { + provider: "elevenlabs", + providers: { + elevenlabs: { + apiKey: "sk-eleven", + }, + }, + }, + }); + vi.mocked(runtime.tts.listVoices).mockResolvedValue([{ id: "voice-a", name: "Claudia" }]); + + const result = await command.handler( + createCommandContext("set Claudia", "webchat", ["operator.write"]), + ); + + expect(result.text).toContain("requires operator.admin"); + expect(runtime.config.writeConfigFile).not.toHaveBeenCalled(); + }); + + it("allows /voice set from gateway client with operator.admin scope", async () => { + const { command, runtime } = createHarness({ + talk: { + provider: "elevenlabs", + providers: { + elevenlabs: { + apiKey: "sk-eleven", + }, + }, + }, + }); + vi.mocked(runtime.tts.listVoices).mockResolvedValue([{ id: "voice-a", name: "Claudia" }]); + + const result = await command.handler( + createCommandContext("set Claudia", "webchat", ["operator.admin"]), + ); + + expect(runtime.config.writeConfigFile).toHaveBeenCalled(); + expect(result.text).toContain("voice-a"); + }); + + it("rejects /voice set from webchat channel with no scopes (TUI/internal)", async () => { + const { command, runtime } = createHarness({ + talk: { + provider: "elevenlabs", + providers: { + elevenlabs: { + apiKey: "sk-eleven", + }, + }, + }, + }); + vi.mocked(runtime.tts.listVoices).mockResolvedValue([{ id: "voice-a", name: "Claudia" }]); + + // gatewayClientScopes omitted — simulates internal webchat session without scopes + const result = await command.handler(createCommandContext("set Claudia", "webchat")); + + expect(result.text).toContain("requires operator.admin"); + expect(runtime.config.writeConfigFile).not.toHaveBeenCalled(); + }); + + it("allows /voice set from non-gateway channels without scope check", async () => { + const { command, runtime } = createHarness({ + talk: { + provider: "elevenlabs", + providers: { + elevenlabs: { + apiKey: "sk-eleven", + }, + }, + }, + }); + vi.mocked(runtime.tts.listVoices).mockResolvedValue([{ id: "voice-a", name: "Claudia" }]); + + const result = await command.handler(createCommandContext("set Claudia", "telegram")); + + expect(runtime.config.writeConfigFile).toHaveBeenCalled(); + expect(result.text).toContain("voice-a"); + }); + it("returns provider lookup errors cleanly", async () => { const { command, runtime } = createHarness({ talk: {
extensions/talk-voice/index.ts+8 −0 modified@@ -164,6 +164,14 @@ export default definePluginEntry({ } if (action === "set") { + // Persistent config writes require operator.admin for gateway clients. + // Without this check, a caller with only operator.write could bypass the + // admin-only config.patch RPC by reaching writeConfigFile indirectly + // through chat.send → /voice set. + if (ctx.channel === "webchat" && !ctx.gatewayClientScopes?.includes("operator.admin")) { + return { text: `⚠️ ${commandLabel} set requires operator.admin for gateway clients.` }; + } + const query = tokens.slice(1).join(" ").trim(); if (!query) { return { text: `Usage: ${commandLabel} set <voiceId|name>` };
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
4- github.com/openclaw/openclaw/commit/e34694733fc64931ed4a543c73d84ad3435d5df1nvdPatchWEB
- github.com/advisories/GHSA-3q42-xmxv-9vfrghsaADVISORY
- github.com/openclaw/openclaw/security/advisories/GHSA-3q42-xmxv-9vfrnvdVendor AdvisoryWEB
- www.vulncheck.com/advisories/openclaw-privilege-escalation-via-chat-send-to-admin-class-talk-voice-confignvdThird Party Advisory
News mentions
0No linked articles in our index yet.