mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-27 12:56:01 -06:00
fix(hooks): bound HOOK.md reads during hook install validation (#101469)
* fix(hooks): bound HOOK.md reads during hook install validation * fix(hooks): remove unused fs import after switching to bounded reader * test(hooks): cover symlinked install metadata --------- Co-authored-by: Peter Steinberger <steipete@gmail.com>
This commit is contained in:
@@ -466,6 +466,34 @@ describe("installHooksFromPath", () => {
|
||||
expect(fs.existsSync(path.join(hooksDir, "my-hook"))).toBe(false);
|
||||
});
|
||||
|
||||
it("rejects an oversized HOOK.md to prevent OOM during frontmatter parsing", async () => {
|
||||
const stateDir = makeTempDir();
|
||||
const workDir = makeTempDir();
|
||||
const hookDir = path.join(workDir, "my-hook");
|
||||
fs.mkdirSync(hookDir, { recursive: true });
|
||||
fs.writeFileSync(path.join(hookDir, "HOOK.md"), "x".repeat(1024 * 1024 + 1), "utf8");
|
||||
fs.writeFileSync(path.join(hookDir, "handler.ts"), "export default async () => {};\n");
|
||||
|
||||
await expect(
|
||||
installHooksFromPath({ path: hookDir, hooksDir: path.join(stateDir, "hooks") }),
|
||||
).rejects.toThrow(/File exceeds 1048576 bytes/);
|
||||
});
|
||||
|
||||
it.runIf(process.platform !== "win32")("rejects a symlinked HOOK.md", async () => {
|
||||
const stateDir = makeTempDir();
|
||||
const workDir = makeTempDir();
|
||||
const hookDir = path.join(workDir, "my-hook");
|
||||
const externalHookMd = path.join(workDir, "external-HOOK.md");
|
||||
fs.mkdirSync(hookDir, { recursive: true });
|
||||
fs.writeFileSync(externalHookMd, "---\nname: external\n---\n", "utf8");
|
||||
fs.symlinkSync(externalHookMd, path.join(hookDir, "HOOK.md"), "file");
|
||||
fs.writeFileSync(path.join(hookDir, "handler.ts"), "export default async () => {};\n");
|
||||
|
||||
await expect(
|
||||
installHooksFromPath({ path: hookDir, hooksDir: path.join(stateDir, "hooks") }),
|
||||
).rejects.toThrow(/path must be a regular file/);
|
||||
});
|
||||
|
||||
it("classifies hook packages that also declare plugin extensions", async () => {
|
||||
const stateDir = makeTempDir();
|
||||
const pkgDir = makeTempDir();
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
// Hook install service installs hook packages from archives and local sources.
|
||||
import fs from "node:fs/promises";
|
||||
|
||||
import path from "node:path";
|
||||
import { normalizeTrimmedStringList } from "@openclaw/normalization-core/string-normalization";
|
||||
import { MANIFEST_KEY } from "../compat/legacy-names.js";
|
||||
import { resolveSafeInstallDir, unscopedPackageName } from "../infra/install-safe-path.js";
|
||||
import type { NpmIntegrityDrift, NpmSpecResolution } from "../infra/install-source-utils.js";
|
||||
import { readRegularFile } from "../infra/regular-file.js";
|
||||
import { detectBundleManifestFormat } from "../plugins/bundle-manifest.js";
|
||||
import {
|
||||
scanPackageInstallSource,
|
||||
@@ -17,6 +18,10 @@ import { createLazyRuntimeModule } from "../shared/lazy-runtime.js";
|
||||
import { CONFIG_DIR, resolveUserPath } from "../utils.js";
|
||||
import { parseFrontmatter } from "./frontmatter.js";
|
||||
|
||||
// HOOK.md is only parsed for frontmatter; a small cap prevents a malicious or
|
||||
// malformed hook package from OOMing the install path.
|
||||
const HOOK_MD_MAX_BYTES = 1024 * 1024;
|
||||
|
||||
const loadHookInstallRuntime = createLazyRuntimeModule(() => import("./install.runtime.js"));
|
||||
|
||||
/** Logger contract used by hook install and update operations. */
|
||||
@@ -358,8 +363,8 @@ async function resolveHookNameFromDir(hookDir: string): Promise<string> {
|
||||
if (!(await runtime.fileExists(hookMdPath))) {
|
||||
throw new Error(`HOOK.md missing in ${hookDir}`);
|
||||
}
|
||||
const raw = await fs.readFile(hookMdPath, "utf-8");
|
||||
const frontmatter = parseFrontmatter(raw);
|
||||
const { buffer } = await readRegularFile({ filePath: hookMdPath, maxBytes: HOOK_MD_MAX_BYTES });
|
||||
const frontmatter = parseFrontmatter(buffer.toString("utf-8"));
|
||||
return frontmatter.name || path.basename(hookDir);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user