diff --git a/src/hooks/install.test.ts b/src/hooks/install.test.ts index cb1ec50c29..579bff4172 100644 --- a/src/hooks/install.test.ts +++ b/src/hooks/install.test.ts @@ -1,50 +1,27 @@ -import JSZip from "jszip"; import { randomUUID } from "node:crypto"; import fs from "node:fs"; -import fsPromises from "node:fs/promises"; import os from "node:os"; import path from "node:path"; -import * as tar from "tar"; -import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; +import { afterAll, beforeEach, describe, expect, it, vi } from "vitest"; const fixtureRoot = path.join(os.tmpdir(), `openclaw-hook-install-${randomUUID()}`); let tempDirIndex = 0; -let zipHooksBuffer: Buffer; -let zipTraversalBuffer: Buffer; -let tarHooksBuffer: Buffer; -let tarTraversalBuffer: Buffer; -let tarEvilIdBuffer: Buffer; -let tarReservedIdBuffer: Buffer; -let npmPackHooksBuffer: Buffer; +const fixturesDir = path.resolve(process.cwd(), "test", "fixtures", "hooks-install"); +const zipHooksBuffer = fs.readFileSync(path.join(fixturesDir, "zip-hooks.zip")); +const zipTraversalBuffer = fs.readFileSync(path.join(fixturesDir, "zip-traversal.zip")); +const tarHooksBuffer = fs.readFileSync(path.join(fixturesDir, "tar-hooks.tar")); +const tarTraversalBuffer = fs.readFileSync(path.join(fixturesDir, "tar-traversal.tar")); +const tarEvilIdBuffer = fs.readFileSync(path.join(fixturesDir, "tar-evil-id.tar")); +const tarReservedIdBuffer = fs.readFileSync(path.join(fixturesDir, "tar-reserved-id.tar")); +const npmPackHooksBuffer = fs.readFileSync(path.join(fixturesDir, "npm-pack-hooks.tgz")); vi.mock("../process/exec.js", () => ({ runCommandWithTimeout: vi.fn(), })); -async function packToArchive({ - pkgDir, - outDir, - outName, -}: { - pkgDir: string; - outDir: string; - outName: string; -}) { - const dest = path.join(outDir, outName); - fs.rmSync(dest, { force: true }); - await tar.c( - { - gzip: true, - file: dest, - cwd: path.dirname(pkgDir), - }, - [path.basename(pkgDir)], - ); - return dest; -} - function makeTempDir() { + fs.mkdirSync(fixtureRoot, { recursive: true }); const dir = path.join(fixtureRoot, `case-${tempDirIndex++}`); fs.mkdirSync(dir, { recursive: true }); return dir; @@ -54,139 +31,6 @@ const { runCommandWithTimeout } = await import("../process/exec.js"); const { installHooksFromArchive, installHooksFromNpmSpec, installHooksFromPath } = await import("./install.js"); -beforeAll(async () => { - fs.mkdirSync(fixtureRoot, { recursive: true }); - - const zipHooks = new JSZip(); - zipHooks.file( - "package/package.json", - JSON.stringify({ - name: "@openclaw/zip-hooks", - version: "0.0.1", - openclaw: { hooks: ["./hooks/zip-hook"] }, - }), - ); - zipHooks.file( - "package/hooks/zip-hook/HOOK.md", - [ - "---", - "name: zip-hook", - "description: Zip hook", - 'metadata: {"openclaw":{"events":["command:new"]}}', - "---", - "", - "# Zip Hook", - ].join("\n"), - ); - zipHooks.file("package/hooks/zip-hook/handler.ts", "export default async () => {};\n"); - zipHooksBuffer = await zipHooks.generateAsync({ type: "nodebuffer" }); - - const zipTraversal = new JSZip(); - zipTraversal.file("../pwned.txt", "nope\n"); - zipTraversalBuffer = await zipTraversal.generateAsync({ type: "nodebuffer" }); - - const makeTarWithPackage = async (params: { - packageName: string; - hookName: string; - }): Promise => { - const workDir = makeTempDir(); - const archivePath = path.join(workDir, "hooks.tar"); - const pkgDir = path.join(workDir, "package"); - fs.mkdirSync(path.join(pkgDir, "hooks", params.hookName), { recursive: true }); - fs.writeFileSync( - path.join(pkgDir, "package.json"), - JSON.stringify({ - name: params.packageName, - version: "0.0.1", - openclaw: { hooks: [`./hooks/${params.hookName}`] }, - }), - "utf-8", - ); - fs.writeFileSync( - path.join(pkgDir, "hooks", params.hookName, "HOOK.md"), - [ - "---", - `name: ${params.hookName}`, - `description: ${params.hookName}`, - 'metadata: {"openclaw":{"events":["command:new"]}}', - "---", - "", - "# Hook", - ].join("\n"), - "utf-8", - ); - fs.writeFileSync( - path.join(pkgDir, "hooks", params.hookName, "handler.ts"), - "export default async () => {};\n", - "utf-8", - ); - await tar.c({ cwd: workDir, file: archivePath }, ["package"]); - return await fsPromises.readFile(archivePath); - }; - - tarHooksBuffer = await makeTarWithPackage({ - packageName: "@openclaw/tar-hooks", - hookName: "tar-hook", - }); - tarEvilIdBuffer = await makeTarWithPackage({ packageName: "@evil/..", hookName: "evil-hook" }); - tarReservedIdBuffer = await makeTarWithPackage({ - packageName: "@evil/.", - hookName: "reserved-hook", - }); - - const makeTraversalTar = async (): Promise => { - const workDir = makeTempDir(); - const insideDir = path.join(workDir, "inside"); - fs.mkdirSync(insideDir, { recursive: true }); - fs.writeFileSync(path.join(workDir, "outside.txt"), "nope\n", "utf-8"); - const archivePath = path.join(workDir, "traversal.tar"); - await tar.c({ cwd: insideDir, file: archivePath }, ["../outside.txt"]); - return await fsPromises.readFile(archivePath); - }; - - tarTraversalBuffer = await makeTraversalTar(); - - const makeNpmPackTgz = async (): Promise => { - const workDir = makeTempDir(); - const packedName = "test-hooks-0.0.1.tgz"; - const pkgDir = path.join(workDir, "package"); - fs.mkdirSync(path.join(pkgDir, "hooks", "one-hook"), { recursive: true }); - fs.writeFileSync( - path.join(pkgDir, "package.json"), - JSON.stringify({ - name: "@openclaw/test-hooks", - version: "0.0.1", - openclaw: { hooks: ["./hooks/one-hook"] }, - }), - "utf-8", - ); - fs.writeFileSync( - path.join(pkgDir, "hooks", "one-hook", "HOOK.md"), - [ - "---", - "name: one-hook", - "description: One hook", - 'metadata: {"openclaw":{"events":["command:new"]}}', - "---", - "", - "# One Hook", - ].join("\n"), - "utf-8", - ); - fs.writeFileSync( - path.join(pkgDir, "hooks", "one-hook", "handler.ts"), - "export default async () => {};\n", - "utf-8", - ); - - const packTmpDir = makeTempDir(); - const archivePath = await packToArchive({ pkgDir, outDir: packTmpDir, outName: packedName }); - return await fsPromises.readFile(archivePath); - }; - - npmPackHooksBuffer = await makeNpmPackTgz(); -}); - afterAll(() => { try { fs.rmSync(fixtureRoot, { recursive: true, force: true }); diff --git a/test/fixtures/hooks-install/npm-pack-hooks.tgz b/test/fixtures/hooks-install/npm-pack-hooks.tgz new file mode 100644 index 0000000000..ee382e1256 Binary files /dev/null and b/test/fixtures/hooks-install/npm-pack-hooks.tgz differ diff --git a/test/fixtures/hooks-install/tar-evil-id.tar b/test/fixtures/hooks-install/tar-evil-id.tar new file mode 100644 index 0000000000..2b5cab3618 Binary files /dev/null and b/test/fixtures/hooks-install/tar-evil-id.tar differ diff --git a/test/fixtures/hooks-install/tar-hooks.tar b/test/fixtures/hooks-install/tar-hooks.tar new file mode 100644 index 0000000000..6574839a59 Binary files /dev/null and b/test/fixtures/hooks-install/tar-hooks.tar differ diff --git a/test/fixtures/hooks-install/tar-reserved-id.tar b/test/fixtures/hooks-install/tar-reserved-id.tar new file mode 100644 index 0000000000..7c1ea84420 Binary files /dev/null and b/test/fixtures/hooks-install/tar-reserved-id.tar differ diff --git a/test/fixtures/hooks-install/tar-traversal.tar b/test/fixtures/hooks-install/tar-traversal.tar new file mode 100644 index 0000000000..cf0530ab9d Binary files /dev/null and b/test/fixtures/hooks-install/tar-traversal.tar differ diff --git a/test/fixtures/hooks-install/zip-hooks.zip b/test/fixtures/hooks-install/zip-hooks.zip new file mode 100644 index 0000000000..444d26c5ab Binary files /dev/null and b/test/fixtures/hooks-install/zip-hooks.zip differ diff --git a/test/fixtures/hooks-install/zip-traversal.zip b/test/fixtures/hooks-install/zip-traversal.zip new file mode 100644 index 0000000000..ef09ab9ba4 Binary files /dev/null and b/test/fixtures/hooks-install/zip-traversal.zip differ