VYPR
Medium severity4.3NVD Advisory· Published Apr 23, 2026· Updated Apr 28, 2026

CVE-2026-41350

CVE-2026-41350

Description

OpenClaw before 2026.3.31 contains a session visibility bypass vulnerability where the session_status function fails to enforce configured tools.sessions.visibility restrictions for unsandboxed invocations. Attackers can invoke session_status without sandbox constraints to bypass session-policy controls and access restricted session information.

Affected products

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

Patches

1
4d369a3400dc

harden session-status tool visibility guard for all callers

https://github.com/openclaw/openclawRobin WaslanderMar 30, 2026via nvd-ref
2 files changed · +54 10
  • src/agents/openclaw-tools.session-status.test.ts+41 0 modified
    @@ -731,6 +731,47 @@ describe("session_status tool", () => {
         expect(updateSessionStoreMock).not.toHaveBeenCalled();
       });
     
    +  it("blocks unsandboxed same-agent bare main session_status outside self visibility", async () => {
    +    resetSessionStore({
    +      "agent:main:main": {
    +        sessionId: "s-parent",
    +        updatedAt: 10,
    +        providerOverride: "anthropic",
    +        modelOverride: "claude-sonnet-4-6",
    +      },
    +      "agent:main:subagent:child": {
    +        sessionId: "s-child",
    +        updatedAt: 20,
    +      },
    +    });
    +    mockConfig = {
    +      session: { mainKey: "main", scope: "per-sender" },
    +      tools: {
    +        sessions: { visibility: "self" },
    +        agentToAgent: { enabled: true, allow: ["*"] },
    +      },
    +      agents: {
    +        defaults: {
    +          model: { primary: "openai/gpt-5.4" },
    +          models: {},
    +        },
    +      },
    +    };
    +
    +    const tool = getSessionStatusTool("agent:main:subagent:child");
    +
    +    await expect(
    +      tool.execute("call-self-visibility-bare-main", {
    +        sessionKey: "main",
    +        model: "default",
    +      }),
    +    ).rejects.toThrow(
    +      "Session status visibility is restricted to the current session (tools.sessions.visibility=self).",
    +    );
    +
    +    expect(updateSessionStoreMock).not.toHaveBeenCalled();
    +  });
    +
       it("blocks unsandboxed same-agent session_status outside tree visibility before mutation", async () => {
         resetSessionStore({
           "agent:main:main": {
    
  • src/agents/tools/session-status-tool.ts+13 10 modified
    @@ -233,7 +233,7 @@ export function createSessionStatusTool(opts?: {
           const requesterAgentId = resolveAgentIdFromSessionKey(
             opts?.agentSessionKey ?? effectiveRequesterKey,
           );
    -      const visibilityRequesterKey = effectiveRequesterKey.trim();
    +      const visibilityRequesterKey = (opts?.agentSessionKey ?? effectiveRequesterKey).trim();
           const usesLegacyMainAlias = alias === mainKey;
           const isLegacyMainVisibilityKey = (sessionKey: string) => {
             const trimmed = sessionKey.trim();
    @@ -282,7 +282,8 @@ export function createSessionStatusTool(opts?: {
     
           const requestedKeyParam = readStringParam(params, "sessionKey");
           let requestedKeyRaw = requestedKeyParam ?? opts?.agentSessionKey;
    -      let resolvedTargetViaSessionId = false;
    +      const requestedKeyInput = requestedKeyRaw?.trim() ?? "";
    +      let resolvedViaSessionId = false;
           if (!requestedKeyRaw?.trim()) {
             throw new Error("sessionKey required");
           }
    @@ -357,7 +358,7 @@ export function createSessionStatusTool(opts?: {
               }
               // If resolution points at another agent, enforce A2A policy before switching stores.
               ensureAgentAccess(resolveAgentIdFromSessionKey(visibleSession.key));
    -          resolvedTargetViaSessionId = true;
    +          resolvedViaSessionId = true;
               requestedKeyRaw = visibleSession.key;
               agentId = resolveAgentIdFromSessionKey(visibleSession.key);
               storePath = resolveStorePath(cfg.session?.store, { agentId });
    @@ -395,13 +396,15 @@ export function createSessionStatusTool(opts?: {
             throw new Error(`Unknown ${kind}: ${requestedKeyRaw}`);
           }
     
    -      if (resolvedTargetViaSessionId || (opts?.sandboxed === true && !isExplicitAgentKey)) {
    -        const access = visibilityGuard.check(
    -          normalizeVisibilityTargetSessionKey(resolved.key, agentId),
    -        );
    -        if (!access.allowed) {
    -          throw new Error(access.error);
    -        }
    +      // Preserve caller-scoped raw-key/current lookups as "self" for visibility checks.
    +      const visibilityTargetKey =
    +        !resolvedViaSessionId &&
    +        (requestedKeyInput === "current" || resolved.key === requestedKeyInput)
    +          ? visibilityRequesterKey
    +          : normalizeVisibilityTargetSessionKey(resolved.key, agentId);
    +      const access = visibilityGuard.check(visibilityTargetKey);
    +      if (!access.allowed) {
    +        throw new Error(access.error);
           }
     
           const configured = resolveDefaultModelForAgent({ cfg, agentId });
    

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

3

News mentions

0

No linked articles in our index yet.