[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

@@ -6,10 +6,17 @@ import type { TelegramContext } from "./types.js";
const saveMediaBuffer = vi.fn();
const fetchRemoteMedia = vi.fn();
const readFileWithinRoot = vi.fn();
const rootRead = vi.fn();
vi.mock("openclaw/plugin-sdk/file-access-runtime", () => ({
readFileWithinRoot: (...args: unknown[]) => readFileWithinRoot(...args),
root: async (rootDir: string) => ({
read: async (relativePath: string, options?: { maxBytes?: number }) =>
await rootRead({
rootDir,
relativePath,
maxBytes: options?.maxBytes,
}),
}),
}));
vi.mock("./delivery.resolve-media.runtime.js", () => {
@@ -201,7 +208,7 @@ describe("resolveMedia getFile retry", () => {
vi.useFakeTimers();
fetchRemoteMedia.mockReset();
saveMediaBuffer.mockReset();
readFileWithinRoot.mockReset();
rootRead.mockReset();
});
afterEach(() => {
@@ -435,7 +442,7 @@ describe("resolveMedia getFile retry", () => {
it("copies trusted local absolute file paths into inbound media storage for media downloads", async () => {
const getFile = vi.fn().mockResolvedValue({ file_path: "/var/lib/telegram-bot-api/file.pdf" });
readFileWithinRoot.mockResolvedValueOnce({
rootRead.mockResolvedValueOnce({
buffer: Buffer.from("pdf-data"),
realPath: "/var/lib/telegram-bot-api/file.pdf",
stat: { size: 8 },
@@ -451,7 +458,7 @@ describe("resolveMedia getFile retry", () => {
);
expect(fetchRemoteMedia).not.toHaveBeenCalled();
expect(readFileWithinRoot).toHaveBeenCalledWith({
expect(rootRead).toHaveBeenCalledWith({
rootDir: "/var/lib/telegram-bot-api",
relativePath: "file.pdf",
maxBytes: MAX_MEDIA_BYTES,
@@ -476,7 +483,7 @@ describe("resolveMedia getFile retry", () => {
const getFile = vi
.fn()
.mockResolvedValue({ file_path: "/var/lib/telegram-bot-api/sticker.webp" });
readFileWithinRoot.mockResolvedValueOnce({
rootRead.mockResolvedValueOnce({
buffer: Buffer.from("sticker-data"),
realPath: "/var/lib/telegram-bot-api/sticker.webp",
stat: { size: 12 },
@@ -491,7 +498,7 @@ describe("resolveMedia getFile retry", () => {
});
expect(fetchRemoteMedia).not.toHaveBeenCalled();
expect(readFileWithinRoot).toHaveBeenCalledWith({
expect(rootRead).toHaveBeenCalledWith({
rootDir: "/var/lib/telegram-bot-api",
relativePath: "sticker.webp",
maxBytes: MAX_MEDIA_BYTES,
@@ -513,7 +520,7 @@ describe("resolveMedia getFile retry", () => {
it("maps trusted local absolute path read failures to MediaFetchError", async () => {
const getFile = vi.fn().mockResolvedValue({ file_path: "/var/lib/telegram-bot-api/file.pdf" });
readFileWithinRoot.mockRejectedValueOnce(new Error("file not found"));
rootRead.mockRejectedValueOnce(new Error("file not found"));
await expect(
resolveMediaWithDefaults(makeCtx("document", getFile, { mime_type: "application/pdf" }), {
@@ -530,7 +537,7 @@ describe("resolveMedia getFile retry", () => {
it("maps oversized trusted local absolute path reads to MediaFetchError", async () => {
const getFile = vi.fn().mockResolvedValue({ file_path: "/var/lib/telegram-bot-api/file.pdf" });
readFileWithinRoot.mockRejectedValueOnce(new Error("file exceeds limit"));
rootRead.mockRejectedValueOnce(new Error("file exceeds limit"));
await expect(
resolveMediaWithDefaults(makeCtx("document", getFile, { mime_type: "application/pdf" }), {
@@ -558,7 +565,7 @@ describe("resolveMedia getFile retry", () => {
}),
);
expect(readFileWithinRoot).not.toHaveBeenCalled();
expect(rootRead).not.toHaveBeenCalled();
expect(fetchRemoteMedia).not.toHaveBeenCalled();
});
});

View File

@@ -1,6 +1,6 @@
import path from "node:path";
import { GrammyError } from "grammy";
import { readFileWithinRoot } from "openclaw/plugin-sdk/file-access-runtime";
import { root as fsRoot } from "openclaw/plugin-sdk/file-access-runtime";
import type { TelegramTransport } from "../fetch.js";
import { cacheSticker, getCachedSticker } from "../sticker-cache.js";
import {
@@ -203,9 +203,8 @@ async function downloadAndSaveTelegramFile(params: {
if (trustedLocalFile) {
let localFile;
try {
localFile = await readFileWithinRoot({
rootDir: trustedLocalFile.rootDir,
relativePath: trustedLocalFile.relativePath,
const root = await fsRoot(trustedLocalFile.rootDir);
localFile = await root.read(trustedLocalFile.relativePath, {
maxBytes: params.maxBytes,
});
} catch (err) {

View File

@@ -1,7 +1,7 @@
import fs from "node:fs";
import path from "node:path";
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-types";
import { logVerbose } from "openclaw/plugin-sdk/runtime-env";
import { replaceFileAtomicSync } from "openclaw/plugin-sdk/security-runtime";
import { resolveStorePath } from "openclaw/plugin-sdk/session-store-runtime";
const TTL_MS = 24 * 60 * 60 * 1000;
@@ -119,10 +119,11 @@ function persistSentMessages(bucket: SentMessageBucket): void {
fs.rmSync(persistedPath, { force: true });
return;
}
fs.mkdirSync(path.dirname(persistedPath), { recursive: true });
const tempPath = `${persistedPath}.${process.pid}.tmp`;
fs.writeFileSync(tempPath, JSON.stringify(serialized), "utf-8");
fs.renameSync(tempPath, persistedPath);
replaceFileAtomicSync({
filePath: persistedPath,
content: JSON.stringify(serialized),
tempPrefix: ".telegram-sent-message-cache",
});
}
export function recordSentMessage(

View File

@@ -1,12 +1,12 @@
import fs from "node:fs";
import type { ChannelLegacyStateMigrationPlan } from "openclaw/plugin-sdk/channel-contract";
import { resolveChannelAllowFromPath } from "openclaw/plugin-sdk/channel-pairing-paths";
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-types";
import { statRegularFileSync } from "openclaw/plugin-sdk/security-runtime";
import { resolveDefaultTelegramAccountId } from "./account-selection.js";
function fileExists(pathValue: string): boolean {
try {
return fs.existsSync(pathValue) && fs.statSync(pathValue).isFile();
return !statRegularFileSync(pathValue).missing;
} catch {
return false;
}

View File

@@ -1,6 +1,6 @@
import fs from "node:fs";
import path from "node:path";
import { logVerbose } from "openclaw/plugin-sdk/runtime-env";
import { replaceFileAtomicSync } from "openclaw/plugin-sdk/security-runtime";
const MAX_ENTRIES = 2_048;
const TOPIC_NAME_CACHE_STATE_KEY = Symbol.for("openclaw.telegramTopicNameCacheState");
@@ -146,10 +146,11 @@ function persistTopicStore(persistedPath: string, store: TopicNameStore): void {
fs.rmSync(persistedPath, { force: true });
return;
}
fs.mkdirSync(path.dirname(persistedPath), { recursive: true });
const tempPath = `${persistedPath}.${process.pid}.tmp`;
fs.writeFileSync(tempPath, JSON.stringify(Object.fromEntries(store)), "utf-8");
fs.renameSync(tempPath, persistedPath);
replaceFileAtomicSync({
filePath: persistedPath,
content: JSON.stringify(Object.fromEntries(store)),
tempPrefix: ".telegram-topic-name-cache",
});
}
export function updateTopicName(