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

CVE-2026-41396

CVE-2026-41396

Description

OpenClaw before 2026.3.31 allows workspace .env files to override the OPENCLAW_BUNDLED_PLUGINS_DIR environment variable, compromising plugin trust verification. Attackers with control over workspace configuration can inject malicious plugins by overriding the bundled plugin trust root directory.

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
330a9f98cb29

fix(config): block workspace bundled-root dotenv overrides (#58170)

https://github.com/openclaw/openclawVincent KocMar 31, 2026via ghsa
3 files changed · +54 0
  • CHANGELOG.md+1 0 modified
    @@ -242,6 +242,7 @@ Docs: https://docs.openclaw.ai
     - Agents/compaction: trigger timeout recovery compaction before retrying high-context LLM timeouts so embedded runs stop repeating oversized requests. (#46417) thanks @joeykrug.
     - Agents/compaction: reconcile `sessions.json.compactionCount` after a late embedded auto-compaction success so persisted session counts catch up once the handler reports completion. (#45493) Thanks @jackal092927.
     - Agents/failover: classify Codex accountId token extraction failures as auth errors so model fallback continues to the next configured candidate. (#55206) Thanks @cosmicnet.
    +- Hooks/plugins/skills: block workspace `.env` overrides for bundled root directories so workspace startup cannot redirect bundled trust roots away from the packaged defaults. Thanks @nexrin and @vincentkoc.
     - Plugins/runtime: reuse only compatible active plugin registries across tools, providers, web search, and channel bootstrap, align `/tools/invoke` plugin loading with the session workspace, and retry outbound channel recovery when the pinned channel surface changes so plugin tools and channels stop disappearing or re-registering from mismatched runtime loads. Thanks @gumadeiras.
     - Talk/macOS: stop direct system-voice failures from replaying system speech, use app-locale fallback for shared watchdog timing, and add regression coverage for the macOS fallback route and language-aware timeout policy. (#53511) thanks @hongsw.
     - Discord/gateway cleanup: keep late Carbon reconnect-exhausted errors suppressed through startup/dispose cleanup so Discord monitor shutdown no longer crashes on late gateway close events. (#55373) Thanks @Takhoffman.
    
  • src/infra/dotenv.test.ts+51 0 modified
    @@ -236,6 +236,31 @@ describe("loadDotEnv", () => {
         });
       });
     
    +  it("blocks bundled trust-root vars from workspace .env", async () => {
    +    await withIsolatedEnvAndCwd(async () => {
    +      await withDotEnvFixture(async ({ cwdDir }) => {
    +        await writeEnvFile(
    +          path.join(cwdDir, ".env"),
    +          [
    +            "OPENCLAW_BUNDLED_HOOKS_DIR=./attacker-hooks",
    +            "OPENCLAW_BUNDLED_PLUGINS_DIR=./attacker-plugins",
    +            "OPENCLAW_BUNDLED_SKILLS_DIR=./attacker-skills",
    +          ].join("\n"),
    +        );
    +
    +        delete process.env.OPENCLAW_BUNDLED_HOOKS_DIR;
    +        delete process.env.OPENCLAW_BUNDLED_PLUGINS_DIR;
    +        delete process.env.OPENCLAW_BUNDLED_SKILLS_DIR;
    +
    +        loadWorkspaceDotEnvFile(path.join(cwdDir, ".env"), { quiet: true });
    +
    +        expect(process.env.OPENCLAW_BUNDLED_HOOKS_DIR).toBeUndefined();
    +        expect(process.env.OPENCLAW_BUNDLED_PLUGINS_DIR).toBeUndefined();
    +        expect(process.env.OPENCLAW_BUNDLED_SKILLS_DIR).toBeUndefined();
    +      });
    +    });
    +  });
    +
       it("still allows trusted global .env to set non-workspace runtime vars", async () => {
         await withIsolatedEnvAndCwd(async () => {
           await withDotEnvFixture(async ({ cwdDir, stateDir }) => {
    @@ -348,6 +373,32 @@ describe("loadCliDotEnv", () => {
         });
       });
     
    +  it("blocks bundled trust-root vars from workspace .env during CLI startup", async () => {
    +    await withIsolatedEnvAndCwd(async () => {
    +      await withDotEnvFixture(async ({ cwdDir }) => {
    +        await writeEnvFile(
    +          path.join(cwdDir, ".env"),
    +          [
    +            "OPENCLAW_BUNDLED_HOOKS_DIR=./attacker-hooks",
    +            "OPENCLAW_BUNDLED_PLUGINS_DIR=./attacker-plugins",
    +            "OPENCLAW_BUNDLED_SKILLS_DIR=./attacker-skills",
    +          ].join("\n"),
    +        );
    +
    +        delete process.env.OPENCLAW_BUNDLED_HOOKS_DIR;
    +        delete process.env.OPENCLAW_BUNDLED_PLUGINS_DIR;
    +        delete process.env.OPENCLAW_BUNDLED_SKILLS_DIR;
    +        vi.spyOn(process, "cwd").mockReturnValue(cwdDir);
    +
    +        loadCliDotEnv({ quiet: true });
    +
    +        expect(process.env.OPENCLAW_BUNDLED_HOOKS_DIR).toBeUndefined();
    +        expect(process.env.OPENCLAW_BUNDLED_PLUGINS_DIR).toBeUndefined();
    +        expect(process.env.OPENCLAW_BUNDLED_SKILLS_DIR).toBeUndefined();
    +      });
    +    });
    +  });
    +
       it("blocks workspace .env takeover vars before loading the global fallback", async () => {
         await withIsolatedEnvAndCwd(async () => {
           await withDotEnvFixture(async ({ base, cwdDir, stateDir }) => {
    
  • src/infra/dotenv.ts+2 0 modified
    @@ -17,7 +17,9 @@ const BLOCKED_WORKSPACE_DOTENV_KEYS = new Set([
       "NODE_TLS_REJECT_UNAUTHORIZED",
       "NO_PROXY",
       "OPENCLAW_AGENT_DIR",
    +  "OPENCLAW_BUNDLED_HOOKS_DIR",
       "OPENCLAW_BUNDLED_PLUGINS_DIR",
    +  "OPENCLAW_BUNDLED_SKILLS_DIR",
       "OPENCLAW_CONFIG_PATH",
       "OPENCLAW_GATEWAY_PASSWORD",
       "OPENCLAW_GATEWAY_SECRET",
    

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

6

News mentions

0

No linked articles in our index yet.