[codex] Extract filesystem safety primitives (#77918)

* refactor: extract filesystem safety primitives

* refactor: use fs-safe for file access helpers

* refactor: reuse fs-safe for media reads

* refactor: use fs-safe for image reads

* refactor: reuse fs-safe in qqbot media opener

* refactor: reuse fs-safe for local media checks

* refactor: consume cleaner fs-safe api

* refactor: align fs-safe json option names

* fix: preserve fs-safe migration contracts

* refactor: use fs-safe primitive subpaths

* refactor: use grouped fs-safe subpaths

* refactor: align fs-safe api usage

* refactor: adapt private state store api

* chore: refresh proof gate

* refactor: follow fs-safe json api split

* refactor: follow reduced fs-safe surface

* build: default fs-safe python helper off

* fix: preserve fs-safe plugin sdk aliases

* refactor: consolidate fs-safe usage

* refactor: unify fs-safe store usage

* refactor: trim fs-safe temp workspace usage

* refactor: hide low-level fs-safe primitives

* build: use published fs-safe package

* fix: preserve outbound recovery durability after rebase

* chore: refresh pr checks
This commit is contained in:
Peter Steinberger
2026-05-06 02:15:17 +01:00
committed by GitHub
parent 61481eb34f
commit 538605ff44
356 changed files with 4918 additions and 11913 deletions

View File

@@ -10,6 +10,8 @@ import type { SourceReplyDeliveryMode } from "../../auto-reply/get-reply-options
import type { ThinkLevel } from "../../auto-reply/thinking.js";
import type { CliBackendConfig } from "../../config/types.js";
import type { OpenClawConfig } from "../../config/types.openclaw.js";
import { privateFileStore } from "../../infra/private-file-store.js";
import { tempWorkspace } from "../../infra/private-temp-workspace.js";
import { resolvePreferredOpenClawTmpDir } from "../../infra/tmp-openclaw-dir.js";
import { MAX_IMAGE_BYTES } from "../../media/constants.js";
import { extensionForMime } from "../../media/mime.js";
@@ -283,14 +285,14 @@ export async function writeCliImages(params: {
workspaceDir: params.workspaceDir,
});
await fs.mkdir(imageRoot, { recursive: true, mode: 0o700 });
const store = privateFileStore(imageRoot);
const paths: string[] = [];
for (let i = 0; i < params.images.length; i += 1) {
const image = params.images[i];
const fileName = path.basename(resolveCliImagePath(image));
const filePath = path.join(imageRoot, fileName);
const buffer = Buffer.from(image.data, "base64");
await fs.writeFile(filePath, buffer, { mode: 0o600 });
paths.push(filePath);
await store.writeText(fileName, buffer);
paths.push(store.path(fileName));
}
// Keep content-addressed image paths stable across Claude CLI runs so prompt
// text and argv don't churn on every turn with fresh temp-dir suffixes.
@@ -308,19 +310,17 @@ export async function writeCliSystemPromptFile(params: {
) {
return { cleanup: async () => {} };
}
const tempDir = await fs.mkdtemp(
path.join(resolvePreferredOpenClawTmpDir(), "openclaw-cli-system-prompt-"),
);
const filePath = path.join(tempDir, "system-prompt.md");
await fs.writeFile(filePath, stripSystemPromptCacheBoundary(params.systemPrompt), {
encoding: "utf-8",
mode: 0o600,
const workspace = await tempWorkspace({
rootDir: resolvePreferredOpenClawTmpDir(),
prefix: "openclaw-cli-system-prompt-",
});
const filePath = await workspace.write(
"system-prompt.md",
stripSystemPromptCacheBoundary(params.systemPrompt),
);
return {
filePath,
cleanup: async () => {
await fs.rm(tempDir, { recursive: true, force: true });
},
cleanup: async () => await workspace.cleanup(),
};
}

View File

@@ -6,6 +6,7 @@ import {
resolveSessionFilePathOptions,
} from "../../config/sessions/paths.js";
import type { OpenClawConfig } from "../../config/types.openclaw.js";
import { isPathInside } from "../../infra/path-guards.js";
import { resolveSessionAgentIds } from "../agent-scope.js";
import {
limitAgentHookHistoryMessages,
@@ -108,11 +109,6 @@ async function safeRealpath(filePath: string): Promise<string | undefined> {
}
}
function isPathWithinBase(basePath: string, targetPath: string): boolean {
const relative = path.relative(basePath, targetPath);
return Boolean(relative) && !relative.startsWith("..") && !path.isAbsolute(relative);
}
function resolveSafeCliSessionFile(params: {
sessionId: string;
sessionFile: string;
@@ -155,7 +151,11 @@ async function loadCliSessionEntries(params: {
}
const realSessionsDir = (await safeRealpath(sessionsDir)) ?? path.resolve(sessionsDir);
const realSessionFile = await safeRealpath(sessionFile);
if (!realSessionFile || !isPathWithinBase(realSessionsDir, realSessionFile)) {
if (
!realSessionFile ||
realSessionFile === realSessionsDir ||
!isPathInside(realSessionsDir, realSessionFile)
) {
return [];
}
const stat = await fsp.stat(realSessionFile);