# Defensive Security Hardening: Prevent Script Execution in Toolbox UI
Rendering
> **Note:** This issue was identified during security research and
reviewed previously.
> While typical deployments operate within a trusted configuration
model, addressing this behavior was recommended as a defense-in-depth
improvement. This PR describes the implemented fix.
## Overview
This change improves the safety of the GenAI Toolbox UI by preventing
unintended JavaScript execution when rendering values derived from tool
configuration files.
Previously, certain fields from tool definitions were rendered directly
into HTML contexts without escaping. As a result, tool definitions
containing embedded HTML or script payloads could trigger JavaScript
execution when viewed in the dashboard. While this occurs within the
same trust boundary as the configuration owner, escaping these values by
default avoids unexpected execution and improves robustness.
## Changes Implemented
### 1. New Utility
- Added `sanitize.js` which exports a strict `escapeHtml()` function.
- Escapes dangerous characters: `&`, `<`, `>`, `"`, `'`, `/`, `` ` ``.
- Performs strict type checking, rendering `null` and `undefined` values
as empty strings.
### 2. Input Handling
- Updated `internal/server/static/js/toolDisplay.js` to wrap `tool.name`
and `tool.description` with `escapeHtml()` prior to rendering them into
the DOM.
### 3. Error Handling
- Updated `internal/server/static/js/loadTools.js` to sanitize error
messages that may reflect user-controlled or derived input before
rendering.
## Validation
- Verified behavior using tool definition files containing common script
execution vectors.
- Confirmed that embedded HTML and script payloads are rendered as
literal text.
- Verified that standard and existing tool definitions continue to
render correctly without functional regression.
## Notes
This change is a defense-in-depth hardening measure.
It does not modify the existing trust model or intended usage patterns,
but ensures safer default rendering behavior and avoids unintended
script execution in the UI.
## Attribution
**Contributor:** Mohammed Tanveer (threatpointer)
---------
Co-authored-by: threatpointer <mohammed.tanveer1@gmail.com>
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Co-authored-by: Yuan Teoh <45984206+Yuan325@users.noreply.github.com>
The `ParseParams` Tool interface is only passing the tool's parameter
into a generic `parameters.ParseParams()` helper. Instead of keeping it
as a tool interface, we add a `GetParameters()` method
(https://github.com/googleapis/genai-toolbox/pull/2374) to the tool
interface and call it directly from the API handlers. This way we keep
the parameter parsing logic independent from the tools.
This PR introduces a significant update to the Toolbox configuration
file format, which is one of the primary **breaking changes** required
for the implementation of the Advanced Control Plane.
# Summary of Changes
The configuration schema has been updated to enforce resource isolation
and facilitate atomic, incremental updates.
* Resource Isolation: Resource definitions are now separated into
individual blocks, using a distinct structure for each resource type
(Source, Tool, Toolset, etc.). This improves readability, management,
and auditing of configuration files.
* Field Name Modification: Internal field names have been modified to
align with declarative methodologies. Specifically, the configuration
now separates kind (general resource type, e.g., Source) from type
(specific implementation, e.g., Postgres).
# User Impact
Existing tools.yaml configuration files are now in an outdated format.
Users must eventually update their files to the new YAML format.
# Mitigation & Compatibility
Backward compatibility is maintained during this transition to ensure no
immediate user action is required for existing files.
* Immediate Backward Compatibility: The source code includes a
pre-processing layer that automatically detects outdated configuration
files (v1 format) and converts them to the new v2 format under the hood.
* [COMING SOON] Migration Support: The new toolbox migrate subcommand
will be introduced to allow users to automatically convert their old
configuration files to the latest format.
# Example
Example for config file v2:
```
kind: sources
name: my-pg-instance
type: cloud-sql-postgres
project: my-project
region: my-region
instance: my-instance
database: my_db
user: my_user
password: my_pass
---
kind: authServices
name: my-google-auth
type: google
clientId: testing-id
---
kind: tools
name: example_tool
type: postgres-sql
source: my-pg-instance
description: some description
statement: SELECT * FROM SQL_STATEMENT;
parameters:
- name: country
type: string
description: some description
---
kind: tools
name: example_tool_2
type: postgres-sql
source: my-pg-instance
description: returning the number one
statement: SELECT 1;
---
kind: toolsets
name: example_toolset
tools:
- example_tool
```
---------
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Co-authored-by: Averi Kitsch <akitsch@google.com>
Check for naming validation for Tool. This validation follows the MCP
SEP986 [naming
guidance](1b1eb60ec4/docs/specification/draft/server/tools.mdx (tool-names)).
Name will be checked before MCP initialization (where specs version is
confirmed). Hence, we will be implementing this across all versions and
endpoints.
This will be a breaking change for user that currently uses other
special character as name (other than `_`, `-`, `.`)
This PR introduces a new configuration field valueFromParam to the tool
definitions. This feature allows a parameter to automatically inherit
its value from another sibling parameter, mainly to streamline the
configuration of vector insertion tools.
Parameters utilizing valueFromParam are excluded from the Tool and MCP
manifests. This means the LLM does not see these parameters and is not
required to generate them. The value is resolved internally by the
Toolbox during execution.
## Description
Add a new `--user-agent-metadata` flag that allows user to append
additional user agent metadata. The flag takes in []string and will
concatenate it with `.`.
```
go run . --user-agent-metadata=foo
```
produces `0.25.0+dev.darwin.arm64+foo` user agent string
```
go run . --user-agent-metadata=foo,bar
```
produces `0.25.0+dev.darwin.arm64+foo+bar` user agent string
## PR Checklist
> Thank you for opening a Pull Request! Before submitting your PR, there
are a
> few things you can do to make sure it goes smoothly:
- [x] Make sure you reviewed
[CONTRIBUTING.md](https://github.com/googleapis/genai-toolbox/blob/main/CONTRIBUTING.md)
- [x] Make sure to open an issue as a
[bug/issue](https://github.com/googleapis/genai-toolbox/issues/new/choose)
before writing your code! That way we can discuss the change, evaluate
designs, and agree on the general idea
- [x] Ensure the tests and linter pass
- [x] Code coverage does not decrease (if any source code was changed)
- [x] Appropriate docs were updated (if necessary)
- [x] Make sure to add `!` if this involve a breaking change
🛠️ Fixes #<issue_number_goes_here>
Update error code from 400 to 403 according to MCP
[updates](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/1439)
for invalid origin header.
Also updated hostCheck to only check host, not port.
To test, run Toolbox with the following (also work with port number e.g.
`--allowed-host=127.0.0.1:5000`):
```
go run . --allowed-hosts=127.0.0.1
```
Test with the following:
```
// curl successfully
curl -H "Host: 127.0.0.1:5000" http://127.0.0.1:5000
// curl successfully
curl -H "Host: 127.0.0.1:3000" http://127.0.0.1:5000
// will show Invalid Host Header error
curl -H "Host: attacker:5000" http://127.0.0.1:5000
```
## Description
Previously added `allowed-origins` (for CORs) is not sufficient for
preventing DNS rebinding attacks. We'll have to check host headers.
To test, run Toolbox with the following:
```
go run . --allowed-hosts=127.0.0.1:5000
```
Test with the following:
```
// curl successfully
curl -H "Host: 127.0.0.1:5000" http://127.0.0.1:5000
// will show Invalid Host Header error
curl -H "Host: attacker:5000" http://127.0.0.1:5000
```
## PR Checklist
> Thank you for opening a Pull Request! Before submitting your PR, there
are a
> few things you can do to make sure it goes smoothly:
- [ ] Make sure you reviewed
[CONTRIBUTING.md](https://github.com/googleapis/genai-toolbox/blob/main/CONTRIBUTING.md)
- [ ] Make sure to open an issue as a
[bug/issue](https://github.com/googleapis/genai-toolbox/issues/new/choose)
before writing your code! That way we can discuss the change, evaluate
designs, and agree on the general idea
- [ ] Ensure the tests and linter pass
- [ ] Code coverage does not decrease (if any source code was changed)
- [ ] Appropriate docs were updated (if necessary)
- [ ] Make sure to add `!` if this involve a breaking change
🛠️ Fixes #<issue_number_goes_here>
Add parameter `embeddedBy` field to support vector embedding & semantic
search.
Major change in `internal/util/parameters/parameters.go`
This PR only adds vector formatter for the postgressql tool. Other tools
requiring vector formatting may not work with embeddedBy.
Second part of the Semantic Search support. First part:
https://github.com/googleapis/genai-toolbox/pull/2121
This PR update the linking mechanism between Source and Tool.
Tools are directly linked to their Source, either by pointing to the
Source's functions or by assigning values from the source during Tool's
initialization. However, the existing approach means that any
modification to the Source after Tool's initialization might not be
reflected. To address this limitation, each tool should only store a
name reference to the Source, rather than direct link or assigned
values.
Tools will provide interface for `compatibleSource`. This will be used
to determine if a Source is compatible with the Tool.
```
type compatibleSource interface{
Client() http.Client
ProjectID() string
}
```
During `Invoke()`, the tool will run the following operations:
* retrieve Source from the `resourceManager` with source's named defined
in Tool's config
* validate Source via `compatibleSource interface{}`
* run the remaining `Invoke()` function. Fields that are needed is
retrieved directly from the source.
With this update, resource manager is also added as input to other
Tool's function that require access to source (e.g.
`RequiresClientAuthorization()`).
## Description
Remove warning about unused parameter in vscode
## PR Checklist
> Thank you for opening a Pull Request! Before submitting your PR, there
are a
> few things you can do to make sure it goes smoothly:
- [x] Make sure you reviewed
[CONTRIBUTING.md](https://github.com/googleapis/genai-toolbox/blob/main/CONTRIBUTING.md)
- [] Make sure to open an issue as a
[bug/issue](https://github.com/googleapis/genai-toolbox/issues/new/choose)
before writing your code! That way we can discuss the change, evaluate
designs, and agree on the general idea
- [x] Ensure the tests and linter pass
- [x] Code coverage does not decrease (if any source code was changed)
- [x] Appropriate docs were updated (if necessary)
- [x] Make sure to add `!` if this involve a breaking change
## Description
Tool `invoke()` and `RequiresClientAuthorization()` takes a new input
argument -- Resource Manager. Resource manager will be used to retrieve
Source in the next step.
In order to achieve the goal, this PR implements the follows:
* move resource manager from the server package to a new package to
prevent import cycles (between server and mcp)
* added a new interface in `tools.go` to prevent import cycle (between
resources and tools package)
* add new input argument in all tools
## PR Checklist
> Thank you for opening a Pull Request! Before submitting your PR, there
are a
> few things you can do to make sure it goes smoothly:
- [x] Make sure you reviewed
[CONTRIBUTING.md](https://github.com/googleapis/genai-toolbox/blob/main/CONTRIBUTING.md)
- [x] Make sure to open an issue as a
[bug/issue](https://github.com/googleapis/genai-toolbox/issues/new/choose)
before writing your code! That way we can discuss the change, evaluate
designs, and agree on the general idea
- [x] Ensure the tests and linter pass
- [x] Code coverage does not decrease (if any source code was changed)
- [x] Appropriate docs were updated (if necessary)
- [x] Make sure to add `!` if this involve a breaking change
Support `allowed-origins` flag to allow secure deployment of Toolbox.
Current Toolbox is **insecure by default**, which allows all origin
(`*`). This PR also updated docs to notify user of the new
`allowed-origins` flag in the Cloud Run, kubernetes, and docker
deployment docs.
This PR was tested manually by mocking a browser access:
1. Created a HTML file with Javascript fetch named
`malicious-client.html`:
```
<!DOCTYPE html>
<html>
<head>
<title>Malicious CORS Test</title>
</head>
<body>
<h1>Attempting to access API at http://127.0.0.1:5000/mcp</h1>
<p>Check the **Chrome Developer Console** (F12 -> Console tab) for the result.</p>
<script>
fetch('http://127.0.0.1:5000/mcp', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
// The browser automatically adds the 'Origin' header based on where this HTML is served from (http://localhost:8000)
},
body: JSON.stringify({
"jsonrpc": "2.0",
"id": 1,
"method": "tools/list"
})
})
.then(response => {
console.log('Success (but check console for CORS enforcement details):', response);
return response.json();
})
.then(data => console.log('Data received (only if CORS passes):', data))
.catch(error => console.error('Fetch Error:', error));
</script>
</body>
</html>
```
2. Run `python3 -m http.server 8000`
3. Open `http://localhost:8000/malicious-client.html` in browser.
4. Tried without `--allowed-origins` flag -- success.
Tried with `--allowed-origins=http://localhost:8000` -- success.
Tried with `--allowed-origins=http://foo.com` -- unsuccessful.
---------
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Co-authored-by: Averi Kitsch <akitsch@google.com>
## Description
The MCP spec supports tool annotations like the below structure in the
2025-06-18 version of the spec.
https://modelcontextprotocol.io/specification/2025-06-18/schema#toolannotations
```
{
destructiveHint?: boolean;
idempotentHint?: boolean;
openWorldHint?: boolean;
readOnlyHint?: boolean;
}
```
Added a ToolAnnotations structure, an Annotations member to the
McpManifest structure, and a nil initializer for the Annotations member
to all calls to GetMcpManifest.
The ToolAnnotations structure and the member annotations are all defined
as pointers so that they are omited when not set. There are times when
the zero value is meaningful so this was the only way to make sure that
we distinguish between not setting the annotation and setting it with a
zero value.
## PR Checklist
> Thank you for opening a Pull Request! Before submitting your PR, there
are a
> few things you can do to make sure it goes smoothly:
- [x] Make sure you reviewed
[CONTRIBUTING.md](https://github.com/googleapis/genai-toolbox/blob/main/CONTRIBUTING.md)
- [x] Make sure to open an issue as a
[bug/issue](https://github.com/googleapis/genai-toolbox/issues/new/choose)
before writing your code! That way we can discuss the change, evaluate
designs, and agree on the general idea
- [x] Ensure the tests and linter pass
- [x] Code coverage does not decrease (if any source code was changed)
- [x] Appropriate docs were updated (if necessary)
- [x] Make sure to add `!` if this involve a breaking change
🛠️Fixes#927
---------
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
## Description
This commit allows a tool to pull an alternate authorization
token from the header of the http request.
This is initially being built for the Looker integration. Looker
uses its own OAuth token. When deploying MCP Toolbox to Cloud
Run, the default token in the "Authorization" header is for
authentication with Cloud Run. An alternate token can be put into
another header by a client such as ADK or any other client that
can programatically set http headers. This token will be used
to authenticate with Looker.
If needed, other sources can use this by setting the header name
in the source config, passing it into the tool config, and returning
the header name in the Tool GetAuthTokenHeaderName() function.
## PR Checklist
> Thank you for opening a Pull Request! Before submitting your PR, there
are a
> few things you can do to make sure it goes smoothly:
- [x] Make sure you reviewed
[CONTRIBUTING.md](https://github.com/googleapis/genai-toolbox/blob/main/CONTRIBUTING.md)
- [x] Make sure to open an issue as a
[bug/issue](https://github.com/googleapis/genai-toolbox/issues/new/choose)
before writing your code! That way we can discuss the change, evaluate
designs, and agree on the general idea
- [x] Ensure the tests and linter pass
- [x] Code coverage does not decrease (if any source code was changed)
- [x] Appropriate docs were updated (if necessary)
- [x] Make sure to add `!` if this involve a breaking change
🛠️Fixes#1540
To keep a persistent backend storage for configuration, we will have to
keep a single source of truth. This involves supporting bi-directional
conversion for Prompt and Promptsets.
This PR make the following changes:
* Separate Prompt from PromptConfig
* Embed Config in Resources (for both prompt and promptset)
* Add `ToConfig()` to extract Config from Resources (for both prompt and
promptset)
To keep a persistent backend storage for configuration, we will have to
keep a single source of truth. This involves supporting bi-directional
conversion between ToolsetConfig and Toolset.
This PR make the following changes:
* Embed ToolsetConfig in Toolset
* Add `ToConfig()` to extract ToolsetConfig from Toolset.
To keep a persistent backend storage for configuration, we will have to
keep a single source of truth. This involves supporting bi-directional
conversion between Config and Tool.
This PR make the following changes:
* Embed Config in Tool
* Add `ToConfig()` to extract Config from Tool.
Jules PR
---
*PR created automatically by Jules for task
[11947649751737965380](https://jules.google.com/task/11947649751737965380)*
---------
Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
Co-authored-by: Yuan Teoh <yuanteoh@google.com>
To keep a persistent backend storage for configuration, we will have to
keep a single source of truth. This involves supporting bi-directional
conversion between Config and Source.
This PR make the following changes:
* Embed Config in Source
* Add `ToConfig()` to extract Config from Source.
To facilitate the transition of moving invocation implementation to
Source, we will have to move parameter to `internal/util`. This approach
is crucial because certain parameters may not be fully resolvable
pre-implementation. Since both `internal/sources` and `internal/tools`
will need access to `parameters`, it will be more relevant to move
parameters implementation to utils.
Add `_meta` for `tools/list` method in MCP Toolbox.
If there are authorized invocation, the following will be return in
`_meta`:
```
{
"name":"my-tool-name",
"description":"my tool description",
"inputSchema":{
"type":"object",
"properties":{
"user_id":{"type":"string","description":"user's name from google login"}
},
"required":["user_id"]
},
"_meta":{
"toolbox/authParam":{"user_id":["my_auth"]}
}
}
```
If there are authenticated parameter, the following will be return in
`_meta`:
```
{
"name":"my-tool-name",
"description":"my tool description",
"inputSchema":{
"type":"object",
"properties":{
"sql":{"type":"string","description":"The sql to execute."}
},
"required":["sql"]
},
"_meta":{
"toolbox/authInvoke":["my_auth"]
}
}
```
If there are no authorized invocation or authenticated prameter, the
`_meta` field will be omitted.
With this feature, the following were updated in the source code:
* In each `func(p CommonParameter) McpManifest()`, we will return a
`[]string` for the list of authenticated parameters. This is similar to
how Manifest() return the list of authNames in non-MCP Toolbox's
manifest.
* The `func(ps Parameters) McpManifest()` will return a
`map[string][]string` that with key as param's name, and value as the
param's auth.
* Added a new function `GetMcpManifest()` in `tools.go`. This function
will consctruct the McpManifest, and add the `Metadata` field.
* Associated tests were added or updated.
Existing `/mcp` endpoint of Toolbox does not support auth (authorized
invocation and authenticated parameters). This PR add support for
Toolbox auth to the `/mcp` endpoint.
Added integration test for MCP with auth.
Note that Toolbox auth is **NOT** supported in stdio transport protocol,
invocations of tools with auth will result in error.
For Toolbox protocol:
Before - return 400 error for all tool invocation errors.
After - Propagate auth-related errors (401 & 403) to the client if using
client credentials. If using ADC, raise 500 error instead.
For MCP protocol:
Before - return 200 with error message in the response body.
After - Propagate auth-related errors (401 & 403) to the client if using
client credentials. If using ADC, raise 500 error instead.
Add `Sign in with Google` button within Toolbox UI's `Edit Header` modal
that automatically retrieves a valid ID token for users based on an
input clientID.
This should make it significantly easier/faster for users to format
request headers properly and test tools that use auth.
Introduce Toolbox UI, which can be launched with the `--ui` flag.
This initial version of Toolbox UI allows users to test Toolbox by
inspecting tools/toolsets, modifying parameters, managing headers, and
executing API calls.
Toolbox MCP endpoint to accept request of multiple content type
according to the json schema
(https://www.jsonrpc.org/historical/json-rpc-over-http.html#http-header)
Toolbox endpoints only accept `Content-Type: application/json`. Update
to accept `Content-Type: application/json-rpc` and
`Content-Type:application/jsonrequest` as well.
Fixes#1004
Update `tool.Invoke()` to return type `any` instead of `[]any`.
Toolbox return a map with the `results` key, and the SDK reads the
string from the key. So this won't break existing SDK implementation.
Fixes#870
This PR add supports to MCP version 2025-06-18 defined
[here](https://modelcontextprotocol.io/specification/2025-06-18).
The main updates includes:
* Retrieving protocol version from header via `MCP-Protocol-Version`.
* Throwing `400 Bad Request` when an invalid version is received.
Allow Toolbox server to automatically update when users modify their
tool configuration file(s), instead of requiring a restart.
This feature is automatically enabled, but can be turned off with the
flag `--disable-reload`.