← MCP server and skill grades

google_workspace_mcp

taylorwilsdon/google_workspace_mcp

B
Type
MCP server
Scanned
Oct 29, 2026
Version
c0bff66e6842
Files read
350

This tool gives AI assistants read and write access to twelve Google Workspace services, including Gmail, Drive, Calendar, Docs, Sheets, and Apps Script. The biggest risk is that hidden instructions inside untrusted emails or documents could trick the AI into reading, changing, deleting, or sending data across your connected Google accounts, or running code via Apps Script.

What it can reach

Network
Connects to Google Workspace APIs (plus optional Google Cloud Storage, Valkey, JWKS, and OpenTelemetry services), downloads files from external URLs, and runs local or remote web listeners for authentication and file transfers.
Files
Reads and writes login tokens, logs, and temporary attachments in hidden home folders (~/.google_workspace_mcp, ~/.workspace-mcp, and ~/.fastmcp), and reads local files from permitted directories for uploads.
Secrets
Reads Google OAuth client secrets, access tokens, service account keys, Custom Search API keys, and optional Valkey credentials from environment variables and local files.
Browser
Opens the system web browser automatically during Google authentication flows.

Findings

2 medium5 low
  1. MediumSensitive path filter in validate_file_path misses server credential stores and .docker/config.json

    core/utils.py:365

    validate_file_path() is designed to block access to credential files even when ALLOWED_FILE_DIRS is broadened by the user. However, it does not block the MCP server's own credential and key directories (~/.google_workspace_mcp/credentials where plaintext OAuth refresh tokens are stored, ~/.workspace-mcp where .cli-encryption-key is stored, and ~/.fastmcp/oauth-proxy). Additionally, '.docker/config.json' is listed in sensitive_names and compared against resolved.name.lower() (the basename 'config.json'), which can never contain a slash and therefore never matches. If ALLOWED_FILE_DIRS includes the home directory, a prompt injection can attach or upload these credential files via Gmail or Drive tools.

    Fix: Add ~/.google_workspace_mcp, ~/.workspace-mcp, ~/.fastmcp, and ~/.docker to sensitive_home_dirs (and WORKSPACE_MCP_CREDENTIALS_DIR if set), and check multi-segment relative paths against resolved parts rather than resolved.name.

  2. MediumAgent-controlled user_google_email parameter allows cross-account access and unprompted browser launches in legacy OAuth 2.0 mode

    auth/service_decorator.py:561

    When OAuth 2.1 and trusted-gateway modes are not enabled (the default in stdio mode), user_google_email is taken from the tool arguments provided by the AI agent, and USER_GOOGLE_EMAIL only acts as a fallback when the argument is omitted. If multiple accounts have cached credentials in ~/.google_workspace_mcp/credentials, a prompt-injected instruction can pass another account's email address to read or modify its Workspace data. Passing an unauthenticated email address causes get_authenticated_google_service() to start the local callback server and open the user's browser to a Google OAuth consent screen.

    Fix: When USER_GOOGLE_EMAIL is configured in legacy OAuth 2.0 or single-user mode, enforce that any caller-supplied user_google_email matches USER_GOOGLE_EMAIL (as service-account mode already does), and require explicit user confirmation or start_google_auth before opening a browser for a new account.

  3. LowOAuth callback handler unconditionally enables OAUTHLIB_INSECURE_TRANSPORT process-wide

    auth/google_auth.py:595

    In handle_auth_callback(), if OAUTHLIB_INSECURE_TRANSPORT is not set in the environment, the code unconditionally sets os.environ['OAUTHLIB_INSECURE_TRANSPORT'] = '1' without checking whether redirect_uri is a loopback address. Additionally, manifest.json and fastmcp.json enable OAUTHLIB_INSECURE_TRANSPORT by default. This disables oauthlib's HTTPS enforcement for all OAuth flows in the process.

    Fix: Only set OAUTHLIB_INSECURE_TRANSPORT when the parsed redirect_uri hostname is a loopback address (localhost, 127.0.0.1, or ::1), and default OAUTHLIB_INSECURE_TRANSPORT to false in deployment manifests.

  4. LowUnsanitized folder_id interpolated into Drive query in list_docs_in_folder

    gdocs/docs_tools.py:507

    Unlike list_drive_items() (which resolves folder_id via the Drive API) and search_docs() (which escapes query values), list_docs_in_folder() interpolates folder_id directly into the Drive API q filter string. A crafted folder_id containing single quotes and query operators (such as "root' or name contains '") can alter the query logic to list arbitrary files outside the target folder and bypass the Google Docs MIME type filter.

    Fix: Validate folder_id with resolve_folder_id() or escape it with escape_drive_query_value() before interpolating it into the Drive query string.

  5. LowInsecure OAuth transport enabled by default in plugin and manifest configs

    .claude-plugin/plugin.json:70

    Both .claude-plugin/plugin.json and manifest.json enable OAUTHLIB_INSECURE_TRANSPORT by default. While needed for http://localhost callbacks during local setup, leaving this flag enabled globally disables oauthlib's HTTPS enforcement and allows OAuth flows to proceed over unencrypted HTTP if a non-HTTPS remote URL is configured.

    Fix: Only enable OAUTHLIB_INSECURE_TRANSPORT when the redirect URI is explicitly a loopback address (localhost or 127.0.0.1), or default it to false in production manifests.

  6. LowCredential storage directory created with world-readable permissions in Docker image

    Dockerfile:28

    The Dockerfile creates /app/store_creds with mode 755 (rwxr-xr-x), allowing any user or process inside the container to list and read the directory contents if file permissions inside are not separately restricted.

    Fix: Use chmod 700 /app/store_creds so only the app user can access the credential directory.

  7. LowUnsafe pickle deserialization in manual test script

    tests/gappsscript/manual_test.py:67

    The manual test script loads cached OAuth tokens using pickle.load(), which can execute arbitrary code if the token file is replaced or modified by an attacker. This script is only used for manual developer testing and is not run by the installed MCP server.

    Fix: Serialize and load OAuth credentials using Google Auth's JSON format (Credentials.to_json() and Credentials.from_authorized_user_file()) instead of pickle.