CopilotForXcode
CopilotForXcode copied to clipboard
GitHub Copilot for Xcode Fails in Enterprise Environments with MITM Proxy
Describe the bug
In our enterprise environment, HTTPS traffic is intercepted and inspected using a MITM proxy, requiring the installation of a custom root certificate on all machines. While Node.js allows configuring the trust for a custom root certificate using environment variables such as NODE_EXTRA_CA_CERTS=/path/to/certificate.pem, GitHub Copilot for Xcode lacks an equivalent option. This results in the error: unable to get local issuer certificate.
Versions
- Copilot for Xcode: 0.29.0
- Xcode: 16.2 (likely not relevant)
- macOS: 14.7.2 (likely not relevant)
Steps to reproduce
- Set up GitHub Copilot for Xcode in an enterprise network using a MITM proxy.
- Attempt to use GitHub Copilot for Xcode on a project.
- Observe that GitHub Copilot for Xcode fails to make suggestions due to certificate validation issues.
Screenshots N/A
Logs
[2025-01-07T07:12:51.736Z] [info] [GitHubCopilot] [83702] window/logMessage: {
"message" : "[contentExclusion] Fetching content exclusion policies {\n params: {\n repos: '[email protected]:org\/repo.git',\n scope: 'repo'\n }\n}",
"type" : 4
}
[2025-01-07T07:12:51.788Z] [info] [GitHubCopilot] [83702] window/logMessage: {
"message" : "[contentExclusion] FetchError: unable to get local issuer certificate\n at fetch (\/snapshot\/copilot-client\/node_modules\/@adobe\/helix-fetch\/src\/fetch\/index.js:99:11)\n at processTicksAndRejections (node:internal\/process\/task_queues:95:5)\n at cachingFetch (\/snapshot\/copilot-client\/node_modules\/@adobe\/helix-fetch\/src\/fetch\/index.js:288:16)\n at zge.fetch (\/snapshot\/copilot-client\/lib\/src\/network\/helix.ts:93:22)\n at \/snapshot\/copilot-client\/lib\/src\/contentExclusion\/contentExclusions.ts:228:24 {\n type: 'system',\n _name: 'FetchError',\n code: 'UNABLE_TO_GET_ISSUER_CERT_LOCALLY',\n errno: undefined,\n erroredSysCall: undefined\n} Error evaluating policy for <file:\/\/\/path\/to\/some\/swift.swift>",
"type" : 1
}
Additional context In other environments, such as VSCode or IntelliJ IDEA, I can use GitHub Copilot by configuring the custom root certificate. This highlights that the issue is specific to GitHub Copilot for Xcode.
Date: Tue, 7 Jan 2025 21:23:13 +0900
Subject: [PATCH] Add support for specifying extra CA certificates in GitHub Copilot for Xcode
This patch enables users to configure a custom root certificate for
the language server, addressing issues with MITM proxies in
enterprise environments.
Users can now specify the path to extra CA certificates in the advanced
settings, which will be passed to the language server via
the NODE_EXTRA_CA_CERTS environment variable.
Fixes #95
---
.../AdvancedSettings/EnterpriseSection.swift | 17 +++++++++++++++--
.../LanguageServer/GitHubCopilotService.swift | 9 +++++----
Tool/Sources/Preferences/Keys.swift | 4 ++++
3 files changed, 24 insertions(+), 6 deletions(-)
diff --git a/Core/Sources/HostApp/AdvancedSettings/EnterpriseSection.swift b/Core/Sources/HostApp/AdvancedSettings/EnterpriseSection.swift
index bcd0adf..c50bfde 100644
--- a/Core/Sources/HostApp/AdvancedSettings/EnterpriseSection.swift
+++ b/Core/Sources/HostApp/AdvancedSettings/EnterpriseSection.swift
@@ -4,6 +4,7 @@ import Toast
struct EnterpriseSection: View {
@AppStorage(\.gitHubCopilotEnterpriseURI) var gitHubCopilotEnterpriseURI
+ @AppStorage(\.nodeExtraCaCerts) var nodeExtraCaCerts
@Environment(\.toast) var toast
var body: some View {
@@ -11,12 +12,17 @@ struct EnterpriseSection: View {
SettingsTextField(
title: "Auth provider URL",
prompt: "https://your-enterprise.ghe.com",
- text: DebouncedBinding($gitHubCopilotEnterpriseURI, handler: urlChanged).binding
+ text: DebouncedBinding($gitHubCopilotEnterpriseURI, handler: enterpriseUrlChanged).binding
+ )
+ SettingsTextField(
+ title: "Node extra CA certs",
+ prompt: "Path to extra CA certs (requires restart)",
+ text: DebouncedBinding($nodeExtraCaCerts, handler: nodeExtraCaCertsChanged).binding
)
}
}
- func urlChanged(_ url: String) {
+ func enterpriseUrlChanged(_ url: String) {
if !url.isEmpty {
validateAuthURL(url)
}
@@ -26,6 +32,13 @@ struct EnterpriseSection: View {
)
}
+ func nodeExtraCaCertsChanged(_ path: String) {
+ NotificationCenter.default.post(
+ name: .gitHubCopilotShouldRefreshEditorInformation,
+ object: nil
+ )
+ }
+
func validateAuthURL(_ url: String) {
let maybeURL = URL(string: url)
guard let parsedURl = maybeURL else {
diff --git a/Tool/Sources/GitHubCopilotService/LanguageServer/GitHubCopilotService.swift b/Tool/Sources/GitHubCopilotService/LanguageServer/GitHubCopilotService.swift
index e6d64c1..be5ca49 100644
--- a/Tool/Sources/GitHubCopilotService/LanguageServer/GitHubCopilotService.swift
+++ b/Tool/Sources/GitHubCopilotService/LanguageServer/GitHubCopilotService.swift
@@ -130,6 +130,8 @@ public class GitHubCopilotBaseService {
let home = ProcessInfo.processInfo.homePath
let versionNumber = JSONValue(stringLiteral: Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "")
let xcodeVersion = JSONValue(stringLiteral: SystemInfo().xcodeVersion() ?? "")
+ let nodeExtraCaCerts: String = UserDefaults.shared.value(for: \.nodeExtraCaCerts)
+ let nodeExtraCaCertMap: [String: String] = nodeExtraCaCerts.isEmpty ? [:] : ["NODE_EXTRA_CA_CERTS": nodeExtraCaCerts]
#if DEBUG
// Use local language server if set and available
@@ -145,13 +147,13 @@ public class GitHubCopilotBaseService {
}
}
// Set debug port and verbose when running in debug
- let environment: [String: String] = ["HOME": home, "GH_COPILOT_DEBUG_UI_PORT": "8080", "GH_COPILOT_VERBOSE": "true"]
+ let environment: [String: String] = ["HOME": home, "GH_COPILOT_DEBUG_UI_PORT": "8080", "GH_COPILOT_VERBOSE": "true"].merging(nodeExtraCaCertMap) { _, new in new }
#else
- let environment: [String: String] = if UserDefaults.shared.value(for: \.verboseLoggingEnabled) {
+ let environment: [String: String] = (if UserDefaults.shared.value(for: \.verboseLoggingEnabled) {
["HOME": home, "GH_COPILOT_VERBOSE": "true"]
} else {
["HOME": home]
- }
+ }).merging(nodeExtraCaCertMap) { _, new in new }
#endif
let executionParams = Process.ExecutionParameters(
@@ -695,4 +697,3 @@ extension InitializingServer: GitHubCopilotLSP {
try await sendRequest(endpoint.request)
}
}
-
diff --git a/Tool/Sources/Preferences/Keys.swift b/Tool/Sources/Preferences/Keys.swift
index 3e4a4c1..0d109fb 100644
--- a/Tool/Sources/Preferences/Keys.swift
+++ b/Tool/Sources/Preferences/Keys.swift
@@ -551,6 +551,10 @@ public extension UserDefaultPreferenceKeys {
.init(defaultValue: "", key: "GitHubCopilotEnterpriseURI")
}
+ var nodeExtraCaCerts: PreferenceKey<String> {
+ .init(defaultValue: "", key: "NodeExtraCaCerts")
+ }
+
var verboseLoggingEnabled: PreferenceKey<Bool> {
.init(defaultValue: false, key: "VerboseLoggingEnabled")
}
--
2.47.1
Hm, sounds like bad idea.
locally I also configured MITM proxy to intercept traffic when using Xcode copilot, but I usually disabled Proxy strict SSL and that worked well for me.