diff --git a/docs/reference/tools.md b/docs/reference/tools.md index c72888d072..09f0518c07 100644 --- a/docs/reference/tools.md +++ b/docs/reference/tools.md @@ -63,29 +63,62 @@ details. ## Available tools -The following table lists all available tools, categorized by their primary -function. +The following sections list all available tools, categorized by their primary +function. For detailed parameter information, see the linked documentation for +each tool. -| Category | Tool | Kind | Description | -| :---------- | :----------------------------------------------- | :------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| Execution | [`run_shell_command`](../tools/shell.md) | `Execute` | Executes arbitrary shell commands. Supports interactive sessions and background processes. Requires manual confirmation.

**Parameters:** `command`, `description`, `dir_path`, `is_background` | -| File System | [`glob`](../tools/file-system.md) | `Search` | Finds files matching specific glob patterns across the workspace.

**Parameters:** `pattern`, `dir_path`, `case_sensitive`, `respect_git_ignore`, `respect_gemini_ignore` | -| File System | [`grep_search`](../tools/file-system.md) | `Search` | Searches for a regular expression pattern within file contents. Legacy alias: `search_file_content`.

**Parameters:** `pattern`, `dir_path`, `include`, `exclude_pattern`, `names_only`, `max_matches_per_file`, `total_max_matches` | -| File System | [`list_directory`](../tools/file-system.md) | `Read` | Lists the names of files and subdirectories within a specified path.

**Parameters:** `dir_path`, `ignore`, `file_filtering_options` | -| File System | [`read_file`](../tools/file-system.md) | `Read` | Reads the content of a specific file. Supports text, images, audio, and PDF.

**Parameters:** `file_path`, `start_line`, `end_line` | -| File System | [`read_many_files`](../tools/file-system.md) | `Read` | Reads and concatenates content from multiple files. Often triggered by the `@` symbol in your prompt.

**Parameters:** `include`, `exclude`, `recursive`, `useDefaultExcludes`, `file_filtering_options` | -| File System | [`replace`](../tools/file-system.md) | `Edit` | Performs precise text replacement within a file. Requires manual confirmation.

**Parameters:** `file_path`, `instruction`, `old_string`, `new_string`, `allow_multiple` | -| File System | [`write_file`](../tools/file-system.md) | `Edit` | Creates or overwrites a file with new content. Requires manual confirmation.

**Parameters:** `file_path`, `content` | -| Interaction | [`ask_user`](../tools/ask-user.md) | `Communicate` | Requests clarification or missing information via an interactive dialog.

**Parameters:** `questions` | -| Interaction | [`write_todos`](../tools/todos.md) | `Other` | Maintains an internal list of subtasks. The model uses this to track its own progress and display it to you.

**Parameters:** `todos` | -| Memory | [`activate_skill`](../tools/activate-skill.md) | `Other` | Loads specialized procedural expertise for specific tasks from the `.gemini/skills` directory.

**Parameters:** `name` | -| Memory | [`get_internal_docs`](../tools/internal-docs.md) | `Think` | Accesses Gemini CLI's own documentation to provide more accurate answers about its capabilities.

**Parameters:** `path` | -| Memory | [`save_memory`](../tools/memory.md) | `Think` | Persists specific facts and project details to your `GEMINI.md` file to retain context.

**Parameters:** `fact` | -| Planning | [`enter_plan_mode`](../tools/planning.md) | `Plan` | Switches the CLI to a safe, read-only "Plan Mode" for researching complex changes.

**Parameters:** `reason` | -| Planning | [`exit_plan_mode`](../tools/planning.md) | `Plan` | Finalizes a plan, presents it for review, and requests approval to start implementation.

**Parameters:** `plan` | -| System | `complete_task` | `Other` | Finalizes a subagent's mission and returns the result to the parent agent. This tool is not available to the user.

**Parameters:** `result` | -| Web | [`google_web_search`](../tools/web-search.md) | `Search` | Performs a Google Search to find up-to-date information.

**Parameters:** `query` | -| Web | [`web_fetch`](../tools/web-fetch.md) | `Fetch` | Retrieves and processes content from specific URLs. **Warning:** This tool can access local and private network addresses (e.g., localhost), which may pose a security risk if used with untrusted prompts.

**Parameters:** `prompt` | +### Execution + +| Tool | Kind | Description | +| :--------------------------------------- | :-------- | :----------------------------------------------------------------------------------------------------------------------- | +| [`run_shell_command`](../tools/shell.md) | `Execute` | Executes arbitrary shell commands. Supports interactive sessions and background processes. Requires manual confirmation. | + +### File System + +| Tool | Kind | Description | +| :------------------------------------------- | :------- | :---------------------------------------------------------------------------------------------------- | +| [`glob`](../tools/file-system.md) | `Search` | Finds files matching specific glob patterns across the workspace. | +| [`grep_search`](../tools/file-system.md) | `Search` | Searches for a regular expression pattern within file contents. Legacy alias: `search_file_content`. | +| [`list_directory`](../tools/file-system.md) | `Read` | Lists the names of files and subdirectories within a specified path. | +| [`read_file`](../tools/file-system.md) | `Read` | Reads the content of a specific file. Supports text, images, audio, and PDF. | +| [`read_many_files`](../tools/file-system.md) | `Read` | Reads and concatenates content from multiple files. Often triggered by the `@` symbol in your prompt. | +| [`replace`](../tools/file-system.md) | `Edit` | Performs precise text replacement within a file. Requires manual confirmation. | +| [`write_file`](../tools/file-system.md) | `Edit` | Creates or overwrites a file with new content. Requires manual confirmation. | + +### Interaction + +| Tool | Kind | Description | +| :--------------------------------- | :------------ | :------------------------------------------------------------------------------------- | +| [`ask_user`](../tools/ask-user.md) | `Communicate` | Requests clarification or missing information via an interactive dialog. | +| [`write_todos`](../tools/todos.md) | `Other` | Maintains an internal list of subtasks. The model uses this to track its own progress. | + +### Memory + +| Tool | Kind | Description | +| :----------------------------------------------- | :------ | :----------------------------------------------------------------------------------- | +| [`activate_skill`](../tools/activate-skill.md) | `Other` | Loads specialized procedural expertise from the `.gemini/skills` directory. | +| [`get_internal_docs`](../tools/internal-docs.md) | `Think` | Accesses Gemini CLI's own documentation for accurate answers about its capabilities. | +| [`save_memory`](../tools/memory.md) | `Think` | Persists specific facts and project details to your `GEMINI.md` file. | + +### Planning + +| Tool | Kind | Description | +| :---------------------------------------- | :----- | :--------------------------------------------------------------------------------------- | +| [`enter_plan_mode`](../tools/planning.md) | `Plan` | Switches the CLI to a safe, read-only "Plan Mode" for researching complex changes. | +| [`exit_plan_mode`](../tools/planning.md) | `Plan` | Finalizes a plan, presents it for review, and requests approval to start implementation. | + +### System + +| Tool | Kind | Description | +| :-------------- | :------ | :----------------------------------------------------------------------------------------------------------------- | +| `complete_task` | `Other` | Finalizes a subagent's mission and returns the result to the parent agent. This tool is not available to the user. | + +### Web + +| Tool | Kind | Description | +| :-------------------------------------------- | :------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| [`google_web_search`](../tools/web-search.md) | `Search` | Performs a Google Search to find up-to-date information. | +| [`web_fetch`](../tools/web-fetch.md) | `Fetch` | Retrieves and processes content from specific URLs. **Warning:** This tool can access local and private network addresses (e.g., localhost), which may pose a security risk if used with untrusted prompts. | ## Under the hood diff --git a/packages/core/src/agents/browser/browserManager.test.ts b/packages/core/src/agents/browser/browserManager.test.ts index 303c07288d..c38457e4aa 100644 --- a/packages/core/src/agents/browser/browserManager.test.ts +++ b/packages/core/src/agents/browser/browserManager.test.ts @@ -272,6 +272,76 @@ describe('BrowserManager', () => { expect(result.isError).toBe(true); expect((result.content || [])[0]?.text).toContain('not permitted'); }); + + it('should block proxy URL with embedded disallowed domain in query params', async () => { + const restrictedConfig = makeFakeConfig({ + agents: { + browser: { + allowedDomains: ['*.google.com'], + }, + }, + }); + const manager = new BrowserManager(restrictedConfig); + const result = await manager.callTool('new_page', { + url: 'https://translate.google.com/translate?sl=en&tl=en&u=https://blocked.org/page', + }); + + expect(result.isError).toBe(true); + expect((result.content || [])[0]?.text).toContain( + 'an embedded URL targets a disallowed domain', + ); + }); + + it('should block proxy URL with embedded disallowed domain in URL fragment (hash)', async () => { + const restrictedConfig = makeFakeConfig({ + agents: { + browser: { + allowedDomains: ['*.google.com'], + }, + }, + }); + const manager = new BrowserManager(restrictedConfig); + const result = await manager.callTool('new_page', { + url: 'https://translate.google.com/#view=home&op=translate&sl=en&tl=zh-CN&u=https://blocked.org', + }); + + expect(result.isError).toBe(true); + expect((result.content || [])[0]?.text).toContain( + 'an embedded URL targets a disallowed domain', + ); + }); + + it('should allow proxy URL when embedded domain is also allowed', async () => { + const restrictedConfig = makeFakeConfig({ + agents: { + browser: { + allowedDomains: ['*.google.com', 'github.com'], + }, + }, + }); + const manager = new BrowserManager(restrictedConfig); + const result = await manager.callTool('new_page', { + url: 'https://translate.google.com/translate?u=https://github.com/repo', + }); + + expect(result.isError).toBe(false); + }); + + it('should allow navigation to allowed domain without proxy params', async () => { + const restrictedConfig = makeFakeConfig({ + agents: { + browser: { + allowedDomains: ['*.google.com'], + }, + }, + }); + const manager = new BrowserManager(restrictedConfig); + const result = await manager.callTool('new_page', { + url: 'https://translate.google.com/?sl=en&tl=zh', + }); + + expect(result.isError).toBe(false); + }); }); describe('MCP connection', () => { diff --git a/packages/core/src/agents/browser/browserManager.ts b/packages/core/src/agents/browser/browserManager.ts index cc059feea3..4eb9c2b19c 100644 --- a/packages/core/src/agents/browser/browserManager.ts +++ b/packages/core/src/agents/browser/browserManager.ts @@ -610,29 +610,65 @@ export class BrowserManager { try { const parsedUrl = new URL(url); - const urlHostname = parsedUrl.hostname.replace(/\.$/, ''); + const urlHostname = parsedUrl.hostname; - for (const domainPattern of allowedDomains) { - if (domainPattern.startsWith('*.')) { - const baseDomain = domainPattern.substring(2); + if (!this.isDomainAllowed(urlHostname, allowedDomains)) { + // If none matched, then deny + return `Tool '${toolName}' is not permitted for the requested URL/domain based on your current browser settings.`; + } + + // Check query parameters for embedded URLs that could bypass domain + // restrictions via proxy services (e.g. translate.google.com/translate?u=BLOCKED). + const paramsToCheck = [ + ...parsedUrl.searchParams.values(), + // Also check fragments which might contain query-like params + ...new URLSearchParams(parsedUrl.hash.replace(/^#/, '')).values(), + ]; + for (const paramValue of paramsToCheck) { + try { + const embeddedUrl = new URL(paramValue); if ( - urlHostname === baseDomain || - urlHostname.endsWith(`.${baseDomain}`) + embeddedUrl.protocol === 'http:' || + embeddedUrl.protocol === 'https:' ) { - return undefined; - } - } else { - if (urlHostname === domainPattern) { - return undefined; + const embeddedHostname = embeddedUrl.hostname.replace(/\.$/, ''); + if (!this.isDomainAllowed(embeddedHostname, allowedDomains)) { + return `Tool '${toolName}' is not permitted: an embedded URL targets a disallowed domain.`; + } } + } catch { + // Not a valid URL, skip. } } + + return undefined; } catch { return `Invalid URL: Malformed URL string.`; } + } + /** + * Checks whether a hostname matches any pattern in the allowed domains list. + */ + private isDomainAllowed(hostname: string, allowedDomains: string[]): boolean { + const normalized = hostname.replace(/\.$/, ''); + for (const domainPattern of allowedDomains) { + if (domainPattern.startsWith('*.')) { + const baseDomain = domainPattern.substring(2); + if ( + normalized === baseDomain || + normalized.endsWith(`.${baseDomain}`) + ) { + return true; + } + } else { + if (normalized === domainPattern) { + return true; + } + } + } // If none matched, then deny - return `Tool '${toolName}' is not permitted for the requested URL/domain based on your current browser settings.`; + return false; } /**