Skip to content

Commit 0fc1538

Browse files
gemini-cli-robotchrstnbgaldawave
authored
fix(patch): cherry-pick 58df1c6 to release/v0.30.0-pr-20374 [CONFLICTS] (#20567)
Co-authored-by: christine betts <chrstn@uw.edu> Co-authored-by: Gal Zahavi <38544478+galz10@users.noreply.github.com>
1 parent 176b2ba commit 0fc1538

7 files changed

Lines changed: 458 additions & 15 deletions

File tree

docs/tools/mcp-server.md

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,8 @@ Each server configuration supports the following properties:
163163
- **`args`** (string[]): Command-line arguments for Stdio transport
164164
- **`headers`** (object): Custom HTTP headers when using `url` or `httpUrl`
165165
- **`env`** (object): Environment variables for the server process. Values can
166-
reference environment variables using `$VAR_NAME` or `${VAR_NAME}` syntax
166+
reference environment variables using `$VAR_NAME` or `${VAR_NAME}` syntax (all
167+
platforms), or `%VAR_NAME%` (Windows only).
167168
- **`cwd`** (string): Working directory for Stdio transport
168169
- **`timeout`** (number): Request timeout in milliseconds (default: 600,000ms =
169170
10 minutes)
@@ -184,6 +185,63 @@ Each server configuration supports the following properties:
184185
Service Account to impersonate. Used with
185186
`authProviderType: 'service_account_impersonation'`.
186187

188+
### Environment variable expansion
189+
190+
Gemini CLI automatically expands environment variables in the `env` block of
191+
your MCP server configuration. This allows you to securely reference variables
192+
defined in your shell or environment without hardcoding sensitive information
193+
directly in your `settings.json` file.
194+
195+
The expansion utility supports:
196+
197+
- **POSIX/Bash syntax:** `$VARIABLE_NAME` or `${VARIABLE_NAME}` (supported on
198+
all platforms)
199+
- **Windows syntax:** `%VARIABLE_NAME%` (supported only when running on Windows)
200+
201+
If a variable is not defined in the current environment, it resolves to an empty
202+
string.
203+
204+
**Example:**
205+
206+
```json
207+
"env": {
208+
"API_KEY": "$MY_EXTERNAL_TOKEN",
209+
"LOG_LEVEL": "$LOG_LEVEL",
210+
"TEMP_DIR": "%TEMP%"
211+
}
212+
```
213+
214+
### Security and environment sanitization
215+
216+
To protect your credentials, Gemini CLI performs environment sanitization when
217+
spawning MCP server processes.
218+
219+
#### Automatic redaction
220+
221+
By default, the CLI redacts sensitive environment variables from the base
222+
environment (inherited from the host process) to prevent unintended exposure to
223+
third-party MCP servers. This includes:
224+
225+
- Core project keys: `GEMINI_API_KEY`, `GOOGLE_API_KEY`, etc.
226+
- Variables matching sensitive patterns: `*TOKEN*`, `*SECRET*`, `*PASSWORD*`,
227+
`*KEY*`, `*AUTH*`, `*CREDENTIAL*`.
228+
- Certificates and private key patterns.
229+
230+
#### Explicit overrides
231+
232+
If an environment variable must be passed to an MCP server, you must explicitly
233+
state it in the `env` property of the server configuration in `settings.json`.
234+
Explicitly defined variables (including those from extensions) are trusted and
235+
are **not** subjected to the automatic redaction process.
236+
237+
This follows the security principle that if a variable is explicitly configured
238+
by the user for a specific server, it constitutes informed consent to share that
239+
specific data with that server.
240+
241+
> **Note:** Even when explicitly defined, you should avoid hardcoding secrets.
242+
> Instead, use environment variable expansion (e.g., `"MY_KEY": "$MY_KEY"`) to
243+
> securely pull the value from your host environment at runtime.
244+
187245
### OAuth support for remote MCP servers
188246

189247
The Gemini CLI supports OAuth 2.0 authentication for remote MCP servers using
@@ -738,7 +796,9 @@ The MCP integration tracks several states:
738796
- **Trust settings:** The `trust` option bypasses all confirmation dialogs. Use
739797
cautiously and only for servers you completely control
740798
- **Access tokens:** Be security-aware when configuring environment variables
741-
containing API keys or tokens
799+
containing API keys or tokens. See
800+
[Security and environment sanitization](#security-and-environment-sanitization)
801+
for details on how Gemini CLI protects your credentials.
742802
- **Sandbox compatibility:** When using sandboxing, ensure MCP servers are
743803
available within the sandbox environment
744804
- **Private data:** Using broadly scoped personal access tokens can lead to

package-lock.json

Lines changed: 32 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/core/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@
5353
"ajv-formats": "^3.0.0",
5454
"chardet": "^2.1.0",
5555
"diff": "^8.0.3",
56+
"dotenv": "^17.2.4",
57+
"dotenv-expand": "^12.0.3",
5658
"fast-levenshtein": "^2.0.6",
5759
"fdir": "^6.4.6",
5860
"fzf": "^0.5.2",

packages/core/src/tools/mcp-client.test.ts

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1696,6 +1696,114 @@ describe('mcp-client', () => {
16961696
expect(callArgs.env!['GEMINI_CLI_EXT_VAR']).toBeUndefined();
16971697
});
16981698

1699+
it('should include extension settings with defined values in environment', async () => {
1700+
const mockedTransport = vi
1701+
.spyOn(SdkClientStdioLib, 'StdioClientTransport')
1702+
.mockReturnValue({} as SdkClientStdioLib.StdioClientTransport);
1703+
1704+
await createTransport(
1705+
'test-server',
1706+
{
1707+
command: 'test-command',
1708+
extension: {
1709+
name: 'test-ext',
1710+
resolvedSettings: [
1711+
{
1712+
envVar: 'GEMINI_CLI_EXT_VAR',
1713+
value: 'defined-value',
1714+
sensitive: false,
1715+
name: 'ext-setting',
1716+
},
1717+
],
1718+
version: '',
1719+
isActive: false,
1720+
path: '',
1721+
contextFiles: [],
1722+
id: '',
1723+
},
1724+
},
1725+
false,
1726+
EMPTY_CONFIG,
1727+
);
1728+
1729+
const callArgs = mockedTransport.mock.calls[0][0];
1730+
expect(callArgs.env).toBeDefined();
1731+
expect(callArgs.env!['GEMINI_CLI_EXT_VAR']).toBe('defined-value');
1732+
});
1733+
1734+
it('should resolve environment variables in mcpServerConfig.env using extension settings', async () => {
1735+
const mockedTransport = vi
1736+
.spyOn(SdkClientStdioLib, 'StdioClientTransport')
1737+
.mockReturnValue({} as SdkClientStdioLib.StdioClientTransport);
1738+
1739+
await createTransport(
1740+
'test-server',
1741+
{
1742+
command: 'test-command',
1743+
env: {
1744+
RESOLVED_VAR: '$GEMINI_CLI_EXT_VAR',
1745+
},
1746+
extension: {
1747+
name: 'test-ext',
1748+
resolvedSettings: [
1749+
{
1750+
envVar: 'GEMINI_CLI_EXT_VAR',
1751+
value: 'ext-value',
1752+
sensitive: false,
1753+
name: 'ext-setting',
1754+
},
1755+
],
1756+
version: '',
1757+
isActive: false,
1758+
path: '',
1759+
contextFiles: [],
1760+
id: '',
1761+
},
1762+
},
1763+
false,
1764+
EMPTY_CONFIG,
1765+
);
1766+
1767+
const callArgs = mockedTransport.mock.calls[0][0];
1768+
expect(callArgs.env).toBeDefined();
1769+
expect(callArgs.env!['GEMINI_CLI_EXT_VAR']).toBe('ext-value');
1770+
expect(callArgs.env!['RESOLVED_VAR']).toBe('ext-value');
1771+
});
1772+
1773+
it('should expand environment variables in mcpServerConfig.env and not redact them', async () => {
1774+
const mockedTransport = vi
1775+
.spyOn(SdkClientStdioLib, 'StdioClientTransport')
1776+
.mockReturnValue({} as SdkClientStdioLib.StdioClientTransport);
1777+
1778+
const originalEnv = process.env;
1779+
process.env = {
1780+
...originalEnv,
1781+
GEMINI_TEST_VAR: 'expanded-value',
1782+
};
1783+
1784+
try {
1785+
await createTransport(
1786+
'test-server',
1787+
{
1788+
command: 'test-command',
1789+
env: {
1790+
TEST_EXPANDED: 'Value is $GEMINI_TEST_VAR',
1791+
SECRET_KEY: 'intentional-secret-123',
1792+
},
1793+
},
1794+
false,
1795+
EMPTY_CONFIG,
1796+
);
1797+
1798+
const callArgs = mockedTransport.mock.calls[0][0];
1799+
expect(callArgs.env).toBeDefined();
1800+
expect(callArgs.env!['TEST_EXPANDED']).toBe('Value is expanded-value');
1801+
expect(callArgs.env!['SECRET_KEY']).toBe('intentional-secret-123');
1802+
} finally {
1803+
process.env = originalEnv;
1804+
}
1805+
});
1806+
16991807
describe('useGoogleCredentialProvider', () => {
17001808
beforeEach(() => {
17011809
// Mock GoogleAuth client

0 commit comments

Comments
 (0)