VYPR
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.

PackageAffected versionsPatched versions
openclawnpm
< 2026.3.282026.3.28

Affected products

1
  • cpe:2.3:a:openclaw:openclaw:*:*:*:*:*:node.js:*:*
    Range: <2026.3.28

Patches

1
e34694733fc6

fix(talk-voice): enforce operator.admin scope on /voice set config writes (#54461)

https://github.com/openclaw/openclawJacob TomlinsonMar 25, 2026via ghsa
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

News mentions

0

No linked articles in our index yet.