← MCP server and skill grades

genai-toolbox

googleapis/genai-toolbox

D
Type
MCP server
Scanned
Oct 29, 2026
Version
494092a9bd98
Files read
2015

MCP Toolbox for Databases is a server by Google that connects AI agents to enterprise databases, cloud storage, and analytics services using prebuilt or custom tool configurations. The biggest risks are tools that allow unrestricted reading or writing of local files, database tools vulnerable to query injection and read-only bypasses, and permissive default web settings that let websites interact with a locally running server.

What it can reach

Network
Listens as a local HTTP or STDIO server, connects to configured databases and cloud APIs (including Google Cloud services, PostgreSQL, MySQL, SQLite, MongoDB, Neo4j, Snowflake, and Looker), contacts OAuth and telemetry endpoints, and checks GitHub releases on startup.
Files
Reads YAML configuration files, TLS certificates, local SQLite databases, and configured file templates, and can read or write local files through Cloud Storage upload and download tools.
Shell commands
Executes the external dataform CLI binary when the local Dataform compilation tool is used, and includes maintainer skill files that instruct AI agents to run git, gh, hugo, and go test commands.
Secrets
Reads database passwords, API keys, Google Application Default Credentials, and OAuth or JWT tokens from environment variables, configuration files, and HTTP headers.

Findings

5 high12 medium2 low
  1. HighUnrestricted local file read and write in Cloud Storage upload and download tools

    internal/sources/cloudstorage/cloudstorage.go:121

    The cloud-storage-upload-object and cloud-storage-download-object tools accept arbitrary absolute local filesystem paths via the source and destination tool arguments. Unless the user explicitly configures allowedLocalRoots on the source (or destination_dir on the download tool), validateLocalPath permits any absolute path on the machine. Through prompt injection, an attacker can upload sensitive local files (such as ~/.ssh/id_rsa or cloud credentials) to a bucket to read them back, or write/overwrite arbitrary files on the user's filesystem.

    Fix: Require allowedLocalRoots (or destination_dir / source_dir) to be configured before enabling local filesystem upload and download tools, or reject local file operations when no root directory allowlist is set.

  2. HighURI template prefix bypass in file resource templates when allowedPaths is omitted

    internal/resources/file/file.go:627

    When a URI is matched against a file resourceTemplate (such as file:///var/log/{path}), MatchResourceTemplateURI extracts {path} using (.*). If a caller requests a URI with a leading slash in the {path} segment (for example, file:///var/log//etc/config.yaml), pathStr is /etc/config.yaml. Because FileTemplate.Read only interpolates pathStr into r.URITemplate when !filepath.IsAbs(pathStr), it discards the template's base path prefix /var/log/ and resolves /etc/config.yaml from the filesystem root. If allowedPaths was not explicitly set, any non-hidden file matching the allowed extensions (.yaml, .json, .py, .sql, .txt, .csv, etc.) on the machine can be read.

    Fix: Always resolve {path} relative to the path prefix defined in r.URITemplate (or strip leading slashes before interpolating into r.URITemplate), and require pathStr to remain within the template's base directory even when allowedPaths is not specified.

  3. HighRead-only Cypher classifier bypass via comments inside string literals and quoted procedure names

    internal/tools/neo4j/neo4jexecutecypher/classifier/classifier.go:199

    When neo4j-execute-cypher is configured with readOnly: true, it relies on QueryClassifier.Classify to reject write queries. However, Classify calls normalizeQuery (which strips // and /* ... */ comments) before stripping string literals. An attacker can place /* and */ inside two string literals around a destructive clause (for example, MATCH (n) WHERE n.name <> '/*' DETACH DELETE n RETURN '*/'); the classifier strips the DETACH DELETE as a comment and classifies the query as READ, while Neo4j executes the deletion. Additionally, procedureCallPattern only matches unquoted procedure identifiers (\bCALL\s+([a-zA-Z0-9_.]+)), so backtick-quoted procedure calls such as `CALL apoc.create.node(...)` bypass write-procedure detection.

    Fix: Tokenize string literals, backtick-quoted identifiers, and comments in a single lexical pass rather than running regex comment removal before string-literal removal, and enforce read-only access using Neo4j's native neo4j.AccessModeRead session configuration.

  4. HighSQL injection in prebuilt SQLite list_tables tool via unescaped template parameters

    internal/prebuiltconfigs/tools/sqlite.yaml:101

    The prebuilt list_tables tool for SQLite declares output_format and table_names as templateParameters instead of bound SQL parameters, and interpolates them directly inside single quotes ('{{.output_format}}' and '{{.table_names}}'). Because modernc.org/sqlite supports multi-statement execution, an attacker who controls tool arguments via prompt injection can break out of the single-quoted literal in list_tables and execute arbitrary SQL statements, including DROP TABLE or ATTACH DATABASE to write files to disk.

    Fix: Replace templateParameters with standard bound parameters (?) in internal/prebuiltconfigs/tools/sqlite.yaml, matching the MySQL, Postgres, and MSSQL list_tables implementations.

  5. HighSQL injection in prebuilt MindsDB parameterized_sql tool via template parameters

    internal/prebuiltconfigs/tools/mindsdb.yaml:39

    The prebuilt parameterized_sql tool in mindsdb.yaml interpolates {{.table_name}} and {{.condition_column}} as unescaped string templateParameters directly into the SQL statement. An attacker can supply arbitrary SQL expressions or UNION SELECT payloads in table_name or condition_column to read or modify unauthorized data.

    Fix: Validate identifier template parameters or apply identifier escaping on table_name and condition_column.

  6. MediumPath traversal across stores and datasets in Cloud Healthcare tools

    internal/sources/cloudhealthcare/cloud_healthcare.go:486

    In GetFHIRResource, FHIRPatientEverything, RetrieveRenderedDICOMInstance, and SearchDICOM, tool arguments such as resourceType, resourceID, patientID, and DICOM UIDs are concatenated directly into the resource name string without URL-escaping or rejecting .. and / characters. Because the Google Cloud Healthcare client expands {+name} without encoding slashes, a prompt-injected argument containing ../ segments can traverse out of the intended resource path and bypass allowedFhirStores or allowedDicomStores restrictions.

    Fix: Validate that resource IDs, types, store IDs, and DICOM UIDs do not contain / or .. segments and apply url.PathEscape to each individual path segment.

  7. MediumUnrestricted directory path and argument injection in dataform-compile-local tool

    internal/tools/dataform/dataformcompilelocal/dataformcompilelocal.go:103

    The dataform-compile-local tool passes the agent-supplied project_dir argument directly to exec.CommandContext(ctx, "dataform", "compile", projectDir, "--json"). There is no check preventing project_dir from starting with - (allowing CLI flag injection) or confining project_dir to an allowed workspace directory. Furthermore, because dataform compile executes JavaScript files within the target project directory, pointing it at an untrusted directory can execute arbitrary code.

    Fix: Ensure project_dir does not start with - (or pass "--" before projectDir) and restrict project_dir to a configured base directory.

  8. MediumRDF injection via unescaped parameter substitution in Dgraph mutations

    internal/sources/dgraph/dgraph.go:420

    When executing a Dgraph mutation (isQuery: false), embedParamsIntoMutation substitutes user parameters into the mutation payload using strings.ReplaceAll(mutation, key, fmt.Sprintf("%v", value)) without escaping double quotes, backslashes, or newlines. An attacker controlling a tool parameter can break out of the quoted value and inject arbitrary RDF triples into the Dgraph database.

    Fix: Properly escape string values (e.g., using strconv.Quote or JSON/RDF string escaping) before embedding them into the mutation payload.

  9. MediumPermissive default CORS and Host allowlists in HTTP server mode

    cmd/internal/flags.go:76

    When running as an HTTP server (the default mode on 127.0.0.1:5000), both --allowed-origins and --allowed-hosts default to * while CORS AllowCredentials is set to true. If authentication is not configured, any website visited by the user in a local browser can send cross-origin requests or use DNS rebinding to invoke MCP tools on localhost:5000.

    Fix: Default --allowed-hosts to 127.0.0.1 / localhost and restrict --allowed-origins by default instead of using wildcard *.

  10. MediumPrebuilt PostgreSQL and AlloyDB get_query_plan tools interpolate unescaped SQL via templateParameters

    internal/prebuiltconfigs/tools/alloydb-postgres.yaml:188

    In alloydb-postgres.yaml (as well as alloydb-omni.yaml, cloud-sql-postgres.yaml, and postgres.yaml), the get_query_plan tool interpolates {{.query}} directly into EXPLAIN (FORMAT JSON) {{.query}}; using templateParameters, and is included in read-only monitoring groups (monitor and performance). In alloydb-postgres.yaml, the description explicitly states 'Use in production safely', even though {{.query}} is raw text substitution that allows multi-statement execution if simple_protocol is used or arbitrary EXPLAIN ANALYZE options if injected before the query.

    Fix: Implement a dedicated postgres-get-query-plan tool that validates single-statement input and runs inside a read-only transaction, and remove the claim that raw template interpolation is safe for production.

  11. MediumSecond-order Cypher injection in Neo4j schema extraction when without APOC

    internal/tools/neo4j/neo4jschema/neo4jschema.go:368

    In GetSchemaWithoutAPOC, node labels and relationship types retrieved from the database are interpolated directly into Cypher queries using fmt.Sprintf without backtick quoting or escaping (MATCH (n:%s) and MATCH ()-[r:%s]->()). Unlike falkordbschema.go, which escapes backticks and wraps identifiers in backticks, neo4jschema.go embeds the raw string in a write-mode session, allowing labels containing spaces or Cypher syntax to break or inject Cypher commands.

    Fix: Escape backticks by doubling them (strings.ReplaceAll(label, "", "`")) and wrap the identifier in backticks (`MATCH (n:%s) and MATCH ()-[r:%s]->()`).

  12. MediumMissing operator precedence parentheses in Snowflake prebuilt list_tables query leaks cross-schema metadata

    internal/prebuiltconfigs/tools/snowflake.yaml:55

    In snowflake.yaml, the list_tables query filters INFORMATION_SCHEMA.TABLES with WHERE t.TABLE_TYPE = 'BASE TABLE' AND t.TABLE_SCHEMA NOT IN ('INFORMATION_SCHEMA') AND t.TABLE_SCHEMA = '${SNOWFLAKE_SCHEMA}' AND is_all_tables OR LOWER(T.TABLE_NAME) IN (SELECT table_name FROM filtered_table_names). Because AND binds tighter than OR in SQL, passing any table_names argument (is_all_tables = false) bypasses the t.TABLE_SCHEMA = '${SNOWFLAKE_SCHEMA}' restriction and returns metadata for matching tables across all schemas in the database.

    Fix: Wrap the OR condition in parentheses: AND (is_all_tables OR LOWER(T.TABLE_NAME) IN (SELECT table_name FROM filtered_table_names)).

  13. MediumPermissive default CORS origins and allowed hosts expose HTTP server to browser-based attacks

    docs/en/reference/cli.md:40

    By default, when Toolbox runs in HTTP mode (127.0.0.1:5000), both --allowed-origins (also declared with a default of * in server.json) and --allowed-hosts default to *. Unless the user explicitly overrides these flags, any malicious website visited in a local browser can make cross-origin requests or perform DNS rebinding attacks against the local MCP server to list and invoke database tools.

    Fix: Change the default values of --allowed-hosts to localhost,127.0.0.1 and --allowed-origins to an empty list (disabling cross-origin browser access unless explicitly configured).

  14. MediumPR review skill instructs the agent to fetch and execute untrusted pull request code locally

    skills/maintainer/review-prs/SKILL.md:209

    The review-prs skill instructs the coding agent to fetch the pull request branch (git fetch origin pull/<n>/head:pr<n>), check it out into a worktree, and run a scratch probe_test.go inside the package under review. Because pull requests can be submitted by arbitrary strangers, running go test on the PR's package executes untrusted code (such as init() functions or modified package functions) directly on the reviewer's machine with their local credentials.

    Fix: Instruct the agent never to execute code from an untrusted pull request branch on the host machine unless inside an isolated, credential-free sandbox or container, or require explicit user confirmation before running any code from the PR.

  15. MediumPermissive wildcard CORS origin default in server manifest

    server.json:147

    The MCP server manifest sets the default value of the --allowed-origins runtime argument to '*'. When the server runs in HTTP mode on localhost without authentication, any website open in the user's browser can send cross-origin requests to the local MCP endpoint and invoke configured database tools.

    Fix: Change the default for --allowed-origins to restrict cross-origin browser requests by default, or require explicit origin allowlisting when running in HTTP mode.

  16. MediumWildcard CORS origin default in MCP server manifest

    server.json:147

    The MCP server manifest defines the default value for --allowed-origins as *. When the server runs in HTTP mode on localhost, permitting all origins allows any website open in the user's browser to send cross-origin requests to the local MCP server and invoke database tools.

    Fix: Change the default value of --allowed-origins to restrict cross-origin requests by default (for example, to an empty list or localhost-only origins) and require explicit opt-in for wildcard origins.

  17. MediumPR review skill instructs agent to execute untrusted pull request code locally

    skills/maintainer/review-prs/SKILL.md:210

    The review-prs skill instructs the coding agent to fetch the pull request branch into a local worktree and run a probe_test.go test inside the package under review rather than just reading the code. Running go test on an unreviewed pull request executes any init() functions, modified package code, or build directives submitted by an untrusted contributor with the reviewer's local privileges and credentials.

    Fix: Instruct the agent to only checkout and execute code from a pull request after verifying that the diff does not introduce untrusted code execution, or run untrusted PR code only inside an isolated sandbox/CI environment.

  18. LowMissing collection path validation after template substitution in firestore-query

    internal/tools/firestore/firestorequery/firestorequery.go:177

    Unlike firestore-query-collection and the other Firestore tools, firestore-query populates collectionPath from a template using parameters.PopulateTemplate but does not validate the resulting path with fsUtil.ValidateCollectionPath. If a tool configuration parameterizes part of collectionPath, a caller can inject / or .. segments to traverse into unintended collections.

    Fix: Call fsUtil.ValidateCollectionPath(collectionPath) after parameters.PopulateTemplate and return an AgentError if validation fails.

  19. LowUnbounded blocking sleep in wait tool ignores context cancellation and configured timeout

    internal/tools/utility/wait/wait.go:95

    The wait tool parses the caller-supplied duration parameter and calls time.Sleep(totalDuration) directly without enforcing an upper bound, checking t.Cfg.Timeout, or selecting on ctx.Done(). An attacker via prompt injection can pass a very large duration (such as 87600h) to tie up server goroutines even after the client disconnects or the request context is canceled.

    Fix: Cap totalDuration against t.Cfg.Timeout and use select { case <-time.After(totalDuration): case <-ctx.Done(): return nil, ... } instead of time.Sleep.