mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-02 06:51:35 +00:00
Reject symlinked generated-output roots before build and postbuild mutation paths can recurse into unrelated targets. Cover aggregate, UI, export-template, bundled-plugin, Docker-prune, and plugin-runtime entry points with regression tests and migration guidance. Closes #116498 Co-authored-by: WangYan <wang.yan29@xydigit.com>
77 lines
2.9 KiB
TypeScript
77 lines
2.9 KiB
TypeScript
// Output Root Guard tests cover the shared build output-root symlink guard.
|
|
import fs from "node:fs";
|
|
import path from "node:path";
|
|
import { describe, expect, it } from "vitest";
|
|
import { assertRealOutputRoot } from "../../scripts/lib/output-root-guard.mjs";
|
|
import { createScriptTestHarness } from "./test-helpers.js";
|
|
|
|
const { createTempDir } = createScriptTestHarness();
|
|
|
|
describe("assertRealOutputRoot", () => {
|
|
it("accepts a missing output root", () => {
|
|
const rootDir = createTempDir("openclaw-output-root-guard-");
|
|
|
|
expect(() => assertRealOutputRoot(path.join(rootDir, "dist"))).not.toThrow();
|
|
});
|
|
|
|
it("propagates failures other than a missing output root", () => {
|
|
const failure = Object.assign(new Error("permission denied"), { code: "EACCES" });
|
|
const fsImpl = {
|
|
...fs,
|
|
lstatSync: () => {
|
|
throw failure;
|
|
},
|
|
};
|
|
|
|
expect(() => assertRealOutputRoot("/unreadable/dist", { fs: fsImpl })).toThrow(failure);
|
|
});
|
|
|
|
it("accepts a real directory output root", () => {
|
|
const rootDir = createTempDir("openclaw-output-root-guard-");
|
|
fs.mkdirSync(path.join(rootDir, "dist"));
|
|
|
|
expect(() => assertRealOutputRoot(path.join(rootDir, "dist"))).not.toThrow();
|
|
});
|
|
|
|
it("accepts a plain file output root", () => {
|
|
const rootDir = createTempDir("openclaw-output-root-guard-");
|
|
const distPath = path.join(rootDir, "dist");
|
|
fs.writeFileSync(distPath, "stale\n");
|
|
|
|
expect(() => assertRealOutputRoot(distPath)).not.toThrow();
|
|
});
|
|
|
|
it("rejects a symlinked output root and names the remediation", () => {
|
|
const rootDir = createTempDir("openclaw-output-root-guard-");
|
|
const targetDir = path.join(rootDir, "gateway-dist");
|
|
fs.mkdirSync(targetDir);
|
|
const distLink = path.join(rootDir, "dist");
|
|
fs.symlinkSync(targetDir, distLink, "dir");
|
|
|
|
expect(() => assertRealOutputRoot(distLink)).toThrow(
|
|
/symbolic link.*Remove the symlink or replace it with a real directory/su,
|
|
);
|
|
expect(fs.readlinkSync(distLink)).toBe(targetDir);
|
|
});
|
|
|
|
it("rejects a dangling symlinked output root", () => {
|
|
const rootDir = createTempDir("openclaw-output-root-guard-");
|
|
const distLink = path.join(rootDir, "dist");
|
|
fs.symlinkSync(path.join(rootDir, "missing-target"), distLink, "dir");
|
|
|
|
expect(() => assertRealOutputRoot(distLink)).toThrow(/symbolic link/u);
|
|
expect(fs.lstatSync(distLink).isSymbolicLink()).toBe(true);
|
|
});
|
|
|
|
it.runIf(process.platform === "win32")("rejects an NTFS junction output root", () => {
|
|
const rootDir = createTempDir("openclaw-output-root-guard-");
|
|
const targetDir = path.join(rootDir, "gateway-dist");
|
|
fs.mkdirSync(targetDir);
|
|
const distLink = path.join(rootDir, "dist");
|
|
fs.symlinkSync(targetDir, distLink, "junction");
|
|
|
|
expect(() => assertRealOutputRoot(distLink)).toThrow(/symbolic link/u);
|
|
expect(fs.lstatSync(distLink).isSymbolicLink()).toBe(true);
|
|
});
|
|
});
|