mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-28 05:16:23 -06:00
Bound plugin bundle command file reads with size cap (#110594)
* Bound bundle command file reads with size cap * fix: use buffer.toString for readRegularFileSync result * fix: log oversized bundle command file diagnostic instead of silent skip The catch block now captures the error and emits a console.warn with the file path and error detail, so upgrades do not silently remove oversized installed commands. * test: verify oversized bundle command file is skipped and siblings continue PR #110594: Add focused regression coverage: - Test: an oversized bundle command markdown file (>1 MB) is skipped via catch + continue - Test: normal sibling command files still load correctly - Verifies console.warn diagnostic is emitted for the oversized file * refactor(plugins): log rejected bundle commands Co-authored-by: 陈宪彪0668000387 <chen.xianbiao@xydigit.com> * style(plugins): format bundle warning Co-authored-by: 陈宪彪0668000387 <chen.xianbiao@xydigit.com> --------- Co-authored-by: Peter Steinberger <steipete@gmail.com>
This commit is contained in:
@@ -8,6 +8,11 @@ import type { PluginManifestRecord } from "./manifest-registry.js";
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
plugins: [] as PluginManifestRecord[],
|
||||
warn: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("../logging/subsystem.js", () => ({
|
||||
createSubsystemLogger: () => ({ warn: mocks.warn }),
|
||||
}));
|
||||
|
||||
vi.mock("./manifest-registry.js", () => ({
|
||||
@@ -36,6 +41,7 @@ const tempDirs: string[] = [];
|
||||
|
||||
afterEach(async () => {
|
||||
mocks.plugins = [];
|
||||
mocks.warn.mockReset();
|
||||
await Promise.all(tempDirs.splice(0).map((dir) => fs.rm(dir, { recursive: true, force: true })));
|
||||
});
|
||||
|
||||
@@ -224,4 +230,46 @@ describe("loadEnabledClaudeBundleCommands", () => {
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it("warns and skips oversized bundle commands without dropping siblings", async () => {
|
||||
const homeDir = await createTempDir("openclaw-bundle-commands-oversized-");
|
||||
const workspaceDir = await createTempDir("openclaw-bundle-commands-oversized-ws-");
|
||||
|
||||
await writeClaudeBundleCommandFixture({
|
||||
homeDir,
|
||||
pluginId: "oversized-test",
|
||||
commands: [
|
||||
{
|
||||
relativePath: "commands/normal.md",
|
||||
contents: [
|
||||
"---",
|
||||
"description: Normal command that should be loaded",
|
||||
"---",
|
||||
"This is a normal command.",
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const pluginRoot = resolveBundlePluginRoot(homeDir, "oversized-test");
|
||||
const oversizedFilePath = path.join(pluginRoot, "commands", "oversized.md");
|
||||
await fs.mkdir(path.dirname(oversizedFilePath), { recursive: true });
|
||||
const oversizedContent = Buffer.alloc(1 * 1024 * 1024 + 1, "x");
|
||||
await fs.writeFile(oversizedFilePath, oversizedContent);
|
||||
|
||||
const commands = loadEnabledClaudeBundleCommands({
|
||||
workspaceDir,
|
||||
cfg: {
|
||||
plugins: {
|
||||
entries: { "oversized-test": { enabled: true } },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(commands.map((entry) => entry.rawName)).toEqual(["normal"]);
|
||||
expect(mocks.warn).toHaveBeenCalledOnce();
|
||||
const warning = String(mocks.warn.mock.calls[0]?.[0]);
|
||||
expect(warning).toContain(oversizedFilePath);
|
||||
expect(warning).toContain("1048576");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -10,7 +10,10 @@ import {
|
||||
stripFrontmatterBlock,
|
||||
} from "../../packages/markdown-core/src/frontmatter.js";
|
||||
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
||||
import { formatErrorMessage } from "../infra/errors.js";
|
||||
import { readRootJsonObjectSync } from "../infra/json-files.js";
|
||||
import { readRegularFileSync } from "../infra/regular-file.js";
|
||||
import { createSubsystemLogger } from "../logging/subsystem.js";
|
||||
import { isPathInsideWithRealpath } from "../security/scan-paths.js";
|
||||
import { parseFrontmatterBool } from "../shared/frontmatter.js";
|
||||
import {
|
||||
@@ -33,6 +36,9 @@ type ClaudeBundleCommandSpec = {
|
||||
sourceFilePath: string;
|
||||
};
|
||||
|
||||
const BUNDLE_COMMAND_MAX_BYTES = 1 * 1024 * 1024;
|
||||
const log = createSubsystemLogger("plugins/bundle-commands");
|
||||
|
||||
function readClaudeBundleManifest(rootDir: string): Record<string, unknown> {
|
||||
const result = readRootJsonObjectSync({
|
||||
rootDir,
|
||||
@@ -103,8 +109,11 @@ function loadBundleCommandsFromRoot(params: {
|
||||
for (const filePath of listMarkdownFilesRecursive(params.commandRoot)) {
|
||||
let raw: string;
|
||||
try {
|
||||
raw = fs.readFileSync(filePath, "utf-8");
|
||||
} catch {
|
||||
raw = readRegularFileSync({ filePath, maxBytes: BUNDLE_COMMAND_MAX_BYTES }).buffer.toString(
|
||||
"utf-8",
|
||||
);
|
||||
} catch (error) {
|
||||
log.warn(`skipping unreadable bundle command file ${filePath}: ${formatErrorMessage(error)}`);
|
||||
continue;
|
||||
}
|
||||
const frontmatter = parseFrontmatterBlock(raw);
|
||||
|
||||
Reference in New Issue
Block a user