VYPR
High severity8.8NVD Advisory· Published Apr 28, 2026· Updated Apr 30, 2026

CVE-2026-41404

CVE-2026-41404

Description

OpenClaw before 2026.3.31 contains an incomplete scope-clearing vulnerability in trusted-proxy authentication mode that allows operator.admin privilege escalation. Attackers can exploit this by declaring operator scopes on non-Control-UI clients, allowing self-declared scopes to persist on identity-bearing authentication paths and escalate privileges.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
openclawnpm
< 2026.3.312026.3.31

Affected products

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

Patches

1
8b88b927cb07

gateway: clear unbound scopes for trusted-proxy auth (#57692)

https://github.com/openclaw/openclawJacob TomlinsonMar 30, 2026via ghsa
3 files changed · +97 4
  • src/gateway/server/ws-connection/connect-policy.test.ts+69 0 modified
    @@ -3,6 +3,7 @@ import {
       evaluateMissingDeviceIdentity,
       isTrustedProxyControlUiOperatorAuth,
       resolveControlUiAuthPolicy,
    +  shouldClearUnboundScopesForMissingDeviceIdentity,
       shouldSkipControlUiPairing,
     } from "./connect-policy.js";
     
    @@ -300,4 +301,72 @@ describe("ws connect policy", () => {
           ).toBe(tc.expected);
         }
       });
    +
    +  test("clears unbound scopes for device-less shared auth outside explicit preservation cases", () => {
    +    const nonControlUi = resolveControlUiAuthPolicy({
    +      isControlUi: false,
    +      controlUiConfig: undefined,
    +      deviceRaw: null,
    +    });
    +    const controlUi = resolveControlUiAuthPolicy({
    +      isControlUi: true,
    +      controlUiConfig: { allowInsecureAuth: true },
    +      deviceRaw: null,
    +    });
    +
    +    expect(
    +      shouldClearUnboundScopesForMissingDeviceIdentity({
    +        decision: { kind: "allow" },
    +        controlUiAuthPolicy: nonControlUi,
    +        preserveInsecureLocalControlUiScopes: false,
    +        authMethod: "token",
    +      }),
    +    ).toBe(true);
    +
    +    expect(
    +      shouldClearUnboundScopesForMissingDeviceIdentity({
    +        decision: { kind: "allow" },
    +        controlUiAuthPolicy: nonControlUi,
    +        preserveInsecureLocalControlUiScopes: false,
    +        authMethod: "password",
    +      }),
    +    ).toBe(true);
    +
    +    expect(
    +      shouldClearUnboundScopesForMissingDeviceIdentity({
    +        decision: { kind: "allow" },
    +        controlUiAuthPolicy: nonControlUi,
    +        preserveInsecureLocalControlUiScopes: false,
    +        authMethod: "trusted-proxy",
    +      }),
    +    ).toBe(true);
    +
    +    expect(
    +      shouldClearUnboundScopesForMissingDeviceIdentity({
    +        decision: { kind: "allow" },
    +        controlUiAuthPolicy: nonControlUi,
    +        preserveInsecureLocalControlUiScopes: false,
    +        authMethod: undefined,
    +        trustedProxyAuthOk: true,
    +      }),
    +    ).toBe(true);
    +
    +    expect(
    +      shouldClearUnboundScopesForMissingDeviceIdentity({
    +        decision: { kind: "allow" },
    +        controlUiAuthPolicy: controlUi,
    +        preserveInsecureLocalControlUiScopes: true,
    +        authMethod: "token",
    +      }),
    +    ).toBe(false);
    +
    +    expect(
    +      shouldClearUnboundScopesForMissingDeviceIdentity({
    +        decision: { kind: "reject-device-required" },
    +        controlUiAuthPolicy: nonControlUi,
    +        preserveInsecureLocalControlUiScopes: false,
    +        authMethod: undefined,
    +      }),
    +    ).toBe(true);
    +  });
     });
    
  • src/gateway/server/ws-connection/connect-policy.ts+20 0 modified
    @@ -81,6 +81,26 @@ export type MissingDeviceIdentityDecision =
       | { kind: "reject-unauthorized" }
       | { kind: "reject-device-required" };
     
    +export function shouldClearUnboundScopesForMissingDeviceIdentity(params: {
    +  decision: MissingDeviceIdentityDecision;
    +  controlUiAuthPolicy: ControlUiAuthPolicy;
    +  preserveInsecureLocalControlUiScopes: boolean;
    +  authMethod: string | undefined;
    +  trustedProxyAuthOk?: boolean;
    +}): boolean {
    +  return (
    +    params.decision.kind !== "allow" ||
    +    (!params.controlUiAuthPolicy.allowBypass &&
    +      !params.preserveInsecureLocalControlUiScopes &&
    +      // trusted-proxy auth can bypass pairing for some clients, but those
    +      // self-declared scopes are still unbound without device identity.
    +      (params.authMethod === "token" ||
    +        params.authMethod === "password" ||
    +        params.authMethod === "trusted-proxy" ||
    +        params.trustedProxyAuthOk === true))
    +  );
    +}
    +
     export function evaluateMissingDeviceIdentity(params: {
       hasDeviceIdentity: boolean;
       role: GatewayRole;
    
  • src/gateway/server/ws-connection/message-handler.ts+8 4 modified
    @@ -90,6 +90,7 @@ import {
       evaluateMissingDeviceIdentity,
       isTrustedProxyControlUiOperatorAuth,
       resolveControlUiAuthPolicy,
    +  shouldClearUnboundScopesForMissingDeviceIdentity,
       shouldSkipControlUiPairing,
     } from "./connect-policy.js";
     import {
    @@ -548,10 +549,13 @@ export function attachGatewayWsMessageHandler(params: {
               // allow path, including trusted token-authenticated backend operators.
               if (
                 !device &&
    -            (decision.kind !== "allow" ||
    -              (!controlUiAuthPolicy.allowBypass &&
    -                !preserveInsecureLocalControlUiScopes &&
    -                (authMethod === "token" || authMethod === "password" || trustedProxyAuthOk)))
    +            shouldClearUnboundScopesForMissingDeviceIdentity({
    +              decision,
    +              controlUiAuthPolicy,
    +              preserveInsecureLocalControlUiScopes,
    +              authMethod,
    +              trustedProxyAuthOk,
    +            })
               ) {
                 clearUnboundScopes();
               }
    

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

5

News mentions

0

No linked articles in our index yet.