VYPR
Medium severity6.7NVD Advisory· Published Apr 28, 2026· Updated Apr 30, 2026

CVE-2026-41392

CVE-2026-41392

Description

OpenClaw before 2026.3.31 contains an exec allowlist bypass vulnerability allowing attackers to inherit allowlist trust via shell init-file wrapper invocations. Attackers can exploit shell options like --rcfile, --init-file, and --startup-file to load attacker-chosen initialization files while bypassing exec allowlist matching restrictions.

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
0c8375424620

Exec approvals: reject shell init-file script matches (#58369)

https://github.com/openclaw/openclawJacob TomlinsonMar 31, 2026via ghsa
2 files changed · +63 7
  • src/infra/exec-approvals-allow-always.test.ts+50 0 modified
    @@ -120,6 +120,30 @@ describe("resolveAllowAlwaysPatterns", () => {
         expect(second.allowlistSatisfied).toBe(true);
       }
     
    +  function expectShellScriptFallbackRejected(command: string) {
    +    const { dir, scriptsDir, script, env, safeBins } = createShellScriptFixture();
    +    const rcFile = path.join(scriptsDir, "evilrc");
    +    fs.writeFileSync(rcFile, "echo blocked\n");
    +
    +    const { persisted } = resolvePersistedPatterns({
    +      command,
    +      dir,
    +      env,
    +      safeBins,
    +    });
    +    expect(persisted).toEqual([]);
    +
    +    const second = evaluateShellAllowlist({
    +      command,
    +      allowlist: [{ pattern: script }],
    +      safeBins,
    +      cwd: dir,
    +      env,
    +      platform: process.platform,
    +    });
    +    expect(second.allowlistSatisfied).toBe(false);
    +  }
    +
       function expectPositionalArgvCarrierRejected(command: string) {
         const dir = makeTempDir();
         const touch = makeExecutable(dir, "touch");
    @@ -283,6 +307,32 @@ describe("resolveAllowAlwaysPatterns", () => {
         });
       });
     
    +  it("rejects shell rc and init-file options as persisted or allowlisted script paths", () => {
    +    if (process.platform === "win32") {
    +      return;
    +    }
    +    for (const command of [
    +      "bash --rcfile scripts/evilrc scripts/save_crystal.sh",
    +      "bash --init-file scripts/evilrc scripts/save_crystal.sh",
    +      "bash --startup-file scripts/evilrc scripts/save_crystal.sh",
    +    ]) {
    +      expectShellScriptFallbackRejected(command);
    +    }
    +  });
    +
    +  it("rejects shell rc and init-file equals options as persisted or allowlisted script paths", () => {
    +    if (process.platform === "win32") {
    +      return;
    +    }
    +    for (const command of [
    +      "bash --rcfile=scripts/evilrc scripts/save_crystal.sh",
    +      "bash --init-file=scripts/evilrc scripts/save_crystal.sh",
    +      "bash --startup-file=scripts/evilrc scripts/save_crystal.sh",
    +    ]) {
    +      expectShellScriptFallbackRejected(command);
    +    }
    +  });
    +
       it("rejects shell-wrapper positional argv carriers", () => {
         if (process.platform === "win32") {
           return;
    
  • src/infra/exec-approvals-allowlist.ts+13 7 modified
    @@ -504,16 +504,19 @@ function isShellWrapperSegment(segment: ExecCommandSegment): boolean {
       return hasSegmentExecutableMatch(segment, isShellWrapperExecutable);
     }
     
    -const SHELL_WRAPPER_OPTIONS_WITH_VALUE = new Set([
    -  "-c",
    -  "--command",
    -  "-o",
    -  "-O",
    -  "+O",
    +const SHELL_WRAPPER_OPTIONS_WITH_VALUE = new Set(["-c", "--command", "-o", "-O", "+O"]);
    +
    +const SHELL_WRAPPER_DISQUALIFYING_SCRIPT_OPTIONS = [
       "--rcfile",
       "--init-file",
       "--startup-file",
    -]);
    +] as const;
    +
    +function hasDisqualifyingShellWrapperScriptOption(token: string): boolean {
    +  return SHELL_WRAPPER_DISQUALIFYING_SCRIPT_OPTIONS.some(
    +    (option) => token === option || token.startsWith(`${option}=`),
    +  );
    +}
     
     function resolveShellWrapperScriptCandidatePath(params: {
       segment: ExecCommandSegment;
    @@ -548,6 +551,9 @@ function resolveShellWrapperScriptCandidatePath(params: {
         if (token === "-s" || /^-[^-]*s[^-]*$/i.test(token)) {
           return undefined;
         }
    +    if (hasDisqualifyingShellWrapperScriptOption(token)) {
    +      return undefined;
    +    }
         if (SHELL_WRAPPER_OPTIONS_WITH_VALUE.has(token)) {
           idx += 2;
           continue;
    

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.