mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-05-03 14:10:43 +00:00
Merge pull request #2843 from QwenLM/fix/coretools-mcp-bypass
fix(permissions): allow non-core tools to bypass coreTools allowlist
This commit is contained in:
commit
4e5a8a759b
2 changed files with 93 additions and 3 deletions
|
|
@ -1254,6 +1254,57 @@ describe('PermissionManager', () => {
|
||||||
expect(await pm.isToolEnabled('read_file')).toBe(true);
|
expect(await pm.isToolEnabled('read_file')).toBe(true);
|
||||||
expect(await pm.isToolEnabled('run_shell_command')).toBe(true); // not denied, just unreviewed
|
expect(await pm.isToolEnabled('run_shell_command')).toBe(true); // not denied, just unreviewed
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Non-core tools bypass coreTools allowlist
|
||||||
|
it('MCP tools bypass coreTools allowlist check', async () => {
|
||||||
|
pm = new PermissionManager(makeConfig({ coreTools: ['read_file'] }));
|
||||||
|
pm.initialize();
|
||||||
|
// MCP tools should be enabled even if not in coreTools
|
||||||
|
expect(
|
||||||
|
await pm.isToolEnabled('mcp__markitdown__convert_to_markdown'),
|
||||||
|
).toBe(true);
|
||||||
|
expect(await pm.isToolEnabled('mcp__puppeteer__navigate')).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Skill tool bypasses coreTools allowlist check', async () => {
|
||||||
|
pm = new PermissionManager(makeConfig({ coreTools: ['read_file'] }));
|
||||||
|
pm.initialize();
|
||||||
|
expect(await pm.isToolEnabled('skill')).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Agent tool bypasses coreTools allowlist check', async () => {
|
||||||
|
pm = new PermissionManager(makeConfig({ coreTools: ['read_file'] }));
|
||||||
|
pm.initialize();
|
||||||
|
expect(await pm.isToolEnabled('agent')).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('exit_plan_mode tool bypasses coreTools allowlist check', async () => {
|
||||||
|
pm = new PermissionManager(makeConfig({ coreTools: ['read_file'] }));
|
||||||
|
pm.initialize();
|
||||||
|
expect(await pm.isToolEnabled('exit_plan_mode')).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('ask_user_question tool bypasses coreTools allowlist check', async () => {
|
||||||
|
pm = new PermissionManager(makeConfig({ coreTools: ['read_file'] }));
|
||||||
|
pm.initialize();
|
||||||
|
expect(await pm.isToolEnabled('ask_user_question')).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Non-core tools still respect deny rules', async () => {
|
||||||
|
pm = new PermissionManager(
|
||||||
|
makeConfig({
|
||||||
|
coreTools: ['read_file'],
|
||||||
|
permissionsDeny: ['mcp__markitdown'],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
pm.initialize();
|
||||||
|
// MCP tool should be disabled due to deny rule, even though it bypasses coreTools
|
||||||
|
expect(
|
||||||
|
await pm.isToolEnabled('mcp__markitdown__convert_to_markdown'),
|
||||||
|
).toBe(false);
|
||||||
|
// Other MCP tools without deny rule should still be enabled
|
||||||
|
expect(await pm.isToolEnabled('mcp__puppeteer__navigate')).toBe(true);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('session rules', () => {
|
describe('session rules', () => {
|
||||||
|
|
|
||||||
|
|
@ -396,6 +396,35 @@ export class PermissionManager {
|
||||||
// Registry-level helper
|
// Registry-level helper
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Core tools that are subject to the coreTools allowlist check.
|
||||||
|
* Tools not in this set (MCP, Skill, Agent, etc.) bypass the check.
|
||||||
|
*/
|
||||||
|
private static readonly CORE_TOOLS = new Set([
|
||||||
|
'read_file',
|
||||||
|
'write_file',
|
||||||
|
'edit',
|
||||||
|
'glob',
|
||||||
|
'grep_search',
|
||||||
|
'run_shell_command',
|
||||||
|
'list_directory',
|
||||||
|
'web_fetch',
|
||||||
|
'web_search',
|
||||||
|
'todo_write',
|
||||||
|
'save_memory',
|
||||||
|
'lsp',
|
||||||
|
'cron_create',
|
||||||
|
'cron_list',
|
||||||
|
'cron_delete',
|
||||||
|
]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a tool is a core tool subject to the coreTools allowlist check.
|
||||||
|
*/
|
||||||
|
private isCoreTool(toolName: string): boolean {
|
||||||
|
return PermissionManager.CORE_TOOLS.has(toolName);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine whether a tool should be present in the tool registry.
|
* Determine whether a tool should be present in the tool registry.
|
||||||
*
|
*
|
||||||
|
|
@ -403,13 +432,23 @@ export class PermissionManager {
|
||||||
* (i.e. a whole-tool deny) matches. Specifier-based deny rules such as
|
* (i.e. a whole-tool deny) matches. Specifier-based deny rules such as
|
||||||
* `"Bash(rm -rf *)"` do NOT remove the tool from the registry – they only
|
* `"Bash(rm -rf *)"` do NOT remove the tool from the registry – they only
|
||||||
* deny specific invocations at runtime.
|
* deny specific invocations at runtime.
|
||||||
|
*
|
||||||
|
* Non-core tools (MCP, Skill, Agent, etc.) skip the coreTools allowlist
|
||||||
|
* check because they are dynamically discovered or essential for system
|
||||||
|
* operation.
|
||||||
*/
|
*/
|
||||||
async isToolEnabled(toolName: string): Promise<boolean> {
|
async isToolEnabled(toolName: string): Promise<boolean> {
|
||||||
const canonicalName = resolveToolName(toolName);
|
const canonicalName = resolveToolName(toolName);
|
||||||
|
|
||||||
// If a coreTools allowlist is active, only explicitly listed tools are
|
// Non-core tools bypass coreTools allowlist check
|
||||||
// registered. This mirrors the legacy `tools.core` whitelist semantic:
|
if (!this.isCoreTool(canonicalName)) {
|
||||||
// any tool NOT in the allowlist is excluded from the registry entirely.
|
const decision = await this.evaluate({ toolName: canonicalName });
|
||||||
|
return decision !== 'deny';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Core tools: if a coreTools allowlist is active, only explicitly listed
|
||||||
|
// tools are registered. This mirrors the legacy `tools.core` whitelist
|
||||||
|
// semantic: any tool NOT in the allowlist is excluded from the registry.
|
||||||
if (this.coreToolsAllowList !== null && this.coreToolsAllowList.size > 0) {
|
if (this.coreToolsAllowList !== null && this.coreToolsAllowList.size > 0) {
|
||||||
if (!this.coreToolsAllowList.has(canonicalName)) {
|
if (!this.coreToolsAllowList.has(canonicalName)) {
|
||||||
return false;
|
return false;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue