Skip to content

fix(security): block npm_execpath injection from workspace .env [AI-assisted]#73262

Merged
pgondhi987 merged 6 commits into
openclaw:mainfrom
pgondhi987:fix/fix-532
Apr 28, 2026
Merged

fix(security): block npm_execpath injection from workspace .env [AI-assisted]#73262
pgondhi987 merged 6 commits into
openclaw:mainfrom
pgondhi987:fix/fix-532

Conversation

@pgondhi987

Copy link
Copy Markdown
Contributor

Summary

  • Problem: Workspace .env files could set npm_execpath to an attacker-controlled local script; the bundled runtime dependency installer trusted this value when selecting the npm runner, enabling arbitrary JS execution on openclaw startup.
  • Why it matters: Any repository can ship a .env that redirects npm_execpath, causing OpenClaw to execute an attacker-supplied script as part of bundled runtime dep repair — no user interaction beyond opening the project required.
  • What changed: npm_execpath (any casing) is now blocked at two independent layers: (1) added to BLOCKED_WORKSPACE_DOTENV_KEYS in src/infra/dotenv.ts so it is never loaded from workspace .env; (2) stripped from the install env in createBundledRuntimeDepsInstallEnv before spawning npm; (3) resolveBundledRuntimeDepsNpmRunner no longer reads npm_execpath at all — runner selection is now purely Node-adjacent path resolution.
  • What did NOT change: No public API, plugin SDK, or extension contract changes. Trusted global .env behavior is unaffected.

Change Type (select all)

  • Bug fix
  • Security hardening

Scope (select all touched areas)

  • Skills / tool execution

Linked Issue/PR

  • This PR fixes a bug or regression

Root Cause (if applicable)

  • Root cause: resolveBundledRuntimeDepsNpmRunner read env.npm_execpath and, after a weak path-pattern check (isNpmCliPath), placed it first in the npm-cli candidate list. An attacker could satisfy the check by naming their file npm-cli.js.
  • Missing detection / guardrail: npm_execpath was not on the workspace .env blocklist, and no stripping occurred before the env was passed to the subprocess.
  • Contributing context: npm_execpath is set by npm itself in lifecycle scripts and CI runners, so the original code was trying to reuse it — but that trust is inappropriate for attacker-controlled working directories.

Regression Test Plan (if applicable)

  • Coverage level that should have caught this:
    • Unit test
    • Seam / integration test
  • Target test or file: src/plugins/bundled-runtime-deps.test.ts, src/infra/dotenv.test.ts
  • Scenario the test should lock in: attacker-supplied npm_execpath (and NPM_EXECPATH) is never used as the npm runner and is absent from the spawn env; workspace .env setting npm_execpath is blocked at load time.
  • Why this is the smallest reliable guardrail: unit test for createBundledRuntimeDepsInstallEnv uses .toEqual to assert neither lowercase nor uppercase form appears in output; integration test for installBundledRuntimeDeps verifies the spawn env does not contain npm_execpath; runner tests confirm the attacker path is never selected even when existsSync returns true for it.
  • If no new test is added, why not: N/A — tests added.

User-visible / Behavior Changes

None. npm_execpath was an internal implementation detail; no documented behavior changes.

Diagram (if applicable)

Before:
workspace .env npm_execpath=evil/npm-cli.js -> loaded into process.env -> resolveBundledRuntimeDepsNpmRunner picks attacker path -> spawnSync executes attacker script

After:
workspace .env npm_execpath=evil/npm-cli.js -> BLOCKED by BLOCKED_WORKSPACE_DOTENV_KEYS -> never loaded
process.env npm_execpath (any source) -> stripped by createBundledRuntimeDepsInstallEnv -> not in spawn env
resolveBundledRuntimeDepsNpmRunner -> ignores npm_execpath entirely -> Node-adjacent path only

Security Impact (required)

  • New permissions/capabilities? No
  • Secrets/tokens handling changed? No
  • New/changed network calls? No
  • Command/tool execution surface changed? Yes — npm_execpath can no longer influence which npm CLI is executed during bundled runtime dep installation.
  • Data access scope changed? No
  • Risk + mitigation: Existing environments that legitimately relied on npm_execpath to point to npm (e.g. custom Node installs with npm not in the standard relative location) will now fall through to the Node-adjacent path candidates and then the system npm fallback. This is the correct behavior; npm_execpath redirection from untrusted working directories is the vulnerability being closed.

Repro + Verification

Environment

  • OS: Linux (Ubuntu)
  • Runtime/container: Node 22
  • Model/provider: N/A
  • Integration/channel: N/A

Steps

  1. Before fix: place npm_execpath=/proc/self/cwd/evil/npm-cli.js in workspace .env; trigger bundled runtime dep install; attacker script executes.
  2. After fix: same setup — npm_execpath is blocked at .env load; even if present in env via another vector, stripped before spawn; runner ignores it entirely.

Expected

  • Attacker-controlled npm_execpath is never used as the npm runner path.

Actual

  • Fix confirmed via test suite; both dotenv blocking and install-env stripping verified.

Evidence

  • Failing test/log before + passing after — updated tests in bundled-runtime-deps.test.ts and dotenv.test.ts cover the attack scenarios and pass with the fix applied.

Human Verification (required)

AI-assisted PR — generated by OpenAI Codex with Claude Code review. No human manual verification was performed beyond automated test execution.

  • Verified scenarios: test suite covers dotenv blocking (lowercase), install-env stripping (both casings via .toEqual), runner ignoring attacker path on Linux and Windows, and spawn env absence assertion.
  • Edge cases checked: uppercase NPM_EXECPATH stripping, Windows existsSync-positive attacker path still rejected, non-Windows fallback PATH env does not reintroduce the key.
  • What you did not verify: live end-to-end execution against a real malicious repo; custom Node install layouts where npm is not in either Node-adjacent candidate path.

Review Conversations

  • I replied to or resolved every bot review conversation I addressed in this PR.
  • I left unresolved only the conversations that still need reviewer or maintainer judgment.

Compatibility / Migration

  • Backward compatible? Yes
  • Config/env changes? No (blocked key is not a user-configurable setting)
  • Migration needed? No

Risks and Mitigations

  • Risk: Environments with non-standard Node installs where neither ../lib/node_modules/npm/bin/npm-cli.js nor node_modules/npm/bin/npm-cli.js (relative to node) exist may fall through to the npm shell command instead of the bundled npm-cli.js. This was already the pre-existing fallback behavior before the npm_execpath path was added.
    • Mitigation: The fallback (command: "npm", PATH-based) is the same behavior as before npm_execpath support was introduced and is safe for trusted system npm installs.

@openclaw-barnacle openclaw-barnacle Bot added size: S maintainer Maintainer-authored PR labels Apr 28, 2026
@greptile-apps

greptile-apps Bot commented Apr 28, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR closes an npm_execpath injection vulnerability where a workspace .env could redirect the bundled runtime dep installer to execute an attacker-controlled script. The fix applies three independent layers: blocking NPM_EXECPATH in BLOCKED_WORKSPACE_DOTENV_KEYS, stripping both casings from the install env in createBundledRuntimeDepsInstallEnv, and removing the env read entirely from resolveBundledRuntimeDepsNpmRunner. All three layers are exercised by the updated test suite using strict .toEqual assertions.

Confidence Score: 5/5

Safe to merge — the fix is correct, all-casing variants are handled, and tests are strict.

All three mitigating layers are implemented correctly. Case normalization is consistent (toUpperCase() in the dotenv blocker, toLowerCase() in the install-env stripper). The runner no longer reads npm_execpath at all. Production call sites always pass the stripped installEnv to the runner. No regressions or missing guards identified.

No files require special attention.

Reviews (2): Last reviewed commit: "fix: address PR review feedback" | Re-trigger Greptile

Comment thread src/infra/dotenv.test.ts
Comment on lines +375 to +382

loadWorkspaceDotEnvFile(path.join(cwdDir, ".env"), { quiet: true });

expect(process.env.npm_execpath).toBeUndefined();
});
});
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Test only covers lowercase key variant

The new test writes npm_execpath=./evil/npm-cli.js (lowercase) and asserts process.env.npm_execpath is undefined — but doesn't verify that NPM_EXECPATH=./evil/npm-cli.js in a workspace .env is also blocked. The implementation does handle this correctly via shouldBlockWorkspaceDotEnvKey's key.toUpperCase() normalisation, so there's no actual bug, but a second test case for the uppercase spelling would close the coverage gap and keep the test suite honest.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/infra/dotenv.test.ts
Line: 375-382

Comment:
**Test only covers lowercase key variant**

The new test writes `npm_execpath=./evil/npm-cli.js` (lowercase) and asserts `process.env.npm_execpath` is undefined — but doesn't verify that `NPM_EXECPATH=./evil/npm-cli.js` in a workspace `.env` is also blocked. The implementation does handle this correctly via `shouldBlockWorkspaceDotEnvKey`'s `key.toUpperCase()` normalisation, so there's no actual bug, but a second test case for the uppercase spelling would close the coverage gap and keep the test suite honest.

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in 18104c52d0a7a531c9813517deeeffe9db3e62d7.

@pgondhi987

Copy link
Copy Markdown
Contributor Author

@greptile review

@clawsweeper

clawsweeper Bot commented Apr 28, 2026

Copy link
Copy Markdown
Contributor

Codex review: keeping this open for maintainer follow-up; there is still a little grit to resolve.

Keep open. This PR is protected by both MEMBER author association and the maintainer label, so it needs explicit maintainer judgment. It also is not obsolete: current main still lacks the proposed hardening for npm_execpath in workspace .env, install env creation, and npm runner resolution.

Best possible solution:

Keep this PR open for maintainer/security review. The best path is to land it or an equivalent patch that blocks NPM_EXECPATH from workspace .env, strips all casing variants from the bundled runtime dependency install environment, makes runner selection ignore npm_execpath, and preserves the added regression tests, while deciding whether the PATH fallback concern belongs in this PR or a follow-up.

What I checked:

  • Protected maintainer item: The provided GitHub context marks the PR author association as MEMBER and includes the protected maintainer label. The cleanup policy says to keep such items open even when the technical direction looks plausible.
  • Current main still reads npm_execpath: resolveBundledRuntimeDepsNpmRunner still reads env.npm_execpath, validates only by isNpmCliPath, and puts that path first in the npm CLI candidate list. (src/plugins/bundled-runtime-deps.ts:1290, ba722fd1265a)
  • Install env is not stripped on main: createBundledRuntimeDepsInstallEnv returns the npm install environment without deleting lowercase or uppercase npm_execpath; the install path then passes that env into runner resolution and spawn. (src/plugins/bundled-runtime-deps.ts:1266, ba722fd1265a)
  • Workspace dotenv blocklist is missing the key: BLOCKED_WORKSPACE_DOTENV_KEYS does not include NPM_EXECPATH, while workspace dotenv loading applies this blocklist before copying values into process.env. (src/infra/dotenv.ts:13, ba722fd1265a)
  • Existing tests reflect pre-fix behavior: Current tests still assert Windows npm_execpath can be used as the npm CLI path and do not include the PR's proposed uppercase/lowercase workspace dotenv blocking coverage. (src/plugins/bundled-runtime-deps.test.ts:107, ba722fd1265a)
  • Security review scope: The provided PR file list is limited to the dotenv and bundled-runtime-deps source/tests, with no workflows, lockfiles, dependency sources, package metadata, or release scripts. The Aisle bot comment raises a related PATH fallback concern in the same runner, so this should stay open for maintainer security review rather than automated cleanup. (bb45373f19ba)

Remaining risk / open question:

  • The PR is security-sensitive and has a bot-raised PATH fallback concern that maintainers should explicitly accept, defer, or fold into this patch.
  • If this PR is closed without an equivalent replacement, current main still has the described npm_execpath trust path.

Codex review notes: model gpt-5.5, reasoning high; reviewed against ba722fd1265a.

@pgondhi987

Copy link
Copy Markdown
Contributor Author

Addressed in c8f2df4dcb7296396e40de07f047d9da92dc2720.

Quoted comment from @aisle-research-bot:

🔒 Aisle Security Analysis

We found 1 potential security issue(s) in this PR:

# Severity Title
1 🟠 High PATH hijacking leading to RCE when executing Node-adjacent npm shim on POSIX
1. 🟠 PATH hijacking leading to RCE when executing Node-adjacent `npm` shim on POSIX
Property Value
Severity High
CWE CWE-426
Location src/plugins/bundled-runtime-deps.ts:1291-1303

Description

resolveBundledRuntimeDepsNpmRunner falls back on POSIX to executing a Node-adjacent npm file directly (e.g. /opt/node/bin/npm). Many npm binaries in Node distributions are shim scripts with a shebang like #!/usr/bin/env node.

Because /usr/bin/env node resolves node via PATH, and the returned runner does not set/override PATH (nor does it force the known-safe process.execPath), an attacker who can influence the environment PATH (e.g., CI/workspace env, parent process env) can cause the shim to execute an attacker-controlled node binary.

Impact:

  • The process spawns npm to install deps; hijacking node gives arbitrary code execution with the privileges of the running process.

Vulnerable code:

const npmBinCandidates = [
  pathImpl.resolve(nodeDir, "npm"),
  pathImpl.resolve(nodeDir, "../bin/npm"),
];
...
return {
  command: npmBinPath,
  args: params.npmArgs,
};

Recommendation

Avoid executing the npm shim directly on POSIX, or ensure it cannot resolve node from an attacker-controlled PATH.

Safer options:

  1. Prefer running the npm CLI via the known Node executable (like the Windows branch already does):
// Find npm-cli.js and always run it via execPath
return {
  command: execPath,
  args: [npmCliPath, ...params.npmArgs],
};
  1. If you must execute the npm shim, prefix PATH with nodeDir (or set it to a minimal safe PATH) so /usr/bin/env node resolves to the expected Node:
const env = params.env ?? process.env;
const pathKey = Object.keys(env).find(k => k.toLowerCase() === "path") ?? "PATH";
const currentPath = env[pathKey];
return {
  command: npmBinPath,
  args: params.npmArgs,
  env: {
    ...env,
    [pathKey]: typeof currentPath === "string" && currentPath.length
      ? `${nodeDir}${path.delimiter}${currentPath}`
      : nodeDir,
  },
};

Also consider validating that npmBinPath is not a shim relying on /usr/bin/env, or directly invoking ${nodeDir}/node with the resolved npm-cli.js path.


Analyzed PR: #73262 at commit bb45373

Last updated on: 2026-04-28T10:53:37Z

@pgondhi987

Copy link
Copy Markdown
Contributor Author

Addressed in c8f2df4dcb7296396e40de07f047d9da92dc2720.

Quoted comment from @clawsweeper:

Codex review: keeping this open for maintainer follow-up; there is still a little grit to resolve.

Keep open. This PR is protected by both MEMBER author association and the maintainer label, so it needs explicit maintainer judgment. It also is not obsolete: current main still lacks the proposed hardening for npm_execpath in workspace .env, install env creation, and npm runner resolution.

Best possible solution:

Keep this PR open for maintainer/security review. The best path is to land it or an equivalent patch that blocks NPM_EXECPATH from workspace .env, strips all casing variants from the bundled runtime dependency install environment, makes runner selection ignore npm_execpath, and preserves the added regression tests, while deciding whether the PATH fallback concern belongs in this PR or a follow-up.

What I checked:

  • Protected maintainer item: The provided GitHub context marks the PR author association as MEMBER and includes the protected maintainer label. The cleanup policy says to keep such items open even when the technical direction looks plausible.
  • Current main still reads npm_execpath: resolveBundledRuntimeDepsNpmRunner still reads env.npm_execpath, validates only by isNpmCliPath, and puts that path first in the npm CLI candidate list. (src/plugins/bundled-runtime-deps.ts:1290, ba722fd1265a)
  • Install env is not stripped on main: createBundledRuntimeDepsInstallEnv returns the npm install environment without deleting lowercase or uppercase npm_execpath; the install path then passes that env into runner resolution and spawn. (src/plugins/bundled-runtime-deps.ts:1266, ba722fd1265a)
  • Workspace dotenv blocklist is missing the key: BLOCKED_WORKSPACE_DOTENV_KEYS does not include NPM_EXECPATH, while workspace dotenv loading applies this blocklist before copying values into process.env. (src/infra/dotenv.ts:13, ba722fd1265a)
  • Existing tests reflect pre-fix behavior: Current tests still assert Windows npm_execpath can be used as the npm CLI path and do not include the PR's proposed uppercase/lowercase workspace dotenv blocking coverage. (src/plugins/bundled-runtime-deps.test.ts:107, ba722fd1265a)
  • Security review scope: The provided PR file list is limited to the dotenv and bundled-runtime-deps source/tests, with no workflows, lockfiles, dependency sources, package metadata, or release scripts. The Aisle bot comment raises a related PATH fallback concern in the same runner, so this should stay open for maintainer security review rather than automated cleanup. (bb45373f19ba)

Remaining risk / open question:

  • The PR is security-sensitive and has a bot-raised PATH fallback concern that maintainers should explicitly accept, defer, or fold into this patch.
  • If this PR is closed without an equivalent replacement, current main still has the described npm_execpath trust path.

Codex review notes: model gpt-5.5, reasoning high; reviewed against ba722fd1265a.

@pgondhi987
pgondhi987 merged commit ccb3af5 into openclaw:main Apr 28, 2026
@aisle-research-bot

aisle-research-bot Bot commented Apr 28, 2026

Copy link
Copy Markdown

🔒 Aisle Security Analysis

✅ We scanned this PR and did not find any security vulnerabilities.
Aisle supplements but does not replace security review.


Analyzed PR: #73262 at commit 8e331b2

Last updated on: 2026-04-28T12:53:27Z

github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 9, 2026
…ssisted] (openclaw#73262)

* fix: address issue

* fix: finalize issue changes

* fix: address PR review feedback

* fix: address PR review feedback

* fix: address PR review feedback

* docs: add changelog entry for PR merge
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 24, 2026
…ssisted] (openclaw#73262)

* fix: address issue

* fix: finalize issue changes

* fix: address PR review feedback

* fix: address PR review feedback

* fix: address PR review feedback

* docs: add changelog entry for PR merge
jameslcowan pushed a commit to jameslcowan/openclaw that referenced this pull request Jun 2, 2026
…ssisted] (openclaw#73262)

* fix: address issue

* fix: finalize issue changes

* fix: address PR review feedback

* fix: address PR review feedback

* fix: address PR review feedback

* docs: add changelog entry for PR merge
sablehead pushed a commit to sablehead/openclaw that referenced this pull request Jun 10, 2026
…ssisted] (openclaw#73262)

* fix: address issue

* fix: finalize issue changes

* fix: address PR review feedback

* fix: address PR review feedback

* fix: address PR review feedback

* docs: add changelog entry for PR merge
GusAI40 pushed a commit to GusAI40/openclaw-1 that referenced this pull request Jun 22, 2026
26194 drift commits, 222 flagged (security/regression keywords).
Notable: XSS fix (openclaw#83104), PATH injection (openclaw#73264), npm_execpath
injection (openclaw#73262), implicit tool grant fix (openclaw#75055), payment
credential redaction (openclaw#75230), heartbeat regression (openclaw#88970).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

maintainer Maintainer-authored PR size: S

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant