{"record":{"id":"73850883b4f204b5","repo":"google-gemini/gemini-cli","slug":"private-ip-addresses-are-not-allowed-for-the-extension","errorCode":null,"errorMessage":"Private IP addresses are not allowed for the extension registry.","messagePattern":"Private IP addresses are not allowed for the extension registry\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/cli/src/config/extensionRegistryClient.ts","lineNumber":116,"sourceCode":"    return results.map((r) => r.item);\n  }\n\n  async getExtension(id: string): Promise<RegistryExtension | undefined> {\n    const allExtensions = await this.fetchAllExtensions();\n    return allExtensions.find((ext) => ext.id === id);\n  }\n\n  private async fetchAllExtensions(): Promise<RegistryExtension[]> {\n    if (ExtensionRegistryClient.fetchPromise) {\n      return ExtensionRegistryClient.fetchPromise;\n    }\n\n    const uri = this.registryURI;\n    ExtensionRegistryClient.fetchPromise = (async () => {\n      try {\n        if (uri.startsWith('http')) {\n          if (await isPrivateIp(uri)) {\n            throw new Error(\n              'Private IP addresses are not allowed for the extension registry.',\n            );\n          }\n          const response = await fetchWithTimeout(\n            uri,\n            ExtensionRegistryClient.FETCH_TIMEOUT_MS,\n          );\n          if (!response.ok) {\n            throw new Error(\n              `Failed to fetch extensions: ${response.statusText}`,\n            );\n          }\n\n          // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion\n          return (await response.json()) as RegistryExtension[];\n        } else {\n          // Handle local file path\n          const filePath = resolveToRealPath(uri);","sourceCodeStart":98,"sourceCodeEnd":134,"githubUrl":"https://github.com/google-gemini/gemini-cli/blob/6a466a7e2fe2b1255752c1e74f69b31f0216084d/packages/cli/src/config/extensionRegistryClient.ts#L98-L134","documentation":"ExtensionRegistryClient guards against SSRF by checking the registry URI with isPrivateIp before fetching. If the registry URL resolves to a private/loopback IP (127.0.0.1, 10.x, 192.168.x, etc.), the client refuses to contact it. This prevents fetching extension lists from internal network hosts.","triggerScenarios":"fetchAllExtensions (invoked via allExtensions) with a registryURI starting with 'http' whose host is or resolves to a private, loopback, or link-local IP address.","commonSituations":"Pointing the extension registry setting at a local mirror (localhost or LAN IP), a corporate proxy address, or a hostname that resolves to an internal IP via /etc/hosts.","solutions":["Set the registry URI to a public, internet-reachable HTTPS endpoint.","If you intentionally need a local registry, use an officially supported way to allow it (e.g. a file:// or local-path registry variant if provided) rather than an http URL to a private IP.","Verify with nslookup/dig what the registry hostname resolves to; switch DNS to a public resolution."],"exampleFix":"// before (settings.json)\n{ \"extension\": { \"registry\": \"http://192.168.1.10/registry.json\" } }\n// after\n{ \"extension\": { \"registry\": \"https://registry.example.com/extensions.json\" } }","handlingStrategy":"validation","validationCode":"const { hostname } = new URL(registryUri);\nconst { lookup } = require('dns').promises;\nconst addrs = await lookup(hostname, { all: true });\nconst isPrivate = addrs.some(a =>\n  /^(10\\.|127\\.|192\\.168\\.|172\\.(1[6-9]|2\\d|3[01])\\.|169\\.254\\.|::1$|fc00:)/i.test(a.address));\nif (isPrivate) throw new Error('registry resolves to a private IP');","typeGuard":null,"tryCatchPattern":"try {\n  await registryClient.allExtensions();\n} catch (e) {\n  if (e.message.includes('Private IP addresses')) {\n    // prompt user to use a public registry or approved local path registry\n  }\n}","preventionTips":["Always use public HTTPS registry endpoints.","Check DNS resolution of custom registry hostnames before configuring them.","Never point the registry at localhost/LAN IPs in shared or production configs."],"tags":["network","ssrf","security"],"backgroundTag":"private-ip-blocked","analyzedSha":"6a466a7e2fe2b1255752c1e74f69b31f0216084d","analyzedAt":"2026-09-16T18:14:43.978Z","contentChangedAt":"2026-09-16T18:14:43.978Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}