




















11// Covers safe plugin install path normalization and boundary checks.
2+import fsSync from "node:fs";
23import fs from "node:fs/promises";
4+import os from "node:os";
35import path from "node:path";
46import { describe, expect, it } from "vitest";
57import { withTempDir } from "../test-helpers/temp-dir.js";
@@ -12,6 +14,31 @@ import {
1214unscopedPackageName,
1315} from "./install-safe-path.js";
141617+const directorySymlinkType = process.platform === "win32" ? "junction" : "dir";
18+19+// Vitest evaluates skipIf while declaring tests, so probe before describe().
20+const canCreateDirectorySymlinks = (() => {
21+let probeDir: string | undefined;
22+try {
23+probeDir = fsSync.mkdtempSync(
24+path.join(os.tmpdir(), "openclaw-install-safe-dir-symlink-probe-"),
25+);
26+const targetDir = path.join(probeDir, "target");
27+const linkDir = path.join(probeDir, "link");
28+fsSync.mkdirSync(targetDir);
29+fsSync.symlinkSync(targetDir, linkDir, directorySymlinkType);
30+return true;
31+} catch {
32+return false;
33+} finally {
34+if (probeDir) {
35+try {
36+fsSync.rmSync(probeDir, { recursive: true, force: true });
37+} catch {}
38+}
39+}
40+})();
41+1542describe("unscopedPackageName", () => {
1643it.each([
1744{ value: "@openclaw/matrix", expected: "matrix" },
@@ -155,13 +182,13 @@ describe("assertCanonicalPathWithinBase", () => {
155182});
156183});
157184158-it.runIf(process.platform !== "win32")(
185+it.skipIf(!canCreateDirectorySymlinks)(
159186"rejects symlinked candidate directories that escape the base",
160187async () => {
161188await withTempDir({ prefix: "openclaw-install-safe-" }, async (baseDir) => {
162189await withTempDir({ prefix: "openclaw-install-safe-outside-" }, async (outsideDir) => {
163190const linkDir = path.join(baseDir, "alias");
164-await fs.symlink(outsideDir, linkDir);
191+await fs.symlink(outsideDir, linkDir, directorySymlinkType);
165192await expect(
166193assertCanonicalPathWithinBase({
167194 baseDir,
@@ -174,14 +201,14 @@ describe("assertCanonicalPathWithinBase", () => {
174201},
175202);
176203177-it.runIf(process.platform !== "win32")(
204+it.skipIf(!canCreateDirectorySymlinks)(
178205"accepts symlinked base directories when the target stays in the real base",
179206async () => {
180207await withTempDir({ prefix: "openclaw-install-safe-" }, async (parentDir) => {
181208const realBaseDir = path.join(parentDir, "real-base");
182209const symlinkBaseDir = path.join(parentDir, "base-link");
183210await fs.mkdir(realBaseDir, { recursive: true });
184-await fs.symlink(realBaseDir, symlinkBaseDir);
211+await fs.symlink(realBaseDir, symlinkBaseDir, directorySymlinkType);
185212await expect(
186213assertCanonicalPathWithinBase({
187214baseDir: symlinkBaseDir,
@@ -193,7 +220,7 @@ describe("assertCanonicalPathWithinBase", () => {
193220},
194221);
195222196-it.runIf(process.platform !== "win32")(
223+it.skipIf(!canCreateDirectorySymlinks)(
197224"rejects nested symlinked candidate directories",
198225async () => {
199226await withTempDir({ prefix: "openclaw-install-safe-" }, async (parentDir) => {
@@ -202,7 +229,7 @@ describe("assertCanonicalPathWithinBase", () => {
202229const nestedSymlinkDir = path.join(realBaseDir, "nested-link");
203230await fs.mkdir(realBaseDir, { recursive: true });
204231await fs.mkdir(nestedRealDir, { recursive: true });
205-await fs.symlink(nestedRealDir, nestedSymlinkDir);
232+await fs.symlink(nestedRealDir, nestedSymlinkDir, directorySymlinkType);
206233await expect(
207234assertCanonicalPathWithinBase({
208235baseDir: realBaseDir,
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。