mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 07:20:43 +00:00
fix(plugins): rename bundled allowlist discovery policy
This commit is contained in:
@@ -84,13 +84,13 @@ describe("implicit provider plugin allowlist compatibility", () => {
|
||||
).toEqual(["kilocode", "moonshot", "openrouter"]);
|
||||
});
|
||||
|
||||
it("respects allowlist for bundled plugins when bundledMode is respect-allow", () => {
|
||||
it("respects allowlist for bundled plugins when bundledDiscovery is allowlist", () => {
|
||||
const config = withBundledPluginEnablementCompat({
|
||||
config: withBundledPluginAllowlistCompat({
|
||||
config: {
|
||||
plugins: {
|
||||
allow: ["openrouter"],
|
||||
bundledMode: "respect-allow",
|
||||
bundledDiscovery: "allowlist",
|
||||
},
|
||||
},
|
||||
pluginIds: ["kilocode", "moonshot"],
|
||||
|
||||
@@ -161,12 +161,15 @@ export async function loadAndMaybeMigrateDoctorConfig(params: {
|
||||
}));
|
||||
}
|
||||
|
||||
const { collectPluginToolAllowlistWarnings } =
|
||||
const { collectBundledProviderAllowlistPolicyWarnings, collectPluginToolAllowlistWarnings } =
|
||||
await import("./doctor/shared/plugin-tool-allowlist-warnings.js");
|
||||
const pluginToolAllowlistWarnings = collectPluginToolAllowlistWarnings({
|
||||
cfg: candidate,
|
||||
env: process.env,
|
||||
});
|
||||
const pluginToolAllowlistWarnings = [
|
||||
...collectPluginToolAllowlistWarnings({
|
||||
cfg: candidate,
|
||||
env: process.env,
|
||||
}),
|
||||
...collectBundledProviderAllowlistPolicyWarnings({ cfg: candidate }),
|
||||
];
|
||||
if (pluginToolAllowlistWarnings.length > 0) {
|
||||
note(sanitizeDoctorNote(pluginToolAllowlistWarnings.join("\n")), "Doctor warnings");
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import type { PluginManifestRegistry } from "../../../plugins/manifest-registry.js";
|
||||
import { collectPluginToolAllowlistWarnings } from "./plugin-tool-allowlist-warnings.js";
|
||||
import {
|
||||
collectBundledProviderAllowlistPolicyWarnings,
|
||||
collectPluginToolAllowlistWarnings,
|
||||
} from "./plugin-tool-allowlist-warnings.js";
|
||||
|
||||
const manifestRegistry: PluginManifestRegistry = {
|
||||
diagnostics: [],
|
||||
@@ -109,4 +112,29 @@ describe("collectPluginToolAllowlistWarnings", () => {
|
||||
|
||||
expect(warnings).toEqual([]);
|
||||
});
|
||||
|
||||
it("warns when restrictive plugins.allow leaves bundled provider discovery in compat mode", () => {
|
||||
const warnings = collectBundledProviderAllowlistPolicyWarnings({
|
||||
cfg: {
|
||||
plugins: { allow: ["telegram"] },
|
||||
},
|
||||
});
|
||||
|
||||
expect(warnings).toEqual([
|
||||
expect.stringContaining('set plugins.bundledDiscovery to "allowlist"'),
|
||||
]);
|
||||
});
|
||||
|
||||
it("does not warn when bundled provider discovery follows the allowlist", () => {
|
||||
const warnings = collectBundledProviderAllowlistPolicyWarnings({
|
||||
cfg: {
|
||||
plugins: {
|
||||
allow: ["telegram"],
|
||||
bundledDiscovery: "allowlist",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(warnings).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -193,3 +193,21 @@ export function collectPluginToolAllowlistWarnings(params: {
|
||||
|
||||
return warnings;
|
||||
}
|
||||
|
||||
export function collectBundledProviderAllowlistPolicyWarnings(params: {
|
||||
cfg: OpenClawConfig;
|
||||
}): string[] {
|
||||
if (params.cfg.plugins?.enabled === false) {
|
||||
return [];
|
||||
}
|
||||
const allow = params.cfg.plugins?.allow;
|
||||
if (!Array.isArray(allow) || allow.length === 0) {
|
||||
return [];
|
||||
}
|
||||
if (params.cfg.plugins?.bundledDiscovery === "allowlist") {
|
||||
return [];
|
||||
}
|
||||
return [
|
||||
'- plugins.allow is restrictive, but bundled provider discovery is still in legacy compatibility mode. Bundled provider plugins can still appear in runtime provider inventories; set plugins.bundledDiscovery to "allowlist" after confirming omitted bundled providers are intentionally blocked.',
|
||||
];
|
||||
}
|
||||
|
||||
@@ -24186,12 +24186,12 @@ export const GENERATED_BASE_CONFIG_SCHEMA: BaseConfigSchemaResponse = {
|
||||
description:
|
||||
"Per-plugin settings keyed by plugin ID including enablement and plugin-specific runtime configuration payloads. Use this for scoped plugin tuning without changing global loader policy.",
|
||||
},
|
||||
bundledMode: {
|
||||
bundledDiscovery: {
|
||||
type: "string",
|
||||
enum: ["compat", "respect-allow"],
|
||||
title: "Bundled Plugin Mode",
|
||||
enum: ["compat", "allowlist"],
|
||||
title: "Bundled Plugin Discovery",
|
||||
description:
|
||||
'Controls whether bundled plugins bypass plugins.allow on runtime discovery paths. "compat" (default) preserves legacy behavior where bundled provider plugins are force-loaded on every chat turn. "respect-allow" gates bundled plugins by the allowlist the same way third-party plugins are gated.',
|
||||
'Controls bundled plugin runtime discovery when plugins.allow is configured. "compat" (default) preserves legacy behavior where bundled provider plugins can be force-loaded on every chat turn. "allowlist" gates bundled provider plugins by plugins.allow like third-party plugins.',
|
||||
},
|
||||
},
|
||||
additionalProperties: false,
|
||||
@@ -28872,9 +28872,9 @@ export const GENERATED_BASE_CONFIG_SCHEMA: BaseConfigSchemaResponse = {
|
||||
help: "Optional allowlist of plugin IDs; when set, only listed plugins are eligible to load. Configured bundled chat channels can still activate their bundled plugin when the channel is explicitly enabled in config. Use this to enforce approved extension inventories in controlled environments.",
|
||||
tags: ["access"],
|
||||
},
|
||||
"plugins.bundledMode": {
|
||||
label: "Bundled Plugin Mode",
|
||||
help: 'Controls whether bundled plugins bypass plugins.allow on runtime discovery paths. "compat" (default) preserves legacy behavior where bundled provider plugins are force-loaded on every chat turn. "respect-allow" gates bundled plugins by the allowlist the same way third-party plugins are gated.',
|
||||
"plugins.bundledDiscovery": {
|
||||
label: "Bundled Plugin Discovery",
|
||||
help: 'Controls bundled plugin runtime discovery when plugins.allow is configured. "compat" (default) preserves legacy behavior where bundled provider plugins can be force-loaded on every chat turn. "allowlist" gates bundled provider plugins by plugins.allow like third-party plugins.',
|
||||
tags: ["advanced"],
|
||||
},
|
||||
"plugins.deny": {
|
||||
|
||||
@@ -1212,8 +1212,8 @@ export const FIELD_HELP: Record<string, string> = {
|
||||
'Select the active memory plugin by id, or "none" to disable memory plugins.',
|
||||
"plugins.slots.contextEngine":
|
||||
"Selects the active context engine plugin by id so one plugin provides context orchestration behavior.",
|
||||
"plugins.bundledMode":
|
||||
'Controls whether bundled plugins bypass plugins.allow on runtime discovery paths. "compat" (default) preserves legacy behavior where bundled provider plugins are force-loaded on every chat turn. "respect-allow" gates bundled plugins by the allowlist the same way third-party plugins are gated.',
|
||||
"plugins.bundledDiscovery":
|
||||
'Controls bundled plugin runtime discovery when plugins.allow is configured. "compat" (default) preserves legacy behavior where bundled provider plugins can be force-loaded on every chat turn. "allowlist" gates bundled provider plugins by plugins.allow like third-party plugins.',
|
||||
"plugins.entries":
|
||||
"Per-plugin settings keyed by plugin ID including enablement and plugin-specific runtime configuration payloads. Use this for scoped plugin tuning without changing global loader policy.",
|
||||
"plugins.entries.*.enabled":
|
||||
|
||||
@@ -905,7 +905,7 @@ export const FIELD_LABELS: Record<string, string> = {
|
||||
plugins: "Plugins",
|
||||
"plugins.enabled": "Enable Plugins",
|
||||
"plugins.allow": "Plugin Allowlist",
|
||||
"plugins.bundledMode": "Bundled Plugin Mode",
|
||||
"plugins.bundledDiscovery": "Bundled Plugin Discovery",
|
||||
"plugins.deny": "Plugin Denylist",
|
||||
"plugins.load": "Plugin Loader",
|
||||
"plugins.load.paths": "Plugin Load Paths",
|
||||
|
||||
@@ -52,15 +52,15 @@ export type PluginsConfig = {
|
||||
/** Optional plugin denylist (plugin ids). */
|
||||
deny?: string[];
|
||||
/**
|
||||
* Controls whether bundled plugins bypass `allow` / `entries` on runtime
|
||||
* provider discovery paths.
|
||||
* Controls how bundled plugins participate in runtime provider discovery when
|
||||
* `allow` is configured.
|
||||
*
|
||||
* - `"compat"` (default): bundled provider plugins are force-loaded on
|
||||
* every chat turn regardless of the allowlist (legacy behavior).
|
||||
* - `"respect-allow"`: bundled provider plugins are gated by `allow` and
|
||||
* `entries.<id>.enabled` the same way third-party plugins are.
|
||||
* - `"allowlist"`: bundled provider plugins are gated by `allow` and
|
||||
* `entries.<id>.enabled` like third-party plugins.
|
||||
*/
|
||||
bundledMode?: "compat" | "respect-allow";
|
||||
bundledDiscovery?: "compat" | "allowlist";
|
||||
load?: PluginsLoadConfig;
|
||||
slots?: PluginSlotsConfig;
|
||||
entries?: Record<string, PluginEntryConfig>;
|
||||
|
||||
@@ -1073,7 +1073,7 @@ export const OpenClawSchema = z
|
||||
.strict()
|
||||
.optional(),
|
||||
entries: z.record(z.string(), PluginEntrySchema).optional(),
|
||||
bundledMode: z.enum(["compat", "respect-allow"]).optional(),
|
||||
bundledDiscovery: z.enum(["compat", "allowlist"]).optional(),
|
||||
})
|
||||
.strict()
|
||||
.optional(),
|
||||
|
||||
@@ -79,10 +79,10 @@ export function withActivatedPluginIds(params: {
|
||||
return params.config;
|
||||
}
|
||||
const originalAllow = params.config?.plugins?.allow ?? [];
|
||||
// Empty allowlists are still open; respect-allow only stops compat from widening configured allowlists.
|
||||
const respectAllow =
|
||||
params.config?.plugins?.bundledMode === "respect-allow" && originalAllow.length > 0;
|
||||
const originalAllowSet = respectAllow ? new Set(originalAllow) : undefined;
|
||||
// Empty allowlists are still open; allowlist mode only stops compat from widening configured allowlists.
|
||||
const useAllowlistDiscovery =
|
||||
params.config?.plugins?.bundledDiscovery === "allowlist" && originalAllow.length > 0;
|
||||
const originalAllowSet = useAllowlistDiscovery ? new Set(originalAllow) : undefined;
|
||||
const allow = new Set(originalAllow);
|
||||
const entries = {
|
||||
...params.config?.plugins?.entries,
|
||||
|
||||
@@ -6,7 +6,7 @@ export function withBundledPluginAllowlistCompat(params: {
|
||||
config: OpenClawConfig | undefined;
|
||||
pluginIds: readonly string[];
|
||||
}): OpenClawConfig | undefined {
|
||||
if (params.config?.plugins?.bundledMode === "respect-allow") {
|
||||
if (params.config?.plugins?.bundledDiscovery === "allowlist") {
|
||||
return params.config;
|
||||
}
|
||||
const allow = params.config?.plugins?.allow;
|
||||
@@ -42,8 +42,8 @@ export function withBundledPluginEnablementCompat(params: {
|
||||
}): OpenClawConfig | undefined {
|
||||
const existingEntries = params.config?.plugins?.entries ?? {};
|
||||
const forcePluginsEnabled = params.config?.plugins?.enabled === false;
|
||||
const respectAllow = params.config?.plugins?.bundledMode === "respect-allow";
|
||||
const allowSet = respectAllow ? new Set(params.config?.plugins?.allow ?? []) : undefined;
|
||||
const useAllowlistDiscovery = params.config?.plugins?.bundledDiscovery === "allowlist";
|
||||
const allowSet = useAllowlistDiscovery ? new Set(params.config?.plugins?.allow ?? []) : undefined;
|
||||
let changed = false;
|
||||
const nextEntries: Record<string, PluginEntryConfig> = { ...existingEntries };
|
||||
|
||||
|
||||
@@ -593,7 +593,7 @@ describe("resolvePluginProviders", () => {
|
||||
).toEqual(["legacy-auth-owner"]);
|
||||
});
|
||||
|
||||
it("filters bundled provider plugins by allowlist when bundledMode is respect-allow", () => {
|
||||
it("filters bundled provider plugins by allowlist when bundledDiscovery is allowlist", () => {
|
||||
setManifestPlugins([
|
||||
createManifestProviderPlugin({
|
||||
id: "kilocode",
|
||||
@@ -619,7 +619,7 @@ describe("resolvePluginProviders", () => {
|
||||
config: {
|
||||
plugins: {
|
||||
allow: ["openrouter"],
|
||||
bundledMode: "respect-allow",
|
||||
bundledDiscovery: "allowlist",
|
||||
},
|
||||
},
|
||||
env: {} as NodeJS.ProcessEnv,
|
||||
|
||||
@@ -255,7 +255,7 @@ export function resolveDiscoveredProviderPluginIds(params: {
|
||||
const { registry, onlyPluginIdSet } = loadScopedProviderRegistry(params);
|
||||
const providerSurfacePluginIds = resolveProviderSurfacePluginIdSet({ ...params, registry });
|
||||
const shouldFilterUntrustedWorkspacePlugins = params.includeUntrustedWorkspacePlugins === false;
|
||||
const shouldFilterBundledByAllowlist = params.config?.plugins?.bundledMode === "respect-allow";
|
||||
const shouldFilterBundledByAllowlist = params.config?.plugins?.bundledDiscovery === "allowlist";
|
||||
const normalizedConfig = normalizePluginsConfigWithRegistry(params.config?.plugins, registry);
|
||||
return listRegistryPluginIds(registry, (plugin) => {
|
||||
if (
|
||||
@@ -313,7 +313,7 @@ export function resolveDiscoverableProviderOwnerPluginIds(params: {
|
||||
includeUntrustedWorkspacePlugins?: boolean;
|
||||
}): string[] {
|
||||
const shouldFilterUntrustedWorkspacePlugins = params.includeUntrustedWorkspacePlugins === false;
|
||||
const shouldFilterBundledByAllowlist = params.config?.plugins?.bundledMode === "respect-allow";
|
||||
const shouldFilterBundledByAllowlist = params.config?.plugins?.bundledDiscovery === "allowlist";
|
||||
return resolveProviderOwnerPluginIds({
|
||||
...params,
|
||||
isEligible: (plugin, normalizedConfig) =>
|
||||
|
||||
@@ -88,7 +88,7 @@ describe("web provider public artifact manifest fallback", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps explicit bundled web-search public artifact candidates inside respect-allow", () => {
|
||||
it("keeps explicit bundled web-search public artifact candidates inside allowlist discovery", () => {
|
||||
const resolveExplicitWebSearchProviders =
|
||||
mocks.resolveBundledExplicitWebSearchProvidersFromPublicArtifacts as unknown as {
|
||||
mockImplementation: (
|
||||
@@ -105,7 +105,7 @@ describe("web provider public artifact manifest fallback", () => {
|
||||
config: {
|
||||
plugins: {
|
||||
allow: ["fallback-search"],
|
||||
bundledMode: "respect-allow",
|
||||
bundledDiscovery: "allowlist",
|
||||
},
|
||||
},
|
||||
onlyPluginIds: ["blocked-search", "fallback-search"],
|
||||
@@ -117,7 +117,7 @@ describe("web provider public artifact manifest fallback", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps manifest bundled web-fetch public artifact candidates inside respect-allow", () => {
|
||||
it("keeps manifest bundled web-fetch public artifact candidates inside allowlist discovery", () => {
|
||||
mocks.loadPluginMetadataSnapshot.mockReturnValueOnce({
|
||||
diagnostics: [],
|
||||
plugins: [
|
||||
@@ -140,7 +140,7 @@ describe("web provider public artifact manifest fallback", () => {
|
||||
config: {
|
||||
plugins: {
|
||||
allow: ["fallback-fetch"],
|
||||
bundledMode: "respect-allow",
|
||||
bundledDiscovery: "allowlist",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -26,13 +26,13 @@ type BundledCandidateResolution = {
|
||||
manifestRecords?: readonly PluginManifestRecord[];
|
||||
};
|
||||
|
||||
function filterRespectAllowBundledPluginIds(
|
||||
function filterAllowlistedBundledPluginIds(
|
||||
config: PluginLoadOptions["config"] | undefined,
|
||||
pluginIds: readonly string[],
|
||||
) {
|
||||
const allow = config?.plugins?.allow;
|
||||
if (
|
||||
config?.plugins?.bundledMode !== "respect-allow" ||
|
||||
config?.plugins?.bundledDiscovery !== "allowlist" ||
|
||||
!Array.isArray(allow) ||
|
||||
allow.length === 0
|
||||
) {
|
||||
@@ -57,7 +57,7 @@ function resolveBundledCandidatePluginIds(params: {
|
||||
: resolveBundledWebFetchResolutionConfig(params).config;
|
||||
if (params.onlyPluginIds && params.onlyPluginIds.length > 0) {
|
||||
return {
|
||||
pluginIds: filterRespectAllowBundledPluginIds(resolvedConfig, [
|
||||
pluginIds: filterAllowlistedBundledPluginIds(resolvedConfig, [
|
||||
...new Set(params.onlyPluginIds),
|
||||
]).toSorted((left, right) => left.localeCompare(right)),
|
||||
};
|
||||
@@ -72,7 +72,7 @@ function resolveBundledCandidatePluginIds(params: {
|
||||
origin: "bundled",
|
||||
});
|
||||
return {
|
||||
pluginIds: filterRespectAllowBundledPluginIds(resolvedConfig, candidates.pluginIds ?? []),
|
||||
pluginIds: filterAllowlistedBundledPluginIds(resolvedConfig, candidates.pluginIds ?? []),
|
||||
...(candidates.manifestRecords ? { manifestRecords: candidates.manifestRecords } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -482,7 +482,7 @@ describe("resolvePluginWebSearchProviders", () => {
|
||||
expectScopedWebSearchCandidates(["brave"]);
|
||||
});
|
||||
|
||||
it("keeps respect-allow web-search provider discovery scoped to the configured allowlist", () => {
|
||||
it("keeps allowlist web-search provider discovery scoped to the configured allowlist", () => {
|
||||
loadInstalledPluginManifestRegistryMock.mockReturnValueOnce({
|
||||
plugins: [
|
||||
createWebSearchManifestRecord({ id: "brave", providerId: "brave" }),
|
||||
@@ -495,7 +495,7 @@ describe("resolvePluginWebSearchProviders", () => {
|
||||
config: {
|
||||
plugins: {
|
||||
allow: ["brave"],
|
||||
bundledMode: "respect-allow",
|
||||
bundledDiscovery: "allowlist",
|
||||
},
|
||||
},
|
||||
bundledAllowlistCompat: true,
|
||||
@@ -510,7 +510,7 @@ describe("resolvePluginWebSearchProviders", () => {
|
||||
config: expect.objectContaining({
|
||||
plugins: expect.objectContaining({
|
||||
allow: ["brave"],
|
||||
bundledMode: "respect-allow",
|
||||
bundledDiscovery: "allowlist",
|
||||
entries: { brave: { enabled: true } },
|
||||
}),
|
||||
}),
|
||||
|
||||
Reference in New Issue
Block a user