mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-11 04:24:31 +00:00
fix(security): harden workspace bootstrap boundary reads
This commit is contained in:
@@ -1,3 +1,6 @@
|
||||
import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import type { AgentMessage } from "@mariozechner/pi-agent-core";
|
||||
import type { Api, Model } from "@mariozechner/pi-ai";
|
||||
import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent";
|
||||
@@ -13,6 +16,7 @@ const {
|
||||
formatToolFailuresSection,
|
||||
computeAdaptiveChunkRatio,
|
||||
isOversizedForSummary,
|
||||
readWorkspaceContextForSummary,
|
||||
BASE_CHUNK_RATIO,
|
||||
MIN_CHUNK_RATIO,
|
||||
SAFETY_MARGIN,
|
||||
@@ -484,3 +488,41 @@ describe("compaction-safeguard double-compaction guard", () => {
|
||||
expect(getApiKeyMock).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("readWorkspaceContextForSummary", () => {
|
||||
it.runIf(process.platform !== "win32")(
|
||||
"returns empty when AGENTS.md is a symlink escape",
|
||||
async () => {
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-compaction-summary-"));
|
||||
const prevCwd = process.cwd();
|
||||
try {
|
||||
const outside = path.join(root, "outside-secret.txt");
|
||||
fs.writeFileSync(outside, "secret");
|
||||
fs.symlinkSync(outside, path.join(root, "AGENTS.md"));
|
||||
process.chdir(root);
|
||||
await expect(readWorkspaceContextForSummary()).resolves.toBe("");
|
||||
} finally {
|
||||
process.chdir(prevCwd);
|
||||
fs.rmSync(root, { recursive: true, force: true });
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
it.runIf(process.platform !== "win32")(
|
||||
"returns empty when AGENTS.md is a hardlink alias",
|
||||
async () => {
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-compaction-summary-"));
|
||||
const prevCwd = process.cwd();
|
||||
try {
|
||||
const outside = path.join(root, "outside-secret.txt");
|
||||
fs.writeFileSync(outside, "secret");
|
||||
fs.linkSync(outside, path.join(root, "AGENTS.md"));
|
||||
process.chdir(root);
|
||||
await expect(readWorkspaceContextForSummary()).resolves.toBe("");
|
||||
} finally {
|
||||
process.chdir(prevCwd);
|
||||
fs.rmSync(root, { recursive: true, force: true });
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
@@ -3,6 +3,7 @@ import path from "node:path";
|
||||
import type { AgentMessage } from "@mariozechner/pi-agent-core";
|
||||
import type { ExtensionAPI, FileOperations } from "@mariozechner/pi-coding-agent";
|
||||
import { extractSections } from "../../auto-reply/reply/post-compaction-context.js";
|
||||
import { openBoundaryFile } from "../../infra/boundary-file-read.js";
|
||||
import { createSubsystemLogger } from "../../logging/subsystem.js";
|
||||
import {
|
||||
BASE_CHUNK_RATIO,
|
||||
@@ -169,11 +170,22 @@ async function readWorkspaceContextForSummary(): Promise<string> {
|
||||
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 "";
|
||||
}
|
||||
|
||||
const content = await fs.promises.readFile(agentsPath, "utf-8");
|
||||
const content = (() => {
|
||||
try {
|
||||
return fs.readFileSync(opened.fd, "utf-8");
|
||||
} finally {
|
||||
fs.closeSync(opened.fd);
|
||||
}
|
||||
})();
|
||||
const sections = extractSections(content, ["Session Startup", "Red Lines"]);
|
||||
|
||||
if (sections.length === 0) {
|
||||
@@ -392,6 +404,7 @@ export const __testing = {
|
||||
formatToolFailuresSection,
|
||||
computeAdaptiveChunkRatio,
|
||||
isOversizedForSummary,
|
||||
readWorkspaceContextForSummary,
|
||||
BASE_CHUNK_RATIO,
|
||||
MIN_CHUNK_RATIO,
|
||||
SAFETY_MARGIN,
|
||||
|
||||
Reference in New Issue
Block a user