From c3510be219cbb71c06c1fab229d98941ec0f55de Mon Sep 17 00:00:00 2001 From: Rohan Poudel Date: Mon, 3 Aug 2026 00:27:19 -0600 Subject: [PATCH 1/2] fix(cli): use shell-neutral guidance for overriding API key variables `codex-security login status` and `codex-security login` printed POSIX-only `unset OPENAI_API_KEY CODEX_API_KEY` guidance when an environment API key overrides (or would override) a stored ChatGPT sign-in. `unset` is not a valid PowerShell command, so Windows users received advice they could not act on. Both call sites now describe the fix in shell-neutral terms ("remove ... from the environment, then run the command again") instead of naming a POSIX shell built-in. The dynamic variant, which lists whichever of OPENAI_API_KEY/CODEX_API_KEY are actually set, still reads naturally for one or two variables. Fixes #33 --- sdk/typescript/src/cli.ts | 8 ++- .../tests-ts/cli-authentication.test.ts | 68 +++++++++++++++---- 2 files changed, 60 insertions(+), 16 deletions(-) diff --git a/sdk/typescript/src/cli.ts b/sdk/typescript/src/cli.ts index 4ff4f026..479a542d 100644 --- a/sdk/typescript/src/cli.ts +++ b/sdk/typescript/src/cli.ts @@ -1622,7 +1622,8 @@ export async function main( `Effective scan authentication: API key from ${authentication.source}.\n`, ); errorOutput.write( - "To use a ChatGPT sign-in, unset OPENAI_API_KEY and CODEX_API_KEY.\n", + "To use a ChatGPT sign-in, remove OPENAI_API_KEY and CODEX_API_KEY " + + "from the environment, then run the command again.\n", ); } } else if (exitCode === 0 && !options.withApiKey) { @@ -1647,8 +1648,9 @@ export async function main( : "your ChatGPT sign-in"; errorOutput.write( loginWarning + - `To use ${storedCredentials}, pass '--auth chatgpt' or run ` + - `'unset ${configuredApiKeyVariables.join(" ")}'.\n`, + `To use ${storedCredentials}, pass '--auth chatgpt' or remove ` + + `${configuredApiKeyVariables.join(" and ")} from the environment, ` + + "then run the command again.\n", ); } } diff --git a/sdk/typescript/tests-ts/cli-authentication.test.ts b/sdk/typescript/tests-ts/cli-authentication.test.ts index f2e4c00b..5f0feb33 100644 --- a/sdk/typescript/tests-ts/cli-authentication.test.ts +++ b/sdk/typescript/tests-ts/cli-authentication.test.ts @@ -170,25 +170,26 @@ describe("CLI authentication", () => { `Effective scan authentication: API key from ${expectedSource}.`, ); expect(stderr.text()).toContain( - "To use a ChatGPT sign-in, unset OPENAI_API_KEY and CODEX_API_KEY.", + "To use a ChatGPT sign-in, remove OPENAI_API_KEY and CODEX_API_KEY " + + "from the environment, then run the command again.", ); expect(stderr.text()).not.toContain("SYNTHETIC_SECRET"); } }); - test("explains interactive choice and how to unset every shadowing key after ChatGPT login", async () => { - for (const [argv, environment, source, unsetCommand] of [ + test("explains interactive choice and how to remove every shadowing key after ChatGPT login", async () => { + for (const [argv, environment, source, removeVariables] of [ [ ["login"], { OPENAI_API_KEY: "sk-proj-SYNTHETIC_SECRET_123" }, "OPENAI_API_KEY", - "unset OPENAI_API_KEY", + "OPENAI_API_KEY", ], [ ["login", "--device-auth"], { Codex_Api_Key: "sk-proj-SYNTHETIC_SECRET_456" }, "CODEX_API_KEY", - "unset Codex_Api_Key", + "Codex_Api_Key", ], [ ["login"], @@ -197,7 +198,7 @@ describe("CLI authentication", () => { CODEX_API_KEY: "sk-proj-SYNTHETIC_SECRET_456", }, "OPENAI_API_KEY", - "unset OPENAI_API_KEY CODEX_API_KEY", + "OPENAI_API_KEY and CODEX_API_KEY", ], ] as const) { const stdout = capture(); @@ -219,22 +220,24 @@ describe("CLI authentication", () => { `noninteractive scans will use ${source}.`, ); expect(stderr.text()).toContain("--auth chatgpt"); - expect(stderr.text()).toContain(`'${unsetCommand}'`); + expect(stderr.text()).toContain( + `remove ${removeVariables} from the environment, then run the command again.`, + ); expect(stderr.text()).not.toContain("SYNTHETIC_SECRET"); } }); test("warns when an environment API key overrides a successful access-token login", async () => { - for (const [environment, source, unsetCommand] of [ + for (const [environment, source, removeVariables] of [ [ { OPENAI_API_KEY: "sk-proj-SYNTHETIC_SECRET_123" }, "OPENAI_API_KEY", - "unset OPENAI_API_KEY", + "OPENAI_API_KEY", ], [ { Codex_Api_Key: "sk-proj-SYNTHETIC_SECRET_456" }, "CODEX_API_KEY", - "unset Codex_Api_Key", + "Codex_Api_Key", ], [ { @@ -242,7 +245,7 @@ describe("CLI authentication", () => { CODEX_API_KEY: "sk-proj-SYNTHETIC_SECRET_456", }, "OPENAI_API_KEY", - "unset OPENAI_API_KEY CODEX_API_KEY", + "OPENAI_API_KEY and CODEX_API_KEY", ], ] as const) { const stdout = capture(); @@ -261,14 +264,53 @@ describe("CLI authentication", () => { `Access-token login succeeded, but noninteractive scans will use ${source}.`, ); expect(stderr.text()).toContain( - "To use your stored credentials, pass '--auth chatgpt' or run ", + "To use your stored credentials, pass '--auth chatgpt' or remove ", + ); + expect(stderr.text()).toContain( + `remove ${removeVariables} from the environment, then run the command again.`, ); - expect(stderr.text()).toContain(`'${unsetCommand}'`); expect(stderr.text()).not.toContain("ChatGPT login succeeded"); expect(stderr.text()).not.toContain("SYNTHETIC_SECRET"); } }); + test("uses shell-neutral guidance when an API key overrides the stored login", async () => { + const posixOnlyShellCommands = [/\bunset\b/, /\bexport\s+\w+=/, /\$env:/i]; + + const statusStdout = capture(); + const statusStderr = capture(); + expect( + await main( + ["login", "status"], + statusStdout.stream, + statusStderr.stream, + dependencies({ environment: { OPENAI_API_KEY: "sk-proj-SYNTHETIC" } }), + ), + ).toBe(0); + for (const pattern of posixOnlyShellCommands) { + expect(statusStderr.text()).not.toMatch(pattern); + } + + const loginStdout = capture(); + const loginStderr = capture(); + expect( + await main( + ["login"], + loginStdout.stream, + loginStderr.stream, + dependencies({ + environment: { + OPENAI_API_KEY: "sk-proj-SYNTHETIC_123", + CODEX_API_KEY: "sk-proj-SYNTHETIC_456", + }, + }), + ), + ).toBe(0); + for (const pattern of posixOnlyShellCommands) { + expect(loginStderr.text()).not.toMatch(pattern); + } + }); + test("does not report a ChatGPT login warning for failed or API-key logins", async () => { const environment = { OPENAI_API_KEY: "synthetic-private-key" }; From 34f3cc84dd3c41da4c3c9bd588ea5e90c12ccd75 Mon Sep 17 00:00:00 2001 From: Rohan Poudel Date: Mon, 3 Aug 2026 18:27:09 -0600 Subject: [PATCH 2/2] docs: document clearing API-key variables on Windows Both READMEs gave Windows readers a documented path for setting an environment API key but not for clearing one. `sdk/typescript/README.md` has a dedicated PowerShell block for `$env:OPENAI_API_KEY`, while the instructions for making a stored ChatGPT sign-in the default offered only a bash `unset OPENAI_API_KEY CODEX_API_KEY`. `unset` is not a PowerShell command, so the docs repeated the asymmetry #33 reported in the CLI's runtime guidance. Add a PowerShell block next to each POSIX one, mirroring the existing "On Windows, set the API key in PowerShell" pattern, and describe the step as removing the variables from the environment so the prose matches the shell-neutral wording the CLI now prints. Refs #33 --- README.md | 10 ++++++++-- sdk/typescript/README.md | 10 ++++++++-- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 6ae20d94..4317e681 100644 --- a/README.md +++ b/README.md @@ -36,13 +36,19 @@ npx @openai/codex-security scan . --auth chatgpt npx @openai/codex-security scan . --auth api-key ``` -To make your ChatGPT sign-in the automatic default, unset any configured API -keys: +To make your ChatGPT sign-in the automatic default, remove any configured API +keys from the environment: ```bash unset OPENAI_API_KEY CODEX_API_KEY ``` +On Windows, remove the API-key variables in PowerShell: + +```powershell +Remove-Item Env:OPENAI_API_KEY, Env:CODEX_API_KEY -ErrorAction SilentlyContinue +``` + Scan history is stored in the Codex Security workbench state directory. If that directory cannot be written, set `CODEX_SECURITY_STATE_DIR` to a writable directory outside the repository. diff --git a/sdk/typescript/README.md b/sdk/typescript/README.md index a42b064c..693d661e 100644 --- a/sdk/typescript/README.md +++ b/sdk/typescript/README.md @@ -156,13 +156,19 @@ for existing CI and unattended scans. The SDK accepts the same selection as `security.run(repository, { auth: "chatgpt" })` and `security.preflight(repository, { auth: "chatgpt" })`. -To make the stored ChatGPT sign-in the automatic default instead, unset any -configured API-key variables: +To make the stored ChatGPT sign-in the automatic default instead, remove any +configured API-key variables from the environment: ```bash unset OPENAI_API_KEY CODEX_API_KEY ``` +On Windows, remove the API-key variables in PowerShell: + +```powershell +Remove-Item Env:OPENAI_API_KEY, Env:CODEX_API_KEY -ErrorAction SilentlyContinue +``` + The interactive choice applies only to the current scan and is not persisted. When an environment key is configured, ChatGPT login and