mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 13:10:43 +00:00
[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:
committed by
GitHub
parent
61481eb34f
commit
538605ff44
@@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user