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

CVE-2026-41336

CVE-2026-41336

Description

OpenClaw before 2026.3.31 allows workspace .env files to override the OPENCLAW_BUNDLED_HOOKS_DIR environment variable, enabling loading of attacker-controlled hook code. Attackers can replace trusted default-on bundled hooks from untrusted workspaces to execute arbitrary code.

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.