bug: fix ide-client connection to ide-companion when inside docker via ssh/devcontainer (#15049)

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
This commit is contained in:
kapsner
2026-01-22 20:19:04 +01:00
committed by GitHub
parent 902e5d6dae
commit 233fe90f17
2 changed files with 229 additions and 23 deletions
+47 -17
View File
@@ -585,6 +585,8 @@ export class IdeClient {
try {
const portFile = path.join(
os.tmpdir(),
'gemini',
'ide',
`gemini-ide-server-${this.ideProcessInfo.pid}.json`,
);
const portFileContents = await fs.promises.readFile(portFile, 'utf8');
@@ -671,11 +673,11 @@ export class IdeClient {
return validWorkspaces[0];
}
private createProxyAwareFetch() {
// ignore proxy for '127.0.0.1' by default to allow connecting to the ide mcp server
private async createProxyAwareFetch(ideServerHost: string) {
// ignore proxy for the IDE server host to allow connecting to the ide mcp server
const existingNoProxy = process.env['NO_PROXY'] || '';
const agent = new EnvHttpProxyAgent({
noProxy: [existingNoProxy, '127.0.0.1'].filter(Boolean).join(','),
noProxy: [existingNoProxy, ideServerHost].filter(Boolean).join(','),
});
const undiciPromise = import('undici');
// Suppress unhandled rejection if the promise is not awaited immediately.
@@ -777,23 +779,28 @@ export class IdeClient {
private async establishHttpConnection(port: string): Promise<boolean> {
let transport: StreamableHTTPClientTransport | undefined;
try {
const ideServerHost = getIdeServerHost();
const portNumber = parseInt(port, 10);
// validate port to prevent Server-Side Request Forgery (SSRF) vulnerability
if (isNaN(portNumber) || portNumber <= 0 || portNumber > 65535) {
return false;
}
const serverUrl = `http://${ideServerHost}:${portNumber}/mcp`;
logger.debug('Attempting to connect to IDE via HTTP SSE');
logger.debug(`Server URL: ${serverUrl}`);
this.client = new Client({
name: 'streamable-http-client',
// TODO(#3487): use the CLI version here.
version: '1.0.0',
});
transport = new StreamableHTTPClientTransport(
new URL(`http://${getIdeServerHost()}:${port}/mcp`),
{
fetch: this.createProxyAwareFetch(),
requestInit: {
headers: this.authToken
? { Authorization: `Bearer ${this.authToken}` }
: {},
},
transport = new StreamableHTTPClientTransport(new URL(serverUrl), {
fetch: await this.createProxyAwareFetch(ideServerHost),
requestInit: {
headers: this.authToken
? { Authorization: `Bearer ${this.authToken}` }
: {},
},
);
});
await this.client.connect(transport);
this.registerClientHandlers();
await this.discoverTools();
@@ -846,8 +853,31 @@ export class IdeClient {
}
}
function getIdeServerHost() {
const isInContainer =
fs.existsSync('/.dockerenv') || fs.existsSync('/run/.containerenv');
return isInContainer ? 'host.docker.internal' : '127.0.0.1';
export function getIdeServerHost() {
let host: string;
host = '127.0.0.1';
if (isInContainer()) {
// when ssh-connection (e.g. remote-ssh) or devcontainer setup:
// --> host must be '127.0.0.1' to have cli companion working
if (!isSshConnected() && !isDevContainer()) {
host = 'host.docker.internal';
}
}
logger.debug(`[getIdeServerHost] Mapping IdeServerHost to '${host}'`);
return host;
}
function isInContainer() {
return fs.existsSync('/.dockerenv') || fs.existsSync('/run/.containerenv');
}
function isSshConnected() {
return !!process.env['SSH_CONNECTION'];
}
function isDevContainer() {
return !!(
process.env['VSCODE_REMOTE_CONTAINERS_SESSION'] ||
process.env['REMOTE_CONTAINERS']
);
}