Add GEMINI_API_KEY_FILE for secure credential storage#3
Conversation
Adds the standard _FILE suffix convention (used by Docker secrets and Kubernetes) so API keys can be stored in files with restricted permissions instead of in environment variables or config JSON. When GEMINI_API_KEY is not set, the server checks GEMINI_API_KEY_FILE and reads the key from the specified file path. This prevents keys from being stored in plaintext in Claude Code's MCP config or being visible in process lists via container env vars. Backward compatible — GEMINI_API_KEY env var still works. Assisted-by: Claude Code
There was a problem hiding this comment.
Code Review
This pull request introduces support for loading the Gemini API key from a file via the GEMINI_API_KEY_FILE environment variable, with corresponding updates to the Containerfile and README. Review feedback identifies that this implementation violates repository style guide rules prohibiting filesystem access and requiring credentials to be environment-variable only. Furthermore, the implementation lacks proper error handling for file reads and fails to ensure the loaded key is available for the existing error sanitization logic. A minor documentation improvement was also suggested for path consistency.
| api_key = key_path.read_text().strip() | ||
| logger.info(f"Loaded API key from {key_path}") |
There was a problem hiding this comment.
This implementation has two issues:
- Security: It breaks the API key sanitization logic in
GeminiApiError(defined insrc/mcp_gemini_crunchtools/errors.py). That logic relies on theGEMINI_API_KEYenvironment variable to identify and mask the key in error messages. When the key is loaded from a file, this variable remains unset, potentially leaking the plaintext key in logs or MCP responses if the Gemini API returns it in an error message. - Robustness: The
read_text()call can raisePermissionErroror otherOSErrorif the file is not readable. These should be caught and wrapped in aConfigurationErrorto provide a clear error message and avoid unhandled tracebacks.
| api_key = key_path.read_text().strip() | |
| logger.info(f"Loaded API key from {key_path}") | |
| try: | |
| api_key = key_path.read_text().strip() | |
| # Set environment variable to ensure sanitization in GeminiApiError works | |
| os.environ["GEMINI_API_KEY"] = api_key | |
| logger.info(f"Loaded API key from {key_path}") | |
| except OSError as e: | |
| raise ConfigurationError(f"Failed to read GEMINI_API_KEY_FILE: {e}") |
References
- API credentials MUST use SecretStr — never logged (Rule 10). The sanitization logic in GeminiApiError is essential to enforce this requirement when the API returns the key in error messages. (link)
| key_file = os.environ.get("GEMINI_API_KEY_FILE") | ||
| if key_file: | ||
| key_path = Path(key_file).expanduser() | ||
| if key_path.is_file(): | ||
| api_key = key_path.read_text().strip() | ||
| logger.info(f"Loaded API key from {key_path}") | ||
| else: | ||
| raise ConfigurationError( | ||
| f"GEMINI_API_KEY_FILE points to missing file: {key_file}" | ||
| ) |
There was a problem hiding this comment.
This change introduces filesystem access to load credentials, which appears to violate the Repository Style Guide's security rules. Rule 11 requires credentials to come from environment variables only, and Rule 12 prohibits filesystem access. While file-based secrets are a common best practice in containerized environments, they conflict with the current stated security policy. Please confirm if the style guide should be updated to accommodate this feature.
| chmod 600 ~/.config/mcp-gemini/api-key | ||
|
|
||
| claude mcp add mcp-gemini-crunchtools \ | ||
| --env GEMINI_API_KEY_FILE=$HOME/.config/mcp-gemini/api-key \ |
There was a problem hiding this comment.
Using the ~ shortcut instead of $HOME is more consistent with the code's implementation (which uses Path.expanduser()) and is more robust if the environment variable is stored in a configuration file without shell expansion.
| --env GEMINI_API_KEY_FILE=$HOME/.config/mcp-gemini/api-key \ | |
| --env GEMINI_API_KEY_FILE=~/.config/mcp-gemini/api-key \ |
Summary
GEMINI_API_KEY_FILEenv var support using the standard Docker/Kubernetes_FILEsuffix conventionGEMINI_API_KEYenv var continues to work unchangedCloses #2
Changes
config.pyGEMINI_API_KEY_FILEwhenGEMINI_API_KEYis not set; read key from fileREADME.mdContainerfileTest plan
GEMINI_API_KEY=keystill works (backward compat)GEMINI_API_KEY_FILE=/path/to/filereads key from fileGEMINI_API_KEY_FILEpath raises clear error-v key:/run/secrets/api-key:ro,z -e GEMINI_API_KEY_FILE=/run/secrets/api-keyworksAssisted-by: Claude Code