























@@ -105,18 +105,24 @@ afterEach(() => {
105105});
106106107107describe("resolveBundledRuntimeDepsNpmRunner", () => {
108-it("uses npm_execpath through node on Windows when available", () => {
108+it("ignores npm_execpath and uses the Node-adjacent npm CLI on Windows", () => {
109+const execPath = "C:\\Program Files\\nodejs\\node.exe";
110+const npmCliPath = path.win32.resolve(
111+path.win32.dirname(execPath),
112+"node_modules/npm/bin/npm-cli.js",
113+);
109114const runner = resolveBundledRuntimeDepsNpmRunner({
110-env: { npm_execpath: "C:\\node\\node_modules\\npm\\bin\\npm-cli.js" },
111-execPath: "C:\\Program Files\\nodejs\\node.exe",
112-existsSync: (candidate) => candidate === "C:\\node\\node_modules\\npm\\bin\\npm-cli.js",
115+env: { npm_execpath: "C:\\repo\\evil\\npm-cli.js" },
116+ execPath,
117+existsSync: (candidate) =>
118+candidate === "C:\\repo\\evil\\npm-cli.js" || candidate === npmCliPath,
113119npmArgs: ["install", "acpx@0.5.3"],
114120platform: "win32",
115121});
116122117123expect(runner).toEqual({
118-command: "C:\\Program Files\\nodejs\\node.exe",
119-args: ["C:\\node\\node_modules\\npm\\bin\\npm-cli.js", "install", "acpx@0.5.3"],
124+command: execPath,
125+args: [npmCliPath, "install", "acpx@0.5.3"],
120126});
121127});
122128@@ -139,6 +145,8 @@ describe("resolveBundledRuntimeDepsNpmRunner", () => {
139145npm_config_global: "true",
140146npm_config_location: "global",
141147npm_config_prefix: "/opt/homebrew",
148+npm_execpath: "/repo/evil/npm-cli.js",
149+NPM_EXECPATH: "/repo/evil-uppercase/npm-cli.js",
142150},
143151{ cacheDir: "/opt/openclaw/runtime-cache" },
144152),
@@ -175,15 +183,16 @@ describe("resolveBundledRuntimeDepsNpmRunner", () => {
175183});
176184});
177185178-it("ignores pnpm npm_execpath and falls back to npm", () => {
186+it("ignores npm_execpath and falls back to Node-adjacent npm", () => {
179187const execPath = "/opt/node/bin/node";
180188const npmCliPath = "/opt/node/lib/node_modules/npm/bin/npm-cli.js";
181189const runner = resolveBundledRuntimeDepsNpmRunner({
182190env: {
183-npm_execpath: "/home/runner/setup-pnpm/node_modules/.bin/pnpm.cjs",
191+npm_execpath: "/home/runner/repo/evil/npm-cli.js",
184192},
185193 execPath,
186-existsSync: (candidate) => candidate === npmCliPath,
194+existsSync: (candidate) =>
195+candidate === "/home/runner/repo/evil/npm-cli.js" || candidate === npmCliPath,
187196npmArgs: ["install", "acpx@0.5.3"],
188197platform: "linux",
189198});
@@ -206,24 +215,18 @@ describe("resolveBundledRuntimeDepsNpmRunner", () => {
206215).toThrow("Unable to resolve a safe npm executable on Windows");
207216});
208217209-it("prefixes PATH with the active Node directory on POSIX", () => {
210-const runner = resolveBundledRuntimeDepsNpmRunner({
211-env: {
212-PATH: "/usr/bin:/bin",
213-},
214-execPath: "/opt/node/bin/node",
215-existsSync: () => false,
216-npmArgs: ["install", "acpx@0.5.3"],
217-platform: "linux",
218-});
219-220-expect(runner).toEqual({
221-command: "npm",
222-args: ["install", "acpx@0.5.3"],
223-env: {
224-PATH: `/opt/node/bin${path.delimiter}/usr/bin:/bin`,
225-},
226-});
218+it("refuses POSIX npm shim fallback when npm-cli.js is unavailable", () => {
219+expect(() =>
220+resolveBundledRuntimeDepsNpmRunner({
221+env: {
222+PATH: "/repo/evil/bin:/usr/bin:/bin",
223+},
224+execPath: "/opt/node/bin/node",
225+existsSync: (candidate) => candidate === "/opt/node/bin/npm",
226+npmArgs: ["install"],
227+platform: "linux",
228+}),
229+).toThrow("Unable to resolve a safe npm executable");
227230});
228231});
229232@@ -287,11 +290,16 @@ describe("installBundledRuntimeDeps", () => {
287290);
288291});
289292290-it("uses the npm cmd shim on Windows", () => {
293+it("ignores npm_execpath during Windows installs", () => {
291294const installRoot = makeTempDir();
292295vi.spyOn(process, "platform", "get").mockReturnValue("win32");
296+const safeNpmCliPath = path.win32.resolve(
297+path.win32.dirname(process.execPath),
298+"node_modules/npm/bin/npm-cli.js",
299+);
300+const attackerNpmCliPath = "C:\\repo\\evil\\npm-cli.js";
293301vi.spyOn(fs, "existsSync").mockImplementation(
294-(candidate) => candidate === "C:\\node\\node_modules\\npm\\bin\\npm-cli.js",
302+(candidate) => candidate === attackerNpmCliPath || candidate === safeNpmCliPath,
295303);
296304spawnSyncMock.mockImplementation((_command, _args, options) => {
297305writeInstalledPackage(String(options?.cwd ?? ""), "acpx", "0.5.3");
@@ -311,13 +319,13 @@ describe("installBundledRuntimeDeps", () => {
311319env: {
312320npm_config_prefix: "C:\\prefix",
313321PATH: "C:\\node",
314-npm_execpath: "C:\\node\\node_modules\\npm\\bin\\npm-cli.js",
322+npm_execpath: attackerNpmCliPath,
315323},
316324});
317325318326expect(spawnSyncMock).toHaveBeenCalledWith(
319327expect.any(String),
320-["C:\\node\\node_modules\\npm\\bin\\npm-cli.js", "install", "--ignore-scripts", "acpx@0.5.3"],
328+[safeNpmCliPath, "install", "--ignore-scripts", "acpx@0.5.3"],
321329expect.objectContaining({
322330cwd: installRoot,
323331windowsHide: true,
@@ -338,6 +346,15 @@ describe("installBundledRuntimeDeps", () => {
338346}),
339347}),
340348);
349+expect(spawnSyncMock).toHaveBeenCalledWith(
350+expect.any(String),
351+expect.any(Array),
352+expect.objectContaining({
353+env: expect.not.objectContaining({
354+npm_execpath: expect.any(String),
355+}),
356+}),
357+);
341358});
342359343360it("hides async npm child windows for startup repair installs", async () => {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。