mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-24 09:51:13 +00:00
fix(security): bound plugin manifest reads in audit deep scan (#101773)
This commit is contained in:
@@ -295,6 +295,25 @@ Read the requested file and summarize it.
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it("surfaces manifest_parse_error finding when plugin package.json exceeds the size limit", async () => {
|
||||
const tmpDir = await makeTmpDir("audit-manifest-oversized");
|
||||
const pluginDir = path.join(tmpDir, "extensions", "oversized-plugin");
|
||||
await fs.mkdir(pluginDir, { recursive: true });
|
||||
// Oversized manifest — simulates a plugin trying to exhaust the audit reader
|
||||
// by declaring a huge package.json, hiding its declared extension entrypoints.
|
||||
await fs.writeFile(path.join(pluginDir, "package.json"), "x".repeat(1024 * 1024 + 1), "utf-8");
|
||||
|
||||
const findings = await collectPluginsCodeSafetyFindings({ stateDir: tmpDir });
|
||||
const finding = requireFinding(
|
||||
findings,
|
||||
(f) => f.checkId === "plugins.code_safety.manifest_parse_error",
|
||||
"oversized manifest parse error",
|
||||
);
|
||||
expect(finding.severity).toBe("warn");
|
||||
expect(finding.detail).toContain("oversized-plugin");
|
||||
expect(finding.detail).toContain("too large");
|
||||
});
|
||||
|
||||
it("reports scan_failed when plugin code scanner throws during deep audit", async () => {
|
||||
const scanSpy = vi
|
||||
.spyOn(skillScanner, "scanDirectoryWithSummary")
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
*
|
||||
* These functions perform I/O (filesystem, config reads) to detect security issues.
|
||||
*/
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { clearTimeout as clearNodeTimeout, setTimeout as setNodeTimeout } from "node:timers";
|
||||
import {
|
||||
@@ -21,6 +20,7 @@ import { MANIFEST_KEY } from "../compat/legacy-names.js";
|
||||
import type { OpenClawConfig, ConfigFileSnapshot } from "../config/config.js";
|
||||
import { collectIncludePathsRecursive } from "../config/includes-scan.js";
|
||||
import { resolveOAuthDir } from "../config/paths.js";
|
||||
import { readRegularFile, statRegularFile } from "../infra/fs-safe.js";
|
||||
import { normalizeAgentId } from "../routing/session-key.js";
|
||||
import { getOrCreatePromise } from "../shared/lazy-promise.js";
|
||||
import { createLazyRuntimeModule, createLazyRuntimeNamedExport } from "../shared/lazy-runtime.js";
|
||||
@@ -97,9 +97,25 @@ function expandTilde(p: string, env: NodeJS.ProcessEnv): string | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
const MAX_PLUGIN_MANIFEST_BYTES = 1024 * 1024;
|
||||
|
||||
async function readPluginManifestExtensions(pluginPath: string): Promise<string[]> {
|
||||
const manifestPath = path.join(pluginPath, "package.json");
|
||||
const raw = await fs.readFile(manifestPath, "utf-8").catch(() => "");
|
||||
const statResult = await statRegularFile(manifestPath);
|
||||
if (statResult.missing) {
|
||||
return [];
|
||||
}
|
||||
if (statResult.stat.size > MAX_PLUGIN_MANIFEST_BYTES) {
|
||||
throw new Error(
|
||||
`Plugin manifest at ${manifestPath} is too large (${statResult.stat.size} bytes, max ${MAX_PLUGIN_MANIFEST_BYTES})`,
|
||||
);
|
||||
}
|
||||
|
||||
const { buffer } = await readRegularFile({
|
||||
filePath: manifestPath,
|
||||
maxBytes: MAX_PLUGIN_MANIFEST_BYTES,
|
||||
});
|
||||
const raw = buffer.toString("utf-8");
|
||||
if (!raw.trim()) {
|
||||
return [];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user