fix(security): harden workspace bootstrap boundary reads

This commit is contained in:
Peter Steinberger
2026-03-02 17:07:26 +00:00
parent 67b2dde7c5
commit 07b16d5ad0
8 changed files with 190 additions and 7 deletions

View File

@@ -166,4 +166,28 @@ Never do Y.
expect(result).toContain("Rule 2");
expect(result).not.toContain("Other Section");
});
it.runIf(process.platform !== "win32")(
"returns null when AGENTS.md is a symlink escaping workspace",
async () => {
const outside = path.join(tmpDir, "outside-secret.txt");
fs.writeFileSync(outside, "secret");
fs.symlinkSync(outside, path.join(tmpDir, "AGENTS.md"));
const result = await readPostCompactionContext(tmpDir);
expect(result).toBeNull();
},
);
it.runIf(process.platform !== "win32")(
"returns null when AGENTS.md is a hardlink alias",
async () => {
const outside = path.join(tmpDir, "outside-secret.txt");
fs.writeFileSync(outside, "secret");
fs.linkSync(outside, path.join(tmpDir, "AGENTS.md"));
const result = await readPostCompactionContext(tmpDir);
expect(result).toBeNull();
},
);
});

View File

@@ -1,5 +1,6 @@
import fs from "node:fs";
import path from "node:path";
import { openBoundaryFile } from "../../infra/boundary-file-read.js";
const MAX_CONTEXT_CHARS = 3000;
@@ -11,11 +12,21 @@ export async function readPostCompactionContext(workspaceDir: string): Promise<s
const agentsPath = path.join(workspaceDir, "AGENTS.md");
try {
if (!fs.existsSync(agentsPath)) {
const opened = await openBoundaryFile({
absolutePath: agentsPath,
rootPath: workspaceDir,
boundaryLabel: "workspace root",
});
if (!opened.ok) {
return null;
}
const content = await fs.promises.readFile(agentsPath, "utf-8");
const content = (() => {
try {
return fs.readFileSync(opened.fd, "utf-8");
} finally {
fs.closeSync(opened.fd);
}
})();
// Extract "## Session Startup" and "## Red Lines" sections
// Each section ends at the next "## " heading or end of file