refactor: unify boundary hardening for file reads

This commit is contained in:
Peter Steinberger
2026-02-26 13:04:33 +01:00
parent cf4853e2b8
commit eac86c2081
11 changed files with 455 additions and 56 deletions

View File

@@ -137,6 +137,7 @@ function makeFileStat(params?: {
mtimeMs?: number;
dev?: number;
ino?: number;
nlink?: number;
}): import("node:fs").Stats {
return {
isFile: () => true,
@@ -145,6 +146,7 @@ function makeFileStat(params?: {
mtimeMs: params?.mtimeMs ?? 1234,
dev: params?.dev ?? 1,
ino: params?.ino ?? 1,
nlink: params?.nlink ?? 1,
} as unknown as import("node:fs").Stats;
}
@@ -649,4 +651,66 @@ describe("agents.files.get/set symlink safety", () => {
undefined,
);
});
it("rejects agents.files.get when allowlisted file is a hardlinked alias", async () => {
const workspace = "/workspace/test-agent";
const candidate = path.resolve(workspace, "AGENTS.md");
mocks.fsRealpath.mockImplementation(async (p: string) => {
if (p === workspace) {
return workspace;
}
return p;
});
mocks.fsLstat.mockImplementation(async (...args: unknown[]) => {
const p = typeof args[0] === "string" ? args[0] : "";
if (p === candidate) {
return makeFileStat({ nlink: 2 });
}
throw createEnoentError();
});
const { respond, promise } = makeCall("agents.files.get", {
agentId: "main",
name: "AGENTS.md",
});
await promise;
expect(respond).toHaveBeenCalledWith(
false,
undefined,
expect.objectContaining({ message: expect.stringContaining("unsafe workspace file") }),
);
});
it("rejects agents.files.set when allowlisted file is a hardlinked alias", async () => {
const workspace = "/workspace/test-agent";
const candidate = path.resolve(workspace, "AGENTS.md");
mocks.fsRealpath.mockImplementation(async (p: string) => {
if (p === workspace) {
return workspace;
}
return p;
});
mocks.fsLstat.mockImplementation(async (...args: unknown[]) => {
const p = typeof args[0] === "string" ? args[0] : "";
if (p === candidate) {
return makeFileStat({ nlink: 2 });
}
throw createEnoentError();
});
const { respond, promise } = makeCall("agents.files.set", {
agentId: "main",
name: "AGENTS.md",
content: "x",
});
await promise;
expect(respond).toHaveBeenCalledWith(
false,
undefined,
expect.objectContaining({ message: expect.stringContaining("unsafe workspace file") }),
);
expect(mocks.fsOpen).not.toHaveBeenCalled();
});
});