Skip to content

Commit 571d75a

Browse files
committed
fix(plugins): honor plugin tool denylists
1 parent eeed33e commit 571d75a

7 files changed

Lines changed: 176 additions & 26 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ Docs: https://docs.openclaw.ai
5151
- Gateway/sessions: memoize repeated thinking-option enrichment and skip unused cost fallback checks while listing sessions, reducing per-row work on large multi-agent stores. Fixes #76931.
5252
- Agents/tools: use config-only runtime snapshots for plugin tool registration and live runtime config getters, avoiding expensive full secrets snapshot clones on the core-plugin-tools prep path. Fixes #76295.
5353
- Agents/tools: honor the effective tool denylist before constructing optional PDF/media tool factories, so `tools.deny: ["pdf"]` skips PDF setup before later policy filtering. Fixes #76997.
54+
- Plugin tools: honor explicit tool denylists while selecting plugin tool runtimes, so denied plugin tools are not materialized for direct command or gateway surfaces before later policy filtering. Thanks @vincentkoc.
5455
- Agents/bootstrap: keep pending `BOOTSTRAP.md` and bootstrap truncation notices in system-prompt Project Context instead of copying setup text or raw warning diagnostics into WebChat user/runtime context. Fixes #76946.
5556
- Channels/WhatsApp: allow `@whiskeysockets/libsignal-node` in `onlyBuiltDependencies` so pnpm v9+ `blockExoticSubdeps` no longer rejects the baileys git-tarball subdep and silences all inbound agent replies. Fixes #76539. Thanks @ottodeng and @vincentkoc.
5657
- Gateway/install: keep `.env`-managed values in the macOS LaunchAgent env file while still tracking `OPENCLAW_SERVICE_MANAGED_ENV_KEYS`, so regenerated services do not boot without managed auth/provider keys. Fixes #75374.

src/agents/openclaw-plugin-tools.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import type { AnyAgentTool } from "./tools/common.js";
1717

1818
type ResolveOpenClawPluginToolsOptions = OpenClawPluginToolOptions & {
1919
pluginToolAllowlist?: string[];
20+
pluginToolDenylist?: string[];
2021
currentChannelId?: string;
2122
currentThreadTs?: string;
2223
currentMessageId?: string | number;
@@ -81,6 +82,7 @@ export function resolveOpenClawPluginToolsForOptions(params: {
8182
}),
8283
existingToolNames: params.existingToolNames ?? new Set<string>(),
8384
toolAllowlist: params.options?.pluginToolAllowlist,
85+
toolDenylist: params.options?.pluginToolDenylist,
8486
allowGatewaySubagentBinding: params.options?.allowGatewaySubagentBinding,
8587
...(authProfileStore
8688
? {

src/agents/openclaw-tools.browser-plugin.integration.test.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,31 @@ describe("createOpenClawTools browser plugin integration", () => {
142142
);
143143
});
144144

145+
it("forwards plugin tool deny policy to plugin resolution", () => {
146+
hoisted.resolvePluginTools.mockReturnValue([]);
147+
const config = {
148+
plugins: {
149+
allow: ["browser"],
150+
},
151+
} as OpenClawConfig;
152+
153+
resolveOpenClawPluginToolsForOptions({
154+
options: {
155+
config,
156+
pluginToolAllowlist: ["*"],
157+
pluginToolDenylist: ["browser"],
158+
},
159+
resolvedConfig: config,
160+
});
161+
162+
expect(hoisted.resolvePluginTools).toHaveBeenCalledWith(
163+
expect.objectContaining({
164+
toolAllowlist: ["*"],
165+
toolDenylist: ["browser"],
166+
}),
167+
);
168+
});
169+
145170
it("does not pass a stale active snapshot as plugin runtime config for a resolved run config", () => {
146171
const staleSourceConfig = {
147172
plugins: {

src/agents/pi-tools.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -649,6 +649,7 @@ export function createOpenClawCodingTools(options?: {
649649
allowHostBrowserControl: sandbox ? sandbox.browserAllowHostControl : true,
650650
sandboxed: !!sandbox,
651651
pluginToolAllowlist,
652+
pluginToolDenylist,
652653
currentChannelId: options?.currentChannelId,
653654
currentThreadTs: options?.currentThreadTs,
654655
currentMessageId: options?.currentMessageId,

src/gateway/tool-resolution.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
} from "../agents/tool-policy-pipeline.js";
1616
import {
1717
collectExplicitAllowlist,
18+
collectExplicitDenylist,
1819
mergeAlsoAllowPolicy,
1920
resolveToolProfilePolicy,
2021
} from "../agents/tool-policy.js";
@@ -108,6 +109,16 @@ export function resolveGatewayScopedTools(params: {
108109
subagentPolicy,
109110
gatewayRequestedTools.length > 0 ? { allow: gatewayRequestedTools } : undefined,
110111
]),
112+
pluginToolDenylist: collectExplicitDenylist([
113+
profilePolicy,
114+
providerProfilePolicy,
115+
globalPolicy,
116+
globalProviderPolicy,
117+
agentPolicy,
118+
agentProviderPolicy,
119+
groupPolicy,
120+
subagentPolicy,
121+
]),
111122
});
112123

113124
const policyFiltered = applyToolPolicyPipeline({

src/plugins/tools.optional.test.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ function createContext() {
6868
function createResolveToolsParams(params?: {
6969
context?: ReturnType<typeof createContext> & Record<string, unknown>;
7070
toolAllowlist?: readonly string[];
71+
toolDenylist?: readonly string[];
7172
existingToolNames?: Set<string>;
7273
env?: NodeJS.ProcessEnv;
7374
suppressNameConflicts?: boolean;
@@ -76,6 +77,7 @@ function createResolveToolsParams(params?: {
7677
return {
7778
context: (params?.context ?? createContext()) as never,
7879
...(params?.toolAllowlist ? { toolAllowlist: [...params.toolAllowlist] } : {}),
80+
...(params?.toolDenylist ? { toolDenylist: [...params.toolDenylist] } : {}),
7981
...(params?.existingToolNames ? { existingToolNames: params.existingToolNames } : {}),
8082
...(params?.env ? { env: params.env } : {}),
8183
...(params?.suppressNameConflicts ? { suppressNameConflicts: true } : {}),
@@ -2177,6 +2179,30 @@ describe("resolvePluginTools optional tools", () => {
21772179
expectResolvedToolNames(tools, ["browser"]);
21782180
});
21792181

2182+
it("does not materialize plugin tools blocked by explicit deny policy", () => {
2183+
const browserFactory = vi.fn(() => makeTool("browser"));
2184+
const browserEntry: MockRegistryToolEntry = {
2185+
pluginId: "browser",
2186+
optional: false,
2187+
source: "/tmp/browser.js",
2188+
names: ["browser"],
2189+
declaredNames: ["browser"],
2190+
factory: browserFactory,
2191+
};
2192+
setRegistry([browserEntry]);
2193+
2194+
const tools = resolvePluginTools(
2195+
createResolveToolsParams({
2196+
toolAllowlist: ["*"],
2197+
toolDenylist: ["browser"],
2198+
}),
2199+
);
2200+
2201+
expectResolvedToolNames(tools, []);
2202+
expect(browserFactory).not.toHaveBeenCalled();
2203+
expect(loadOpenClawPluginsMock).not.toHaveBeenCalled();
2204+
});
2205+
21802206
it("includes optional tools when wildcard allowlist is active (#76507)", () => {
21812207
setOptionalDemoRegistry();
21822208

0 commit comments

Comments
 (0)