

















@@ -168,6 +168,18 @@ function setupPluginInstallDirs() {
168168return { tmpDir, pluginDir, extensionsDir };
169169}
170170171+function writeMinimalPackagePlugin(pluginDir: string, name: string): void {
172+fs.writeFileSync(
173+path.join(pluginDir, "package.json"),
174+JSON.stringify({
175+ name,
176+version: "1.0.0",
177+openclaw: { extensions: ["index.js"] },
178+}),
179+);
180+fs.writeFileSync(path.join(pluginDir, "index.js"), "export {};\n");
181+}
182+171183function setupInstallPluginFromDirFixture(params?: {
172184devDependencies?: Record<string, string>;
173185optionalDependencies?: Record<string, string>;
@@ -1402,6 +1414,116 @@ describe("installPluginFromArchive", () => {
14021414},
14031415);
140414161417+it.runIf(process.platform !== "win32")(
1418+"allows package installs when node_modules/openclaw points at the host package root",
1419+async () => {
1420+const { pluginDir, extensionsDir, tmpDir } = setupPluginInstallDirs();
1421+const hostRoot = path.join(tmpDir, "host-openclaw");
1422+fs.mkdirSync(hostRoot, { recursive: true });
1423+fs.writeFileSync(path.join(hostRoot, "package.json"), '{"name":"openclaw"}\n');
1424+vi.mocked(resolveOpenClawPackageRootSync).mockReturnValue(hostRoot);
1425+writeMinimalPackagePlugin(pluginDir, "openclaw-peer-plugin");
1426+1427+const nodeModulesDir = path.join(pluginDir, "node_modules");
1428+fs.mkdirSync(nodeModulesDir, { recursive: true });
1429+fs.symlinkSync(hostRoot, path.join(nodeModulesDir, "openclaw"), "junction");
1430+1431+const { result } = await installFromDirWithWarnings({ pluginDir, extensionsDir });
1432+1433+expect(result.ok).toBe(true);
1434+},
1435+);
1436+1437+it.runIf(process.platform !== "win32")(
1438+"allows package installs when node_modules/.bin/openclaw points inside the host package root",
1439+async () => {
1440+const { pluginDir, extensionsDir, tmpDir } = setupPluginInstallDirs();
1441+const hostRoot = path.join(tmpDir, "host-openclaw");
1442+fs.mkdirSync(hostRoot, { recursive: true });
1443+fs.writeFileSync(path.join(hostRoot, "package.json"), '{"name":"openclaw"}\n');
1444+const hostBin = path.join(hostRoot, "openclaw.mjs");
1445+fs.writeFileSync(hostBin, "#!/usr/bin/env node\n");
1446+vi.mocked(resolveOpenClawPackageRootSync).mockReturnValue(hostRoot);
1447+writeMinimalPackagePlugin(pluginDir, "openclaw-bin-peer-plugin");
1448+1449+const binDir = path.join(pluginDir, "node_modules", ".bin");
1450+fs.mkdirSync(binDir, { recursive: true });
1451+fs.symlinkSync(hostBin, path.join(binDir, "openclaw"), "file");
1452+1453+const { result } = await installFromDirWithWarnings({ pluginDir, extensionsDir });
1454+1455+expect(result.ok).toBe(true);
1456+},
1457+);
1458+1459+it.runIf(process.platform !== "win32")(
1460+"fails package installs when node_modules/openclaw points outside the host package root",
1461+async () => {
1462+const { pluginDir, extensionsDir, tmpDir } = setupPluginInstallDirs();
1463+const hostRoot = path.join(tmpDir, "host-openclaw");
1464+const spoofedRoot = path.join(tmpDir, "spoofed-openclaw");
1465+fs.mkdirSync(hostRoot, { recursive: true });
1466+fs.mkdirSync(spoofedRoot, { recursive: true });
1467+fs.writeFileSync(path.join(hostRoot, "package.json"), '{"name":"openclaw"}\n');
1468+fs.writeFileSync(path.join(spoofedRoot, "package.json"), '{"name":"openclaw"}\n');
1469+vi.mocked(resolveOpenClawPackageRootSync).mockReturnValue(hostRoot);
1470+writeMinimalPackagePlugin(pluginDir, "spoofed-openclaw-peer-plugin");
1471+1472+const nodeModulesDir = path.join(pluginDir, "node_modules");
1473+fs.mkdirSync(nodeModulesDir, { recursive: true });
1474+fs.symlinkSync(spoofedRoot, path.join(nodeModulesDir, "openclaw"), "junction");
1475+1476+const { result } = await installFromDirWithWarnings({ pluginDir, extensionsDir });
1477+1478+expect(result.ok).toBe(false);
1479+if (!result.ok) {
1480+expect(result.code).toBe(PLUGIN_INSTALL_ERROR_CODE.SECURITY_SCAN_FAILED);
1481+expect(result.error).toContain("node_modules/openclaw");
1482+}
1483+},
1484+);
1485+1486+it.runIf(process.platform !== "win32")(
1487+"fails package installs for nested or non-exact openclaw node_modules symlinks",
1488+async () => {
1489+const cases = [
1490+{
1491+pluginName: "nested-openclaw-peer-plugin",
1492+relativePath: path.join("node_modules", "vendor", "node_modules", "openclaw"),
1493+},
1494+{
1495+pluginName: "uppercase-openclaw-peer-plugin",
1496+relativePath: path.join("node_modules", "OpenClaw"),
1497+},
1498+{
1499+pluginName: "trailing-space-openclaw-peer-plugin",
1500+relativePath: path.join("node_modules", "openclaw "),
1501+},
1502+] as const;
1503+1504+for (const testCase of cases) {
1505+const { pluginDir, extensionsDir, tmpDir } = setupPluginInstallDirs();
1506+const hostRoot = path.join(tmpDir, "host-openclaw");
1507+fs.mkdirSync(hostRoot, { recursive: true });
1508+fs.writeFileSync(path.join(hostRoot, "package.json"), '{"name":"openclaw"}\n');
1509+vi.mocked(resolveOpenClawPackageRootSync).mockReturnValue(hostRoot);
1510+writeMinimalPackagePlugin(pluginDir, testCase.pluginName);
1511+1512+const symlinkPath = path.join(pluginDir, testCase.relativePath);
1513+fs.mkdirSync(path.dirname(symlinkPath), { recursive: true });
1514+fs.symlinkSync(hostRoot, symlinkPath, "junction");
1515+1516+const { result } = await installFromDirWithWarnings({ pluginDir, extensionsDir });
1517+1518+expect(result.ok).toBe(false);
1519+if (!result.ok) {
1520+expect(result.code).toBe(PLUGIN_INSTALL_ERROR_CODE.SECURITY_SCAN_FAILED);
1521+expect(result.error).toContain(testCase.relativePath);
1522+}
1523+}
1524+},
1525+);
1526+14051527it("does not block package installs for blocked-looking names outside node_modules", async () => {
14061528const { pluginDir, extensionsDir } = setupPluginInstallDirs();
14071529此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。