From 1b04f55ba08c158eb0b7fcd94aec9bf5ef07efe2 Mon Sep 17 00:00:00 2001 From: Landon Cox Date: Tue, 24 Mar 2026 15:57:08 -0700 Subject: [PATCH 1/5] feat: GHEC tenant support for proxy and guard URL parsing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add auto-derivation of GitHub API URL from GITHUB_API_URL and GITHUB_SERVER_URL environment variables, matching the pattern used by GitHub Agentic Workflows. Priority order: 1. --github-api-url flag (explicit) 2. GITHUB_API_URL env var 3. GITHUB_SERVER_URL auto-derivation: - *.ghe.com → api.*.ghe.com (GHEC) - other hosts → /api/v3 (GHES) - github.com → api.github.com 4. Default: https://api.github.com Also fix Rust guard extract_repo_from_github_url() to handle GHEC/GHES URLs by looking for /repos// pattern generically instead of only matching api.github.com prefixes. Closes #2480 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- AGENTS.md | 2 + .../rust-guard/src/labels/helpers.rs | 9 +- .../github-guard/rust-guard/src/labels/mod.rs | 41 +++++++ internal/cmd/proxy.go | 14 ++- internal/proxy/proxy.go | 49 ++++++++ internal/proxy/proxy_test.go | 107 ++++++++++++++++++ 6 files changed, 219 insertions(+), 3 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 824e2e567..344afa53e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -367,6 +367,8 @@ DEBUG_COLORS=0 DEBUG=* ./awmg --config config.toml ## Environment Variables - `GITHUB_PERSONAL_ACCESS_TOKEN` - GitHub auth +- `GITHUB_API_URL` - Explicit GitHub API endpoint (e.g., `https://api.mycompany.ghe.com`); used by proxy to set upstream target +- `GITHUB_SERVER_URL` - GitHub server URL; proxy auto-derives API endpoint: `*.ghe.com` → `api.*.ghe.com`, GHES → `/api/v3`, `github.com` → `api.github.com` - `DOCKER_API_VERSION` - Set by querying Docker daemon's current API version; falls back to `1.44` for all architectures if detection fails - `DEBUG` - Enable debug logging (e.g., `DEBUG=*`, `DEBUG=server:*,launcher:*`) - `DEBUG_COLORS` - Control colored output (0 to disable, auto-disabled when piping) diff --git a/guards/github-guard/rust-guard/src/labels/helpers.rs b/guards/github-guard/rust-guard/src/labels/helpers.rs index 6ced1aaa2..3e3a80249 100644 --- a/guards/github-guard/rust-guard/src/labels/helpers.rs +++ b/guards/github-guard/rust-guard/src/labels/helpers.rs @@ -513,7 +513,7 @@ pub fn extract_repo_info_from_search_query(query: &str) -> (String, String, Stri (String::new(), String::new(), String::new()) } -fn extract_repo_from_github_url(url: &str) -> Option { +pub(crate) fn extract_repo_from_github_url(url: &str) -> Option { let parse_owner_repo = |path: &str| { let mut parts = path.split('/').filter(|segment| !segment.is_empty()); let owner = parts.next()?; @@ -521,6 +521,7 @@ fn extract_repo_from_github_url(url: &str) -> Option { Some(format!("{}/{}", owner, repo)) }; + // Fast path for well-known github.com URLs if let Some(path) = url .strip_prefix("https://api.github.com/repos/") .or_else(|| url.strip_prefix("http://api.github.com/repos/")) @@ -530,6 +531,12 @@ fn extract_repo_from_github_url(url: &str) -> Option { return parse_owner_repo(path); } + // Generic path: handle GHEC (api.*.ghe.com) and GHES (*/api/v3/repos/*) + // by looking for /repos// in the URL path. + if let Some(pos) = url.find("/repos/") { + return parse_owner_repo(&url[pos + 7..]); + } + None } diff --git a/guards/github-guard/rust-guard/src/labels/mod.rs b/guards/github-guard/rust-guard/src/labels/mod.rs index 8c59c2e76..afee5e96b 100644 --- a/guards/github-guard/rust-guard/src/labels/mod.rs +++ b/guards/github-guard/rust-guard/src/labels/mod.rs @@ -3848,4 +3848,45 @@ mod tests { "item path must use /items/ prefix for REST format" ); } + + #[test] + fn test_extract_repo_from_github_url_ghec() { + // GHEC tenant URLs (api..ghe.com) + assert_eq!( + helpers::extract_repo_from_github_url("https://api.mycompany.ghe.com/repos/owner/repo/issues"), + Some("owner/repo".to_string()) + ); + assert_eq!( + helpers::extract_repo_from_github_url("https://api.mycompany.ghe.com/repos/owner/repo"), + Some("owner/repo".to_string()) + ); + } + + #[test] + fn test_extract_repo_from_github_url_ghes() { + // GHES URLs (host/api/v3/repos/...) + assert_eq!( + helpers::extract_repo_from_github_url("https://github.example.com/api/v3/repos/owner/repo/pulls"), + Some("owner/repo".to_string()) + ); + } + + #[test] + fn test_extract_repo_from_github_url_standard() { + // Standard github.com API URLs + assert_eq!( + helpers::extract_repo_from_github_url("https://api.github.com/repos/octocat/Hello-World/issues"), + Some("octocat/Hello-World".to_string()) + ); + // Standard github.com HTML URLs + assert_eq!( + helpers::extract_repo_from_github_url("https://github.com/octocat/Hello-World"), + Some("octocat/Hello-World".to_string()) + ); + // No match + assert_eq!( + helpers::extract_repo_from_github_url("https://example.com/no-repos-path"), + None + ); + } } diff --git a/internal/cmd/proxy.go b/internal/cmd/proxy.go index a6aaebb81..c92a8687f 100644 --- a/internal/cmd/proxy.go +++ b/internal/cmd/proxy.go @@ -92,7 +92,7 @@ Local usage: cmd.Flags().StringVarP(&proxyListen, "listen", "l", "127.0.0.1:8080", "Proxy listen address") cmd.Flags().StringVar(&proxyLogDir, "log-dir", getDefaultLogDir(), "Log file directory") cmd.Flags().StringVar(&proxyDIFCMode, "guards-mode", "filter", "DIFC enforcement mode: strict, filter, propagate") - cmd.Flags().StringVar(&proxyAPIURL, "github-api-url", proxy.DefaultGitHubAPIBase, "Upstream GitHub API URL") + cmd.Flags().StringVar(&proxyAPIURL, "github-api-url", "", "Upstream GitHub API URL (default: auto-derived from GITHUB_API_URL or GITHUB_SERVER_URL, falls back to https://api.github.com)") cmd.Flags().BoolVar(&proxyTLS, "tls", false, "Enable HTTPS with auto-generated self-signed certificates") cmd.Flags().StringVar(&proxyTLSDir, "tls-dir", "", "Directory for TLS certificates (default: /proxy-tls)") cmd.Flags().StringSliceVar(&proxyTrustedBots, "trusted-bots", nil, "Additional trusted bot usernames (comma-separated, extends built-in list)") @@ -144,12 +144,22 @@ func runProxy(cmd *cobra.Command, args []string) error { logger.LogInfo("startup", "No fallback token — proxy will forward client Authorization headers") } + // Resolve GitHub API URL: flag → env vars → default + apiURL := proxyAPIURL + if apiURL == "" { + apiURL = proxy.DeriveGitHubAPIURL() + } + if apiURL == "" { + apiURL = proxy.DefaultGitHubAPIBase + } + logger.LogInfo("startup", "Upstream GitHub API URL: %s", apiURL) + // Create the proxy server proxySrv, err := proxy.New(ctx, proxy.Config{ WasmPath: proxyGuardWasm, Policy: proxyPolicy, GitHubToken: token, - GitHubAPIURL: proxyAPIURL, + GitHubAPIURL: apiURL, DIFCMode: proxyDIFCMode, TrustedBots: proxyTrustedBots, }) diff --git a/internal/proxy/proxy.go b/internal/proxy/proxy.go index 7818dd506..4a9ca5823 100644 --- a/internal/proxy/proxy.go +++ b/internal/proxy/proxy.go @@ -12,6 +12,8 @@ import ( "io" "log" "net/http" + "net/url" + "os" "strings" "time" @@ -31,6 +33,53 @@ const ( ghHostPathPrefix = "/api/v3" ) +// DeriveGitHubAPIURL resolves the upstream GitHub API URL from environment +// variables. Priority order: +// 1. GITHUB_API_URL — explicit API endpoint (e.g. https://api.mycompany.ghe.com) +// 2. GITHUB_SERVER_URL — auto-derive API endpoint from server URL: +// - https://mycompany.ghe.com → https://api.mycompany.ghe.com +// - https://github.mycompany.com → https://github.mycompany.com/api/v3 +// - https://github.com → https://api.github.com +// 3. Returns empty string if no env vars are set (caller uses DefaultGitHubAPIBase) +func DeriveGitHubAPIURL() string { + if apiURL := os.Getenv("GITHUB_API_URL"); apiURL != "" { + logProxy.Printf("GitHub API URL from GITHUB_API_URL: %s", apiURL) + return apiURL + } + if serverURL := os.Getenv("GITHUB_SERVER_URL"); serverURL != "" { + derived := deriveAPIFromServerURL(serverURL) + if derived != "" { + logProxy.Printf("GitHub API URL derived from GITHUB_SERVER_URL=%s: %s", serverURL, derived) + return derived + } + } + return "" +} + +// deriveAPIFromServerURL converts a GITHUB_SERVER_URL to the corresponding API endpoint. +// GHEC tenants (*.ghe.com): https://tenant.ghe.com → https://api.tenant.ghe.com +// GitHub.com: https://github.com → https://api.github.com +// GHES (all others): https://github.example.com → https://github.example.com/api/v3 +func deriveAPIFromServerURL(serverURL string) string { + parsed, err := url.Parse(strings.TrimRight(serverURL, "/")) + if err != nil || parsed.Host == "" { + return "" + } + + host := strings.ToLower(parsed.Host) + + switch { + case host == "github.com" || host == "www.github.com": + return DefaultGitHubAPIBase + case strings.HasSuffix(host, ".ghe.com"): + // GHEC tenant: api..ghe.com + return fmt.Sprintf("%s://api.%s", parsed.Scheme, parsed.Host) + default: + // GHES: /api/v3 + return fmt.Sprintf("%s://%s/api/v3", parsed.Scheme, parsed.Host) + } +} + // Server is a filtering HTTP forward proxy for the GitHub REST/GraphQL API. // It loads the same WASM guard used by the MCP gateway and runs the 6-phase // DIFC pipeline on every proxied response. diff --git a/internal/proxy/proxy_test.go b/internal/proxy/proxy_test.go index 1c69dc0cd..75448255f 100644 --- a/internal/proxy/proxy_test.go +++ b/internal/proxy/proxy_test.go @@ -4,6 +4,7 @@ import ( "io" "net/http" "net/http/httptest" + "os" "testing" "github.com/github/gh-aw-mcpg/internal/difc" @@ -844,3 +845,109 @@ func TestUnwrapSingleObject(t *testing.T) { }) } } + +func TestDeriveAPIFromServerURL(t *testing.T) { + tests := []struct { + name string + serverURL string + expected string + }{ + { + name: "github.com returns default", + serverURL: "https://github.com", + expected: DefaultGitHubAPIBase, + }, + { + name: "github.com with trailing slash", + serverURL: "https://github.com/", + expected: DefaultGitHubAPIBase, + }, + { + name: "GHEC tenant derives api subdomain", + serverURL: "https://mycompany.ghe.com", + expected: "https://api.mycompany.ghe.com", + }, + { + name: "GHEC tenant with trailing slash", + serverURL: "https://mycompany.ghe.com/", + expected: "https://api.mycompany.ghe.com", + }, + { + name: "GHES uses /api/v3 path", + serverURL: "https://github.mycompany.com", + expected: "https://github.mycompany.com/api/v3", + }, + { + name: "GHES with port", + serverURL: "https://github.example.com:8443", + expected: "https://github.example.com:8443/api/v3", + }, + { + name: "empty string", + serverURL: "", + expected: "", + }, + { + name: "invalid URL", + serverURL: "not-a-url", + expected: "", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := deriveAPIFromServerURL(tt.serverURL) + assert.Equal(t, tt.expected, result) + }) + } +} + +func TestDeriveGitHubAPIURL(t *testing.T) { + tests := []struct { + name string + envVars map[string]string + expected string + }{ + { + name: "no env vars returns empty", + envVars: map[string]string{}, + expected: "", + }, + { + name: "GITHUB_API_URL takes priority", + envVars: map[string]string{"GITHUB_API_URL": "https://api.custom.ghe.com", "GITHUB_SERVER_URL": "https://other.ghe.com"}, + expected: "https://api.custom.ghe.com", + }, + { + name: "GITHUB_SERVER_URL auto-derives GHEC", + envVars: map[string]string{"GITHUB_SERVER_URL": "https://mycompany.ghe.com"}, + expected: "https://api.mycompany.ghe.com", + }, + { + name: "GITHUB_SERVER_URL auto-derives GHES", + envVars: map[string]string{"GITHUB_SERVER_URL": "https://github.example.com"}, + expected: "https://github.example.com/api/v3", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Save and clear relevant env vars + savedAPI := os.Getenv("GITHUB_API_URL") + savedServer := os.Getenv("GITHUB_SERVER_URL") + os.Unsetenv("GITHUB_API_URL") + os.Unsetenv("GITHUB_SERVER_URL") + defer func() { + os.Setenv("GITHUB_API_URL", savedAPI) + os.Setenv("GITHUB_SERVER_URL", savedServer) + }() + + for k, v := range tt.envVars { + os.Setenv(k, v) + } + + result := DeriveGitHubAPIURL() + assert.Equal(t, tt.expected, result) + }) + } +} From 880c1d96bdfbbf103662b7da97841ff030ceffdd Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 24 Mar 2026 23:03:18 +0000 Subject: [PATCH 2/5] fix: use copilot-api prefix for GHEC tenant URL derivation Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com> Agent-Logs-Url: https://github.com/github/gh-aw-mcpg/sessions/78885d5c-1a84-4220-82ee-1439dec81702 --- AGENTS.md | 4 ++-- internal/proxy/proxy.go | 10 +++++----- internal/proxy/proxy_test.go | 8 ++++---- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 344afa53e..455b0b5c2 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -367,8 +367,8 @@ DEBUG_COLORS=0 DEBUG=* ./awmg --config config.toml ## Environment Variables - `GITHUB_PERSONAL_ACCESS_TOKEN` - GitHub auth -- `GITHUB_API_URL` - Explicit GitHub API endpoint (e.g., `https://api.mycompany.ghe.com`); used by proxy to set upstream target -- `GITHUB_SERVER_URL` - GitHub server URL; proxy auto-derives API endpoint: `*.ghe.com` → `api.*.ghe.com`, GHES → `/api/v3`, `github.com` → `api.github.com` +- `GITHUB_API_URL` - Explicit GitHub API endpoint (e.g., `https://copilot-api.mycompany.ghe.com`); used by proxy to set upstream target +- `GITHUB_SERVER_URL` - GitHub server URL; proxy auto-derives API endpoint: `*.ghe.com` → `copilot-api.*.ghe.com`, GHES → `/api/v3`, `github.com` → `api.github.com` - `DOCKER_API_VERSION` - Set by querying Docker daemon's current API version; falls back to `1.44` for all architectures if detection fails - `DEBUG` - Enable debug logging (e.g., `DEBUG=*`, `DEBUG=server:*,launcher:*`) - `DEBUG_COLORS` - Control colored output (0 to disable, auto-disabled when piping) diff --git a/internal/proxy/proxy.go b/internal/proxy/proxy.go index 4a9ca5823..06d45b5ca 100644 --- a/internal/proxy/proxy.go +++ b/internal/proxy/proxy.go @@ -35,9 +35,9 @@ const ( // DeriveGitHubAPIURL resolves the upstream GitHub API URL from environment // variables. Priority order: -// 1. GITHUB_API_URL — explicit API endpoint (e.g. https://api.mycompany.ghe.com) +// 1. GITHUB_API_URL — explicit API endpoint (e.g. https://copilot-api.mycompany.ghe.com) // 2. GITHUB_SERVER_URL — auto-derive API endpoint from server URL: -// - https://mycompany.ghe.com → https://api.mycompany.ghe.com +// - https://mycompany.ghe.com → https://copilot-api.mycompany.ghe.com // - https://github.mycompany.com → https://github.mycompany.com/api/v3 // - https://github.com → https://api.github.com // 3. Returns empty string if no env vars are set (caller uses DefaultGitHubAPIBase) @@ -57,7 +57,7 @@ func DeriveGitHubAPIURL() string { } // deriveAPIFromServerURL converts a GITHUB_SERVER_URL to the corresponding API endpoint. -// GHEC tenants (*.ghe.com): https://tenant.ghe.com → https://api.tenant.ghe.com +// GHEC tenants (*.ghe.com): https://tenant.ghe.com → https://copilot-api.tenant.ghe.com // GitHub.com: https://github.com → https://api.github.com // GHES (all others): https://github.example.com → https://github.example.com/api/v3 func deriveAPIFromServerURL(serverURL string) string { @@ -72,8 +72,8 @@ func deriveAPIFromServerURL(serverURL string) string { case host == "github.com" || host == "www.github.com": return DefaultGitHubAPIBase case strings.HasSuffix(host, ".ghe.com"): - // GHEC tenant: api..ghe.com - return fmt.Sprintf("%s://api.%s", parsed.Scheme, parsed.Host) + // GHEC tenant: copilot-api..ghe.com + return fmt.Sprintf("%s://copilot-api.%s", parsed.Scheme, parsed.Host) default: // GHES: /api/v3 return fmt.Sprintf("%s://%s/api/v3", parsed.Scheme, parsed.Host) diff --git a/internal/proxy/proxy_test.go b/internal/proxy/proxy_test.go index 75448255f..c30169525 100644 --- a/internal/proxy/proxy_test.go +++ b/internal/proxy/proxy_test.go @@ -863,14 +863,14 @@ func TestDeriveAPIFromServerURL(t *testing.T) { expected: DefaultGitHubAPIBase, }, { - name: "GHEC tenant derives api subdomain", + name: "GHEC tenant derives copilot-api subdomain", serverURL: "https://mycompany.ghe.com", - expected: "https://api.mycompany.ghe.com", + expected: "https://copilot-api.mycompany.ghe.com", }, { name: "GHEC tenant with trailing slash", serverURL: "https://mycompany.ghe.com/", - expected: "https://api.mycompany.ghe.com", + expected: "https://copilot-api.mycompany.ghe.com", }, { name: "GHES uses /api/v3 path", @@ -921,7 +921,7 @@ func TestDeriveGitHubAPIURL(t *testing.T) { { name: "GITHUB_SERVER_URL auto-derives GHEC", envVars: map[string]string{"GITHUB_SERVER_URL": "https://mycompany.ghe.com"}, - expected: "https://api.mycompany.ghe.com", + expected: "https://copilot-api.mycompany.ghe.com", }, { name: "GITHUB_SERVER_URL auto-derives GHES", From aafd8a4112a5d330a04e97ea94c34ce23beea21f Mon Sep 17 00:00:00 2001 From: Landon Cox Date: Tue, 24 Mar 2026 16:05:21 -0700 Subject: [PATCH 3/5] Update internal/proxy/proxy_test.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- internal/proxy/proxy_test.go | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/internal/proxy/proxy_test.go b/internal/proxy/proxy_test.go index c30169525..f484dd5a0 100644 --- a/internal/proxy/proxy_test.go +++ b/internal/proxy/proxy_test.go @@ -933,13 +933,21 @@ func TestDeriveGitHubAPIURL(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // Save and clear relevant env vars - savedAPI := os.Getenv("GITHUB_API_URL") - savedServer := os.Getenv("GITHUB_SERVER_URL") + savedAPI, hadAPI := os.LookupEnv("GITHUB_API_URL") + savedServer, hadServer := os.LookupEnv("GITHUB_SERVER_URL") os.Unsetenv("GITHUB_API_URL") os.Unsetenv("GITHUB_SERVER_URL") defer func() { - os.Setenv("GITHUB_API_URL", savedAPI) - os.Setenv("GITHUB_SERVER_URL", savedServer) + if hadAPI { + _ = os.Setenv("GITHUB_API_URL", savedAPI) + } else { + _ = os.Unsetenv("GITHUB_API_URL") + } + if hadServer { + _ = os.Setenv("GITHUB_SERVER_URL", savedServer) + } else { + _ = os.Unsetenv("GITHUB_SERVER_URL") + } }() for k, v := range tt.envVars { From 78a1bf67247129dbf889e74a8e7828bbb12c7335 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 24 Mar 2026 23:07:44 +0000 Subject: [PATCH 4/5] Initial plan From 10cc9d34ac0d42fbfe9a2865a5d93c32ebd7eff4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 24 Mar 2026 23:10:59 +0000 Subject: [PATCH 5/5] fix: use parsed.Hostname() in deriveAPIFromServerURL for port-safe GHEC detection Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com> Agent-Logs-Url: https://github.com/github/gh-aw-mcpg/sessions/9a1660e8-6d08-4073-af81-1733316890aa --- internal/proxy/proxy.go | 17 +++++++++++------ internal/proxy/proxy_test.go | 5 +++++ 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/internal/proxy/proxy.go b/internal/proxy/proxy.go index 06d45b5ca..a473b53da 100644 --- a/internal/proxy/proxy.go +++ b/internal/proxy/proxy.go @@ -66,16 +66,21 @@ func deriveAPIFromServerURL(serverURL string) string { return "" } - host := strings.ToLower(parsed.Host) + // Use Hostname() (not Host) so that an optional port does not interfere + // with the suffix / equality checks below. + hostname := strings.ToLower(parsed.Hostname()) switch { - case host == "github.com" || host == "www.github.com": + case hostname == "github.com" || hostname == "www.github.com": return DefaultGitHubAPIBase - case strings.HasSuffix(host, ".ghe.com"): - // GHEC tenant: copilot-api..ghe.com - return fmt.Sprintf("%s://copilot-api.%s", parsed.Scheme, parsed.Host) + case strings.HasSuffix(hostname, ".ghe.com"): + // GHEC tenant: copilot-api..ghe.com (re-add port when present) + if port := parsed.Port(); port != "" { + return fmt.Sprintf("%s://copilot-api.%s:%s", parsed.Scheme, hostname, port) + } + return fmt.Sprintf("%s://copilot-api.%s", parsed.Scheme, hostname) default: - // GHES: /api/v3 + // GHES: /api/v3 (parsed.Host retains the port, if any) return fmt.Sprintf("%s://%s/api/v3", parsed.Scheme, parsed.Host) } } diff --git a/internal/proxy/proxy_test.go b/internal/proxy/proxy_test.go index f484dd5a0..cb16d888b 100644 --- a/internal/proxy/proxy_test.go +++ b/internal/proxy/proxy_test.go @@ -877,6 +877,11 @@ func TestDeriveAPIFromServerURL(t *testing.T) { serverURL: "https://github.mycompany.com", expected: "https://github.mycompany.com/api/v3", }, + { + name: "GHEC tenant with port", + serverURL: "https://mycompany.ghe.com:8443", + expected: "https://copilot-api.mycompany.ghe.com:8443", + }, { name: "GHES with port", serverURL: "https://github.example.com:8443",