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;
}
/**