
























@@ -2566,6 +2566,132 @@ describe("installPluginFromNpmSpec", () => {
25662566});
25672567});
256825682569+it("emits archive source family after installing a local npm-pack archive", async () => {
2570+const root = suiteTempRootTracker.makeTempDir();
2571+const npmDir = path.join(root, "npm");
2572+const extensionsDir = path.join(root, "extensions");
2573+const packageName = "npm-pack-security-event";
2574+const archivePath = await ensureDynamicArchiveTemplate({
2575+outName: "npm-pack-security-event.tgz",
2576+packageJson: {
2577+name: packageName,
2578+version: "1.2.3",
2579+openclaw: { extensions: ["./dist/index.js"] },
2580+},
2581+withDistIndex: true,
2582+});
2583+vi.mocked(runCommandWithTimeout).mockResolvedValueOnce({
2584+code: 0,
2585+killed: false,
2586+signal: null,
2587+stderr: "",
2588+termination: "exit",
2589+stdout: JSON.stringify([
2590+{
2591+filename: path.basename(archivePath),
2592+name: packageName,
2593+version: "1.2.3",
2594+integrity: "sha512-test",
2595+shasum: "abc123",
2596+},
2597+]),
2598+});
2599+mockSuccessfulManagedNpmInstall({ packageName, version: "1.2.3" });
2600+const captured = captureSecurityEvents();
2601+2602+let result: Awaited<ReturnType<typeof installPluginFromNpmPackArchive>>;
2603+try {
2604+result = await installPluginFromNpmPackArchive({
2605+ archivePath,
2606+ extensionsDir,
2607+ npmDir,
2608+});
2609+} finally {
2610+captured.stop();
2611+}
2612+2613+expect(result!.ok).toBe(true);
2614+expect(captured.events).toHaveLength(1);
2615+expect(captured.events[0]).toMatchObject({
2616+category: "plugin",
2617+action: "plugin.installed",
2618+outcome: "success",
2619+target: { kind: "plugin", name: packageName },
2620+attributes: {
2621+source_family: "archive",
2622+mode: "install",
2623+},
2624+});
2625+});
2626+2627+it("preserves archive source family when a local npm-pack archive scan is blocked", async () => {
2628+const root = suiteTempRootTracker.makeTempDir();
2629+const npmDir = path.join(root, "npm");
2630+const extensionsDir = path.join(root, "extensions");
2631+const packageName = "npm-pack-blocked-security-event";
2632+const archivePath = await ensureDynamicArchiveTemplate({
2633+outName: "npm-pack-blocked-security-event.tgz",
2634+packageJson: {
2635+name: packageName,
2636+version: "1.2.3",
2637+openclaw: { extensions: ["./dist/index.js"] },
2638+},
2639+withDistIndex: true,
2640+});
2641+vi.mocked(runCommandWithTimeout).mockResolvedValueOnce({
2642+code: 0,
2643+killed: false,
2644+signal: null,
2645+stderr: "",
2646+termination: "exit",
2647+stdout: JSON.stringify([
2648+{
2649+filename: path.basename(archivePath),
2650+name: packageName,
2651+version: "1.2.3",
2652+integrity: "sha512-test",
2653+shasum: "abc123",
2654+},
2655+]),
2656+});
2657+mockSuccessfulManagedNpmInstall({ packageName, version: "1.2.3" });
2658+const scanSpy = vi.spyOn(installSecurityScan, "scanPackageInstallSource").mockResolvedValueOnce({
2659+blocked: {
2660+code: "security_scan_blocked",
2661+reason: "blocked by package scan",
2662+},
2663+});
2664+const captured = captureSecurityEvents();
2665+2666+let result: Awaited<ReturnType<typeof installPluginFromNpmPackArchive>>;
2667+try {
2668+result = await installPluginFromNpmPackArchive({
2669+ archivePath,
2670+ extensionsDir,
2671+ npmDir,
2672+});
2673+} finally {
2674+captured.stop();
2675+scanSpy.mockRestore();
2676+}
2677+2678+expect(result.ok).toBe(false);
2679+if (!result.ok) {
2680+expect(result.code).toBe(PLUGIN_INSTALL_ERROR_CODE.SECURITY_SCAN_BLOCKED);
2681+}
2682+expect(captured.events).toHaveLength(1);
2683+expect(captured.events[0]).toMatchObject({
2684+category: "plugin",
2685+action: "plugin.audit.failed",
2686+outcome: "denied",
2687+target: { kind: "plugin", name: packageName },
2688+attributes: {
2689+source_family: "archive",
2690+mode: "install",
2691+},
2692+});
2693+});
2694+25692695it("emits effective install mode when requested npm update creates a new target", async () => {
25702696const root = suiteTempRootTracker.makeTempDir();
25712697const npmDir = path.join(root, "npm");
@@ -2747,14 +2873,20 @@ describe("installPluginFromNpmSpec", () => {
27472873},
27482874]),
27492875});
2876+const captured = captureSecurityEvents();
275028772751-const result = await installPluginFromNpmPackArchive({
2752- archivePath,
2753- extensionsDir,
2754- npmDir,
2755-config: configWithInstallPolicy(scriptPath, logPath),
2756-dryRun: true,
2757-});
2878+let result: Awaited<ReturnType<typeof installPluginFromNpmPackArchive>>;
2879+try {
2880+result = await installPluginFromNpmPackArchive({
2881+ archivePath,
2882+ extensionsDir,
2883+ npmDir,
2884+config: configWithInstallPolicy(scriptPath, logPath),
2885+dryRun: true,
2886+});
2887+} finally {
2888+captured.stop();
2889+}
2758289027592891expect(result.ok).toBe(false);
27602892if (!result.ok) {
@@ -2775,6 +2907,17 @@ describe("installPluginFromNpmSpec", () => {
27752907});
27762908expect(requests[0]?.sourcePath).toBe(archivePath);
27772909expect(requests[0]?.sourcePathKind).toBe("file");
2910+expect(captured.events).toHaveLength(1);
2911+expect(captured.events[0]).toMatchObject({
2912+category: "plugin",
2913+action: "plugin.audit.failed",
2914+outcome: "denied",
2915+target: { kind: "plugin", name: "npm-pack-policy-archive" },
2916+attributes: {
2917+source_family: "archive",
2918+mode: "install",
2919+},
2920+});
27782921});
27792922});
27802923此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。