From a1c8adc6ba385b2bec7c06d1052fa61ff1cf9b67 Mon Sep 17 00:00:00 2001 From: Dipesh Babu Date: Sat, 26 Sep 2026 23:27:19 -0400 Subject: [PATCH 1/4] fix(github): normalize host casing before selecting the API --- sdk/typescript/src/github.ts | 1 + sdk/typescript/tests-ts/github.test.ts | 13 +++++++++++++ 2 files changed, 14 insertions(+) diff --git a/sdk/typescript/src/github.ts b/sdk/typescript/src/github.ts index 8cfcfac0bc..cd7df78a7b 100644 --- a/sdk/typescript/src/github.ts +++ b/sdk/typescript/src/github.ts @@ -56,6 +56,7 @@ export async function createAuthenticatedGitHub( } = {}, ): Promise { options.signal?.throwIfAborted(); + host = host.toLowerCase(); let token = options.token; if (token === undefined) { const trusted = await resolveTrustedExecutable( diff --git a/sdk/typescript/tests-ts/github.test.ts b/sdk/typescript/tests-ts/github.test.ts index 1eed0f7c53..e36b0c2c39 100644 --- a/sdk/typescript/tests-ts/github.test.ts +++ b/sdk/typescript/tests-ts/github.test.ts @@ -274,6 +274,19 @@ describe("GitHub code scanning import", () => { createAuthenticatedGitHub("github.com", { token: " " }), ).rejects.toThrow("must not be empty"); }); + + test.each(["github.com", "GITHUB.COM", "GitHub.Com"])( + "uses the public GitHub API for hostname %s", + async (host) => { + const client = await createAuthenticatedGitHub(host, { + token: "SYNTHETIC_SDK_TOKEN", + environment: {}, + }); + expect(client.request.endpoint("GET /user").url).toBe( + "https://api.github.com/user", + ); + }, + ); }); describe("GitHub import CLI", () => { From b2d773ba827b0f4d804e8bd6a26f2771cc5002c7 Mon Sep 17 00:00:00 2001 From: Dipesh Babu Date: Sat, 26 Sep 2026 23:29:30 -0400 Subject: [PATCH 2/4] test(github): assert resolved endpoints without literal URL narrowing --- sdk/typescript/tests-ts/github.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sdk/typescript/tests-ts/github.test.ts b/sdk/typescript/tests-ts/github.test.ts index e36b0c2c39..23dbc4f952 100644 --- a/sdk/typescript/tests-ts/github.test.ts +++ b/sdk/typescript/tests-ts/github.test.ts @@ -282,9 +282,9 @@ describe("GitHub code scanning import", () => { token: "SYNTHETIC_SDK_TOKEN", environment: {}, }); - expect(client.request.endpoint("GET /user").url).toBe( - "https://api.github.com/user", - ); + expect(client.request.endpoint("GET /user")).toMatchObject({ + url: "https://api.github.com/user", + }); }, ); }); From 320b70b90296d40f20c960114362d12f7990e2e9 Mon Sep 17 00:00:00 2001 From: Michael D'Angelo Date: Mon, 5 Oct 2026 09:23:19 +0000 Subject: [PATCH 3/4] fix(github): select Enterprise Cloud API endpoints --- sdk/typescript/src/github.ts | 5 ++++- sdk/typescript/tests-ts/github.test.ts | 29 ++++++++++++++++++++++---- 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/sdk/typescript/src/github.ts b/sdk/typescript/src/github.ts index c36d730e46..b1d57a0817 100644 --- a/sdk/typescript/src/github.ts +++ b/sdk/typescript/src/github.ts @@ -88,7 +88,10 @@ export async function createAuthenticatedGitHub( } return new Octokit({ auth: token, - ...(host === "github.com" ? {} : { baseUrl: `https://${host}/api/v3` }), + baseUrl: + host === "github.com" || host.endsWith(".ghe.com") + ? `https://api.${host}` + : `https://${host}/api/v3`, }); } diff --git a/sdk/typescript/tests-ts/github.test.ts b/sdk/typescript/tests-ts/github.test.ts index 5578793dfd..677e4d69ff 100644 --- a/sdk/typescript/tests-ts/github.test.ts +++ b/sdk/typescript/tests-ts/github.test.ts @@ -303,15 +303,36 @@ describe("GitHub code scanning import", () => { ).rejects.toThrow("must not be empty"); }); - test.each(["github.com", "GITHUB.COM", "GitHub.Com"])( - "uses the public GitHub API for hostname %s", - async (host) => { + test.each([ + ["github.com", "https://api.github.com", "/graphql"], + ["GITHUB.COM", "https://api.github.com", "/graphql"], + ["GitHub.Com", "https://api.github.com", "/graphql"], + ["example.ghe.com", "https://api.example.ghe.com", "/graphql"], + ["Example.GHE.COM", "https://api.example.ghe.com", "/graphql"], + [ + "GitHub.Example.Com:8443", + "https://github.example.com:8443/api/v3", + "/api/graphql", + ], + ])( + "selects the REST and GraphQL endpoints for hostname %s", + async (host, baseUrl, graphqlPath) => { const client = await createAuthenticatedGitHub(host, { token: "SYNTHETIC_SDK_TOKEN", environment: {}, }); expect(client.request.endpoint("GET /user")).toMatchObject({ - url: "https://api.github.com/user", + url: `${baseUrl}/user`, + }); + await client.graphql("query { viewer { login } }", { + request: { + fetch: async (url: string | URL | Request) => { + expect(String(url)).toBe( + `${new URL(baseUrl).origin}${graphqlPath}`, + ); + return Response.json({ data: { viewer: { login: "synthetic" } } }); + }, + }, }); }, ); From 72dc0c19baa565cd8f2535a2e601159c52667329 Mon Sep 17 00:00:00 2001 From: Michael D'Angelo Date: Sat, 17 Oct 2026 02:24:38 +0000 Subject: [PATCH 4/4] test(github): cover canonical credential lookup and API routing --- sdk/typescript/tests-ts/github.test.ts | 65 ++++++++++++++++++++------ 1 file changed, 50 insertions(+), 15 deletions(-) diff --git a/sdk/typescript/tests-ts/github.test.ts b/sdk/typescript/tests-ts/github.test.ts index 0c56990587..50edf5f9f7 100644 --- a/sdk/typescript/tests-ts/github.test.ts +++ b/sdk/typescript/tests-ts/github.test.ts @@ -4,9 +4,10 @@ import { captureCli, } from "./support/cli-run.js"; import { resolving } from "./support/promises.js"; -import { mkdtemp, rm, writeFile } from "node:fs/promises"; +import { mkdir, mkdtemp, rm, symlink, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; +import { pathToFileURL } from "node:url"; import { Octokit } from "@octokit/core"; import { describe, expect, test, mock } from "bun:test"; import { main } from "../src/cli.js"; @@ -19,6 +20,7 @@ import { import { dependencies, FakeSignals } from "./cli-fixtures.js"; import type { OnCodex } from "./cli-fixtures.js"; import { throwing } from "./support/errors.js"; +import { nodeCommand } from "./support/shell.js"; function alert(number: number, ref = "refs/heads/main") { return { @@ -303,12 +305,58 @@ describe("GitHub code scanning import", () => { ).rejects.toThrow("must not be empty"); }); + test("normalizes the hostname used for GitHub CLI credential lookup", async () => { + const root = await mkdtemp(join(tmpdir(), "codex-security-github-auth-")); + try { + const repository = join(root, "repository"); + const script = join(root, "gh.mjs"); + await mkdir(repository); + await symlink( + nodeCommand().command, + join(root, process.platform === "win32" ? "gh.exe" : "gh"), + "file", + ); + await writeFile( + script, + `import assert from "node:assert/strict"; +import { basename } from "node:path"; +assert.deepEqual([basename(process.argv[1]), ...process.argv.slice(2)], ["auth", "token", "--hostname", process.env.EXPECTED_GITHUB_HOST]); +console.log("SYNTHETIC_CLI_TOKEN"); +process.exit(0);`, + ); + for (const host of [ + "GitHub.Com", + "Example.GHE.COM", + "GitHub.Example.Com:8443", + ]) { + const client = await createAuthenticatedGitHub(host, { + currentDirectory: repository, + environment: { + PATH: root, + SystemRoot: process.env["SystemRoot"], + NODE_OPTIONS: `--import=${pathToFileURL(script).href}`, + EXPECTED_GITHUB_HOST: host.toLowerCase(), + }, + }); + expect(await client.auth()).toMatchObject({ + token: "SYNTHETIC_CLI_TOKEN", + }); + } + } finally { + await rm(root, { recursive: true, force: true }); + } + }); + test.each([ ["github.com", "https://api.github.com", "/graphql"], - ["GITHUB.COM", "https://api.github.com", "/graphql"], ["GitHub.Com", "https://api.github.com", "/graphql"], ["example.ghe.com", "https://api.example.ghe.com", "/graphql"], ["Example.GHE.COM", "https://api.example.ghe.com", "/graphql"], + [ + "github.example.test", + "https://github.example.test/api/v3", + "/api/graphql", + ], [ "GitHub.Example.Com:8443", "https://github.example.com:8443/api/v3", @@ -477,16 +525,3 @@ describe("GitHub import CLI", () => { expect(signals.listeners.get("SIGTERM")?.size).toBe(0); }); }); - -for (const [host, url] of [ - ["github.com", "https://api.github.com/user"], - ["example.ghe.com", "https://api.example.ghe.com/user"], - ["github.example.test", "https://github.example.test/api/v3/user"], -]) { - test(`selects the documented API base for ${host}`, async () => { - const client = await createAuthenticatedGitHub(host!, { - token: "synthetic-token", - }); - expect(client.request.endpoint("/user").url).toBe(url!); - }); -}