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 diff --git a/sdk/typescript/src/cli.ts b/sdk/typescript/src/cli.ts index 34f773d8..d1646bcd 100644 --- a/sdk/typescript/src/cli.ts +++ b/sdk/typescript/src/cli.ts @@ -1629,7 +1629,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) { @@ -1654,8 +1655,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" };