refactor(core): dedupe infra, media, pairing, and plugin helpers

This commit is contained in:
Peter Steinberger
2026-03-02 21:31:18 +00:00
parent 91dd89313a
commit 34daed1d1e
11 changed files with 301 additions and 340 deletions

View File

@@ -24,6 +24,81 @@ afterEach(async () => {
await tempDirs.cleanup();
});
async function expectWriteOpenRaceIsBlocked(params: {
slotPath: string;
outsideDir: string;
runWrite: () => Promise<void>;
}): Promise<void> {
await withRealpathSymlinkRebindRace({
shouldFlip: (realpathInput) => realpathInput.endsWith(path.join("slot", "target.txt")),
symlinkPath: params.slotPath,
symlinkTarget: params.outsideDir,
timing: "before-realpath",
run: async () => {
await expect(params.runWrite()).rejects.toMatchObject({ code: "outside-workspace" });
},
});
}
async function expectSymlinkWriteRaceRejectsOutside(params: {
slotPath: string;
outsideDir: string;
runWrite: (relativePath: string) => Promise<void>;
}): Promise<void> {
const relativePath = path.join("slot", "target.txt");
await expectWriteOpenRaceIsBlocked({
slotPath: params.slotPath,
outsideDir: params.outsideDir,
runWrite: async () => await params.runWrite(relativePath),
});
}
async function withOutsideHardlinkAlias(params: {
aliasPath: string;
run: (outsideFile: string) => Promise<void>;
}): Promise<void> {
const outside = await tempDirs.make("openclaw-fs-safe-outside-");
const outsideFile = path.join(outside, "outside.txt");
await fs.writeFile(outsideFile, "outside");
try {
try {
await fs.link(outsideFile, params.aliasPath);
} catch (err) {
if ((err as NodeJS.ErrnoException).code === "EXDEV") {
return;
}
throw err;
}
await params.run(outsideFile);
} finally {
await fs.rm(params.aliasPath, { force: true });
await fs.rm(outsideFile, { force: true });
}
}
async function setupSymlinkWriteRaceFixture(options?: { seedInsideTarget?: boolean }): Promise<{
root: string;
outside: string;
slot: string;
outsideTarget: string;
}> {
const root = await tempDirs.make("openclaw-fs-safe-root-");
const inside = path.join(root, "inside");
const outside = await tempDirs.make("openclaw-fs-safe-outside-");
await fs.mkdir(inside, { recursive: true });
if (options?.seedInsideTarget) {
await fs.writeFile(path.join(inside, "target.txt"), "inside");
}
const outsideTarget = path.join(outside, "target.txt");
await fs.writeFile(outsideTarget, "X".repeat(4096));
const slot = path.join(root, "slot");
await createRebindableDirectoryAlias({
aliasPath: slot,
targetPath: inside,
});
return { root, outside, slot, outsideTarget };
}
describe("fs-safe", () => {
it("reads a local file safely", async () => {
const dir = await tempDirs.make("openclaw-fs-safe-");
@@ -147,29 +222,18 @@ describe("fs-safe", () => {
it.runIf(process.platform !== "win32")("blocks hardlink aliases under root", async () => {
const root = await tempDirs.make("openclaw-fs-safe-root-");
const outside = await tempDirs.make("openclaw-fs-safe-outside-");
const outsideFile = path.join(outside, "outside.txt");
const hardlinkPath = path.join(root, "link.txt");
await fs.writeFile(outsideFile, "outside");
try {
try {
await fs.link(outsideFile, hardlinkPath);
} catch (err) {
if ((err as NodeJS.ErrnoException).code === "EXDEV") {
return;
}
throw err;
}
await expect(
openFileWithinRoot({
rootDir: root,
relativePath: "link.txt",
}),
).rejects.toMatchObject({ code: "invalid-path" });
} finally {
await fs.rm(hardlinkPath, { force: true });
await fs.rm(outsideFile, { force: true });
}
await withOutsideHardlinkAlias({
aliasPath: hardlinkPath,
run: async () => {
await expect(
openFileWithinRoot({
rootDir: root,
relativePath: "link.txt",
}),
).rejects.toMatchObject({ code: "invalid-path" });
},
});
});
it("writes a file within root safely", async () => {
@@ -245,99 +309,58 @@ describe("fs-safe", () => {
it.runIf(process.platform !== "win32")("rejects writing through hardlink aliases", async () => {
const root = await tempDirs.make("openclaw-fs-safe-root-");
const outside = await tempDirs.make("openclaw-fs-safe-outside-");
const outsideFile = path.join(outside, "outside.txt");
const hardlinkPath = path.join(root, "alias.txt");
await fs.writeFile(outsideFile, "outside");
try {
try {
await fs.link(outsideFile, hardlinkPath);
} catch (err) {
if ((err as NodeJS.ErrnoException).code === "EXDEV") {
return;
}
throw err;
}
await expect(
writeFileWithinRoot({
rootDir: root,
relativePath: "alias.txt",
data: "pwned",
}),
).rejects.toMatchObject({ code: "invalid-path" });
await expect(fs.readFile(outsideFile, "utf8")).resolves.toBe("outside");
} finally {
await fs.rm(hardlinkPath, { force: true });
await fs.rm(outsideFile, { force: true });
}
});
it("does not truncate out-of-root file when symlink retarget races write open", async () => {
const root = await tempDirs.make("openclaw-fs-safe-root-");
const inside = path.join(root, "inside");
const outside = await tempDirs.make("openclaw-fs-safe-outside-");
await fs.mkdir(inside, { recursive: true });
const insideTarget = path.join(inside, "target.txt");
const outsideTarget = path.join(outside, "target.txt");
await fs.writeFile(insideTarget, "inside");
await fs.writeFile(outsideTarget, "X".repeat(4096));
const slot = path.join(root, "slot");
await createRebindableDirectoryAlias({
aliasPath: slot,
targetPath: inside,
});
await withRealpathSymlinkRebindRace({
shouldFlip: (realpathInput) => realpathInput.endsWith(path.join("slot", "target.txt")),
symlinkPath: slot,
symlinkTarget: outside,
timing: "before-realpath",
run: async () => {
await withOutsideHardlinkAlias({
aliasPath: hardlinkPath,
run: async (outsideFile) => {
await expect(
writeFileWithinRoot({
rootDir: root,
relativePath: path.join("slot", "target.txt"),
data: "new-content",
mkdir: false,
relativePath: "alias.txt",
data: "pwned",
}),
).rejects.toMatchObject({ code: "outside-workspace" });
).rejects.toMatchObject({ code: "invalid-path" });
await expect(fs.readFile(outsideFile, "utf8")).resolves.toBe("outside");
},
});
});
it("does not truncate out-of-root file when symlink retarget races write open", async () => {
const { root, outside, slot, outsideTarget } = await setupSymlinkWriteRaceFixture({
seedInsideTarget: true,
});
await expectSymlinkWriteRaceRejectsOutside({
slotPath: slot,
outsideDir: outside,
runWrite: async (relativePath) =>
await writeFileWithinRoot({
rootDir: root,
relativePath,
data: "new-content",
mkdir: false,
}),
});
await expect(fs.readFile(outsideTarget, "utf8")).resolves.toBe("X".repeat(4096));
});
it("does not clobber out-of-root file when symlink retarget races write-from-path open", async () => {
const root = await tempDirs.make("openclaw-fs-safe-root-");
const inside = path.join(root, "inside");
const outside = await tempDirs.make("openclaw-fs-safe-outside-");
const { root, outside, slot, outsideTarget } = await setupSymlinkWriteRaceFixture();
const sourceDir = await tempDirs.make("openclaw-fs-safe-source-");
const sourcePath = path.join(sourceDir, "source.txt");
await fs.writeFile(sourcePath, "new-content");
await fs.mkdir(inside, { recursive: true });
const outsideTarget = path.join(outside, "target.txt");
await fs.writeFile(outsideTarget, "X".repeat(4096));
const slot = path.join(root, "slot");
await createRebindableDirectoryAlias({
aliasPath: slot,
targetPath: inside,
});
await withRealpathSymlinkRebindRace({
shouldFlip: (realpathInput) => realpathInput.endsWith(path.join("slot", "target.txt")),
symlinkPath: slot,
symlinkTarget: outside,
timing: "before-realpath",
run: async () => {
await expect(
writeFileFromPathWithinRoot({
rootDir: root,
relativePath: path.join("slot", "target.txt"),
sourcePath,
mkdir: false,
}),
).rejects.toMatchObject({ code: "outside-workspace" });
},
await expectSymlinkWriteRaceRejectsOutside({
slotPath: slot,
outsideDir: outside,
runWrite: async (relativePath) =>
await writeFileFromPathWithinRoot({
rootDir: root,
relativePath,
sourcePath,
mkdir: false,
}),
});
await expect(fs.readFile(outsideTarget, "utf8")).resolves.toBe("X".repeat(4096));

View File

@@ -15,6 +15,17 @@ function passthroughPluginAutoEnable(config: unknown) {
return { config, changes: [] as unknown[] };
}
function createTelegramPlugin() {
return {
id: "telegram",
meta: { label: "Telegram" },
config: {
listAccountIds: () => [],
resolveAccount: () => ({}),
},
};
}
vi.mock("../../channels/plugins/index.js", () => ({
getChannelPlugin: mocks.getChannelPlugin,
normalizeChannelId: normalizeChannel,
@@ -39,6 +50,13 @@ import { resolveOutboundTarget } from "./targets.js";
describe("resolveOutboundTarget channel resolution", () => {
let registrySeq = 0;
const resolveTelegramTarget = () =>
resolveOutboundTarget({
channel: "telegram",
to: "123456",
cfg: { channels: { telegram: { botToken: "test-token" } } },
mode: "explicit",
});
beforeEach(() => {
registrySeq += 1;
@@ -48,39 +66,20 @@ describe("resolveOutboundTarget channel resolution", () => {
});
it("recovers telegram plugin resolution so announce delivery does not fail with Unsupported channel: telegram", () => {
const telegramPlugin = {
id: "telegram",
meta: { label: "Telegram" },
config: {
listAccountIds: () => [],
resolveAccount: () => ({}),
},
};
const telegramPlugin = createTelegramPlugin();
mocks.getChannelPlugin
.mockReturnValueOnce(undefined)
.mockReturnValueOnce(telegramPlugin)
.mockReturnValue(telegramPlugin);
const result = resolveOutboundTarget({
channel: "telegram",
to: "123456",
cfg: { channels: { telegram: { botToken: "test-token" } } },
mode: "explicit",
});
const result = resolveTelegramTarget();
expect(result).toEqual({ ok: true, to: "123456" });
expect(mocks.loadOpenClawPlugins).toHaveBeenCalledTimes(1);
});
it("retries bootstrap on subsequent resolve when the first bootstrap attempt fails", () => {
const telegramPlugin = {
id: "telegram",
meta: { label: "Telegram" },
config: {
listAccountIds: () => [],
resolveAccount: () => ({}),
},
};
const telegramPlugin = createTelegramPlugin();
mocks.getChannelPlugin
.mockReturnValueOnce(undefined)
.mockReturnValueOnce(undefined)
@@ -93,18 +92,8 @@ describe("resolveOutboundTarget channel resolution", () => {
})
.mockImplementation(() => undefined);
const first = resolveOutboundTarget({
channel: "telegram",
to: "123456",
cfg: { channels: { telegram: { botToken: "test-token" } } },
mode: "explicit",
});
const second = resolveOutboundTarget({
channel: "telegram",
to: "123456",
cfg: { channels: { telegram: { botToken: "test-token" } } },
mode: "explicit",
});
const first = resolveTelegramTarget();
const second = resolveTelegramTarget();
expect(first.ok).toBe(false);
expect(second).toEqual({ ok: true, to: "123456" });