From 70d1871db75f95b030a972dc51889842459ca823 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Sat, 25 Apr 2026 13:45:07 -0700 Subject: [PATCH] fix(secrets): honor plugin install ledger in web search risk --- src/secrets/runtime-web-tools.test.ts | 47 +++++++++++++++++++++++++++ src/secrets/runtime-web-tools.ts | 13 +++++--- 2 files changed, 56 insertions(+), 4 deletions(-) diff --git a/src/secrets/runtime-web-tools.test.ts b/src/secrets/runtime-web-tools.test.ts index 311784b1501..713ce8debc1 100644 --- a/src/secrets/runtime-web-tools.test.ts +++ b/src/secrets/runtime-web-tools.test.ts @@ -62,6 +62,9 @@ const { )[value], ), })); +const { loadPluginInstallRecordsSyncMock } = vi.hoisted(() => ({ + loadPluginInstallRecordsSyncMock: vi.fn(() => ({})), +})); let secretResolve: typeof import("./resolve.js"); let createResolverContext: typeof import("./runtime-shared.js").createResolverContext; let resolveRuntimeWebTools: typeof import("./runtime-web-tools.js").resolveRuntimeWebTools; @@ -102,6 +105,16 @@ vi.mock("./runtime-web-tools-manifest.runtime.js", () => ({ resolveManifestContractPluginIdsByCompatibilityRuntimePathMock, })); +vi.mock("../plugins/install-ledger-store.js", async () => { + const actual = await vi.importActual( + "../plugins/install-ledger-store.js", + ); + return { + ...actual, + loadPluginInstallRecordsSync: loadPluginInstallRecordsSyncMock, + }; +}); + function asConfig(value: unknown): OpenClawConfig { return value as OpenClawConfig; } @@ -322,6 +335,8 @@ describe("runtime web tools resolution", () => { resolveManifestContractOwnerPluginIdMock.mockClear(); resolveManifestContractPluginIdsMock.mockClear(); resolveManifestContractPluginIdsByCompatibilityRuntimePathMock.mockClear(); + loadPluginInstallRecordsSyncMock.mockReset(); + loadPluginInstallRecordsSyncMock.mockReturnValue({}); }); afterEach(() => { @@ -1074,6 +1089,38 @@ describe("runtime web tools resolution", () => { expect(resolvePluginWebSearchProvidersMock).not.toHaveBeenCalled(); }); + it("uses runtime web search discovery when the managed plugin install ledger is populated", async () => { + loadPluginInstallRecordsSyncMock.mockReturnValue({ + "external-search": { + source: "npm", + spec: "@openclaw/external-search", + }, + }); + + const { metadata } = await runRuntimeWebTools({ + config: asConfig({ + tools: { + web: { + search: { + enabled: true, + }, + }, + }, + }), + env: { + BRAVE_API_KEY: "brave-key", // pragma: allowlist secret + }, + }); + + expect(metadata.search.selectedProvider).toBe("brave"); + expect(resolveBundledWebSearchProvidersFromPublicArtifactsMock).not.toHaveBeenCalled(); + expect(resolvePluginWebSearchProvidersMock).toHaveBeenCalledWith( + expect.objectContaining({ + bundledAllowlistCompat: true, + }), + ); + }); + it("uses bundled public artifacts for bundled web fetch provider discovery", async () => { const { metadata } = await runRuntimeWebTools({ config: asConfig({ diff --git a/src/secrets/runtime-web-tools.ts b/src/secrets/runtime-web-tools.ts index 523ef96fd2f..f984c784af8 100644 --- a/src/secrets/runtime-web-tools.ts +++ b/src/secrets/runtime-web-tools.ts @@ -1,5 +1,6 @@ import type { OpenClawConfig } from "../config/types.openclaw.js"; import { resolveSecretInputRef } from "../config/types.secrets.js"; +import { loadPluginInstallRecordsSync } from "../plugins/install-ledger-store.js"; import type { PluginWebFetchProviderEntry, PluginWebSearchProviderEntry, @@ -124,6 +125,14 @@ async function hasCustomWebSearchPluginRisk(params: { config: OpenClawConfig; env: NodeJS.ProcessEnv; }): Promise { + const installRecords = loadPluginInstallRecordsSync({ + config: params.config, + env: params.env, + }); + if (Object.keys(installRecords).length > 0) { + return true; + } + const plugins = params.config.plugins; if (!plugins) { return false; @@ -131,10 +140,6 @@ async function hasCustomWebSearchPluginRisk(params: { if (Array.isArray(plugins.load?.paths) && plugins.load.paths.length > 0) { return true; } - if (plugins.installs && Object.keys(plugins.installs).length > 0) { - return true; - } - const { resolveManifestContractPluginIds } = await loadRuntimeWebToolsManifest(); const bundledPluginIds = new Set( resolveManifestContractPluginIds({