fix(secrets): honor plugin install ledger in web search risk

This commit is contained in:
Vincent Koc
2026-04-25 13:45:07 -07:00
parent 90218364b4
commit 70d1871db7
2 changed files with 56 additions and 4 deletions

View File

@@ -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<typeof import("../plugins/install-ledger-store.js")>(
"../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({

View File

@@ -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<boolean> {
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<string>(
resolveManifestContractPluginIds({