Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GitHub Copilot for Xcode Fails in Enterprise Environments with MITM Proxy #95

Open
bc-lee opened this issue Jan 7, 2025 · 2 comments
Open

Comments

@bc-lee
Copy link

bc-lee commented Jan 7, 2025

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

  1. Set up GitHub Copilot for Xcode in an enterprise network using a MITM proxy.
  2. Attempt to use GitHub Copilot for Xcode on a project.
  3. 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.

@bc-lee
Copy link
Author

bc-lee commented Jan 7, 2025

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

@zkhin
Copy link

zkhin commented Jan 9, 2025

Hm, sounds like bad idea.

zkhin added a commit to zkhin/CopilotForXcode that referenced this issue Jan 14, 2025
Fixes github#95

Add support for specifying extra CA certificates in GitHub Copilot for Xcode.

* Add a new `@AppStorage` property for `nodeExtraCaCerts` in `Core/Sources/HostApp/AdvancedSettings/EnterpriseSection.swift`.
* Add a new `SettingsTextField` for "Node extra CA certs" in `Core/Sources/HostApp/AdvancedSettings/EnterpriseSection.swift`.
* Add a new function `nodeExtraCaCertsChanged` to handle changes in `Core/Sources/HostApp/AdvancedSettings/EnterpriseSection.swift`.
* Add a new environment variable `NODE_EXTRA_CA_CERTS` in `Tool/Sources/GitHubCopilotService/LanguageServer/GitHubCopilotService.swift`.
* Update the `environment` dictionary to include `nodeExtraCaCerts` in `Tool/Sources/GitHubCopilotService/LanguageServer/GitHubCopilotService.swift`.
* Add a new preference key `nodeExtraCaCerts` in `Tool/Sources/Preferences/Keys.swift`.

---

For more details, open the [Copilot Workspace session](https://copilot-workspace.githubnext.com/github/CopilotForXcode/issues/95?shareId=XXXX-XXXX-XXXX-XXXX).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants