test: tighten install safe path coverage

This commit is contained in:
Peter Steinberger 2026-03-13 23:45:36 +00:00
parent 98716bc0d7
commit 1ae2163413
1 changed files with 76 additions and 0 deletions

View File

@ -35,6 +35,12 @@ describe("safePathSegmentHashed", () => {
expect(safePathSegmentHashed("demo-skill")).toBe("demo-skill");
});
it("falls back to a hashed skill name for empty or dot-like segments", () => {
expect(safePathSegmentHashed(" ")).toMatch(/^skill-[a-f0-9]{10}$/);
expect(safePathSegmentHashed(".")).toMatch(/^skill-[a-f0-9]{10}$/);
expect(safePathSegmentHashed("..")).toMatch(/^skill-[a-f0-9]{10}$/);
});
it("normalizes separators and adds hash suffix", () => {
const result = safePathSegmentHashed("../../demo/skill");
expect(result.includes("/")).toBe(false);
@ -96,6 +102,57 @@ describe("assertCanonicalPathWithinBase", () => {
}
});
it("accepts missing candidate paths when their parent stays in base", async () => {
const baseDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-install-safe-"));
try {
const candidate = path.join(baseDir, "tools", "plugin");
await fs.mkdir(path.dirname(candidate), { recursive: true });
await expect(
assertCanonicalPathWithinBase({
baseDir,
candidatePath: candidate,
boundaryLabel: "install directory",
}),
).resolves.toBeUndefined();
} finally {
await fs.rm(baseDir, { recursive: true, force: true });
}
});
it("rejects non-directory base paths", async () => {
const baseDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-install-safe-"));
const baseFile = path.join(baseDir, "not-a-dir");
await fs.writeFile(baseFile, "nope", "utf-8");
try {
await expect(
assertCanonicalPathWithinBase({
baseDir: baseFile,
candidatePath: path.join(baseFile, "child"),
boundaryLabel: "install directory",
}),
).rejects.toThrow(/base directory must be a real directory/i);
} finally {
await fs.rm(baseDir, { recursive: true, force: true });
}
});
it("rejects non-directory candidate paths inside the base", async () => {
const baseDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-install-safe-"));
const candidate = path.join(baseDir, "file.txt");
await fs.writeFile(candidate, "nope", "utf-8");
try {
await expect(
assertCanonicalPathWithinBase({
baseDir,
candidatePath: candidate,
boundaryLabel: "install directory",
}),
).rejects.toThrow(/must stay within install directory/i);
} finally {
await fs.rm(baseDir, { recursive: true, force: true });
}
});
it.runIf(process.platform !== "win32")(
"rejects symlinked candidate directories that escape the base",
async () => {
@ -117,4 +174,23 @@ describe("assertCanonicalPathWithinBase", () => {
}
},
);
it.runIf(process.platform !== "win32")("rejects symlinked base directories", async () => {
const parentDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-install-safe-"));
const realBaseDir = path.join(parentDir, "real-base");
const symlinkBaseDir = path.join(parentDir, "base-link");
await fs.mkdir(realBaseDir, { recursive: true });
await fs.symlink(realBaseDir, symlinkBaseDir);
try {
await expect(
assertCanonicalPathWithinBase({
baseDir: symlinkBaseDir,
candidatePath: path.join(symlinkBaseDir, "tool"),
boundaryLabel: "install directory",
}),
).rejects.toThrow(/base directory must be a real directory/i);
} finally {
await fs.rm(parentDir, { recursive: true, force: true });
}
});
});