VYPR
High severity8.1NVD Advisory· Published Apr 28, 2026· Updated May 1, 2026

CVE-2026-41383

CVE-2026-41383

Description

OpenClaw before 2026.4.2 contains an arbitrary directory deletion vulnerability in mirror mode that allows attackers to delete remote directories by influencing remoteWorkspaceDir and remoteAgentWorkspaceDir configuration values. Attackers can manipulate these OpenShell config paths to cause mirror sync operations to delete unintended remote directory contents and replace them with uploaded workspace data.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
openclawnpm
< 2026.4.22026.4.2

Affected products

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

Patches

1
b21c9840c2e3

OpenShell: constrain mirror sync roots (#58515)

https://github.com/openclaw/openclawAgustin RiveraApr 2, 2026via ghsa
3 files changed · +103 52
  • extensions/openshell/src/config.test.ts+72 0 added
    @@ -0,0 +1,72 @@
    +import fsSync from "node:fs";
    +import { describe, expect, it } from "vitest";
    +import { createOpenShellPluginConfigSchema, resolveOpenShellPluginConfig } from "./config.js";
    +
    +describe("openshell plugin config", () => {
    +  it("applies defaults", () => {
    +    expect(resolveOpenShellPluginConfig(undefined)).toEqual({
    +      mode: "mirror",
    +      command: "openshell",
    +      gateway: undefined,
    +      gatewayEndpoint: undefined,
    +      from: "openclaw",
    +      policy: undefined,
    +      providers: [],
    +      gpu: false,
    +      autoProviders: true,
    +      remoteWorkspaceDir: "/sandbox",
    +      remoteAgentWorkspaceDir: "/agent",
    +      timeoutMs: 120_000,
    +    });
    +  });
    +
    +  it("accepts remote mode", () => {
    +    expect(resolveOpenShellPluginConfig({ mode: "remote" }).mode).toBe("remote");
    +  });
    +
    +  it("rejects relative remote paths", () => {
    +    expect(() =>
    +      resolveOpenShellPluginConfig({
    +        remoteWorkspaceDir: "sandbox",
    +      }),
    +    ).toThrow("OpenShell remoteWorkspaceDir must be absolute");
    +  });
    +
    +  it("rejects remote paths outside managed sandbox roots", () => {
    +    expect(() =>
    +      resolveOpenShellPluginConfig({
    +        remoteWorkspaceDir: "/tmp/victim",
    +      }),
    +    ).toThrow("OpenShell remoteWorkspaceDir must stay under /sandbox or /agent");
    +  });
    +
    +  it("normalizes managed sandbox subpaths", () => {
    +    expect(
    +      resolveOpenShellPluginConfig({
    +        remoteWorkspaceDir: "/sandbox/../sandbox/project",
    +        remoteAgentWorkspaceDir: "/agent/./session",
    +      }),
    +    ).toEqual(
    +      expect.objectContaining({
    +        remoteWorkspaceDir: "/sandbox/project",
    +        remoteAgentWorkspaceDir: "/agent/session",
    +      }),
    +    );
    +  });
    +
    +  it("rejects unknown mode", () => {
    +    expect(() =>
    +      resolveOpenShellPluginConfig({
    +        mode: "bogus",
    +      }),
    +    ).toThrow("mode must be one of mirror, remote");
    +  });
    +
    +  it("keeps the runtime json schema in sync with the manifest config schema", () => {
    +    const manifest = JSON.parse(
    +      fsSync.readFileSync(new URL("../openclaw.plugin.json", import.meta.url), "utf8"),
    +    ) as { configSchema?: unknown };
    +
    +    expect(createOpenShellPluginConfigSchema().jsonSchema).toEqual(manifest.configSchema);
    +  });
    +});
    
  • extensions/openshell/src/config.ts+30 4 modified
    @@ -38,6 +38,10 @@ const DEFAULT_SOURCE = "openclaw";
     const DEFAULT_REMOTE_WORKSPACE_DIR = "/sandbox";
     const DEFAULT_REMOTE_AGENT_WORKSPACE_DIR = "/agent";
     const DEFAULT_TIMEOUT_MS = 120_000;
    +const OPEN_SHELL_MANAGED_REMOTE_ROOTS = [
    +  DEFAULT_REMOTE_WORKSPACE_DIR,
    +  DEFAULT_REMOTE_AGENT_WORKSPACE_DIR,
    +] as const;
     
     function normalizeProviders(value: string[] | undefined): string[] {
       const seen = new Set<string>();
    @@ -100,11 +104,26 @@ function formatOpenShellConfigIssue(issue: z.ZodIssue | undefined): string {
       return issue.message;
     }
     
    -function normalizeRemotePath(value: string | undefined, fallback: string): string {
    +function isManagedOpenShellRemotePath(value: string): boolean {
    +  return OPEN_SHELL_MANAGED_REMOTE_ROOTS.some(
    +    (root) => value === root || value.startsWith(`${root}/`),
    +  );
    +}
    +
    +export function normalizeOpenShellRemotePath(
    +  value: string | undefined,
    +  fallback: string,
    +  fieldName = "remote path",
    +): string {
       const candidate = value ?? fallback;
       const normalized = path.posix.normalize(candidate.trim() || fallback);
       if (!normalized.startsWith("/")) {
    -    throw new Error(`OpenShell remote path must be absolute: ${candidate}`);
    +    throw new Error(`OpenShell ${fieldName} must be absolute: ${candidate}`);
    +  }
    +  if (!isManagedOpenShellRemotePath(normalized)) {
    +    throw new Error(
    +      `OpenShell ${fieldName} must stay under ${OPEN_SHELL_MANAGED_REMOTE_ROOTS.join(" or ")}: ${candidate}`,
    +    );
       }
       return normalized;
     }
    @@ -137,6 +156,8 @@ export function createOpenShellPluginConfigSchema(): OpenClawPluginConfigSchema
     
     export function resolveOpenShellPluginConfig(value: unknown): ResolvedOpenShellPluginConfig {
       if (value === undefined) {
    +    // The built-in defaults are managed OpenShell roots, so they do not need to
    +    // flow back through normalizeOpenShellRemotePath.
         return {
           mode: DEFAULT_MODE,
           command: DEFAULT_COMMAND,
    @@ -170,10 +191,15 @@ export function resolveOpenShellPluginConfig(value: unknown): ResolvedOpenShellP
         providers: normalizeProviders(cfg.providers),
         gpu: cfg.gpu ?? false,
         autoProviders: cfg.autoProviders ?? true,
    -    remoteWorkspaceDir: normalizeRemotePath(cfg.remoteWorkspaceDir, DEFAULT_REMOTE_WORKSPACE_DIR),
    -    remoteAgentWorkspaceDir: normalizeRemotePath(
    +    remoteWorkspaceDir: normalizeOpenShellRemotePath(
    +      cfg.remoteWorkspaceDir,
    +      DEFAULT_REMOTE_WORKSPACE_DIR,
    +      "remoteWorkspaceDir",
    +    ),
    +    remoteAgentWorkspaceDir: normalizeOpenShellRemotePath(
           cfg.remoteAgentWorkspaceDir,
           DEFAULT_REMOTE_AGENT_WORKSPACE_DIR,
    +      "remoteAgentWorkspaceDir",
         ),
         timeoutMs:
           typeof cfg.timeoutSeconds === "number"
    
  • extensions/openshell/src/openshell-core.test.ts+1 48 modified
    @@ -12,61 +12,14 @@ import {
       setBundledOpenShellCommandResolverForTest,
       shellEscape,
     } from "./cli.js";
    -import { createOpenShellPluginConfigSchema, resolveOpenShellPluginConfig } from "./config.js";
    +import { resolveOpenShellPluginConfig } from "./config.js";
     
     const cliMocks = vi.hoisted(() => ({
       runOpenShellCli: vi.fn(),
     }));
     
     let createOpenShellSandboxBackendManager: typeof import("./backend.js").createOpenShellSandboxBackendManager;
     
    -describe("openshell plugin config", () => {
    -  it("applies defaults", () => {
    -    expect(resolveOpenShellPluginConfig(undefined)).toEqual({
    -      mode: "mirror",
    -      command: "openshell",
    -      gateway: undefined,
    -      gatewayEndpoint: undefined,
    -      from: "openclaw",
    -      policy: undefined,
    -      providers: [],
    -      gpu: false,
    -      autoProviders: true,
    -      remoteWorkspaceDir: "/sandbox",
    -      remoteAgentWorkspaceDir: "/agent",
    -      timeoutMs: 120_000,
    -    });
    -  });
    -
    -  it("accepts remote mode", () => {
    -    expect(resolveOpenShellPluginConfig({ mode: "remote" }).mode).toBe("remote");
    -  });
    -
    -  it("rejects relative remote paths", () => {
    -    expect(() =>
    -      resolveOpenShellPluginConfig({
    -        remoteWorkspaceDir: "sandbox",
    -      }),
    -    ).toThrow("OpenShell remote path must be absolute");
    -  });
    -
    -  it("rejects unknown mode", () => {
    -    expect(() =>
    -      resolveOpenShellPluginConfig({
    -        mode: "bogus",
    -      }),
    -    ).toThrow("mode must be one of mirror, remote");
    -  });
    -
    -  it("keeps the runtime json schema in sync with the manifest config schema", () => {
    -    const manifest = JSON.parse(
    -      fsSync.readFileSync(new URL("../openclaw.plugin.json", import.meta.url), "utf8"),
    -    ) as { configSchema?: unknown };
    -
    -    expect(createOpenShellPluginConfigSchema().jsonSchema).toEqual(manifest.configSchema);
    -  });
    -});
    -
     describe("openshell cli helpers", () => {
       afterEach(() => {
         setBundledOpenShellCommandResolverForTest();
    

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.