mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 09:10:45 +00:00
fix(openai): harden codex device auth prep
This commit is contained in:
11
CHANGELOG.md
11
CHANGELOG.md
@@ -185,6 +185,17 @@ Docs: https://docs.openclaw.ai
|
||||
- Control UI/device pairing: explain scope and role approval upgrades during reconnects, and show requested versus approved access in the Control UI and `openclaw devices` so broader reconnects no longer look like lost pairings. (#69221) Thanks @obviyus.
|
||||
- Gateway/Control UI: surface pending scope, role, and device-metadata pairing approvals in auth errors and Control UI hints so broader reconnects no longer look like random auth breakage. (#69226) Thanks @obviyus.
|
||||
|
||||
## 2026.4.19-beta.2
|
||||
|
||||
### Fixes
|
||||
|
||||
- Agents/openai-completions: always send `stream_options.include_usage` on streaming requests, so local and custom OpenAI-compatible backends report real context usage instead of showing 0%. (#68746) Thanks @kagura-agent.
|
||||
- Agents/nested lanes: scope nested agent work per target session so a long-running nested run on one session no longer head-of-line blocks unrelated sessions across the gateway. (#67785) Thanks @stainlu.
|
||||
- Agents/status: preserve carried-forward session token totals for providers that omit usage metadata, so `/status` and `openclaw sessions` keep showing the last known context usage instead of dropping back to unknown/0%. (#67695) Thanks @stainlu.
|
||||
- Install/update: keep legacy update verification compatible with the QA Lab runtime shim, so updating older global installs to beta no longer fails after npm installs the package successfully.
|
||||
|
||||
## 2026.4.19-beta.1
|
||||
|
||||
### Fixes
|
||||
|
||||
- Agents/channels: route cross-agent subagent spawns through the target agent's bound channel account while preserving peer and workspace/role-scoped bindings, so child sessions no longer inherit the caller's account in shared rooms, workspaces, or multi-account setups. (#67508) Thanks @lukeboyett and @gumadeiras.
|
||||
|
||||
@@ -182,4 +182,34 @@ describe("loginOpenAICodexDeviceCode", () => {
|
||||
"OpenAI device authorization failed: authorization_declined spoofed (Denied next line)",
|
||||
);
|
||||
});
|
||||
|
||||
it("strips C1 terminal controls from reflected device-code errors", async () => {
|
||||
const fetchMock = vi
|
||||
.fn()
|
||||
.mockResolvedValueOnce(
|
||||
createJsonResponse({
|
||||
device_auth_id: "device-auth-123",
|
||||
user_code: "CODE-12345",
|
||||
interval: "0",
|
||||
}),
|
||||
)
|
||||
.mockResolvedValueOnce(
|
||||
createJsonResponse(
|
||||
{
|
||||
error: `authorization_declined${String.fromCharCode(0x9b)}spoofed`,
|
||||
error_description: `Denied${String.fromCharCode(0x9d)}next line`,
|
||||
},
|
||||
{ status: 401 },
|
||||
),
|
||||
);
|
||||
|
||||
await expect(
|
||||
loginOpenAICodexDeviceCode({
|
||||
fetchFn: fetchMock as typeof fetch,
|
||||
onVerification: async () => {},
|
||||
}),
|
||||
).rejects.toThrow(
|
||||
"OpenAI device authorization failed: authorization_declined spoofed (Denied next line)",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -88,7 +88,9 @@ function sanitizeDeviceCodeErrorText(value: string): string {
|
||||
const c0Start = String.fromCharCode(0x00);
|
||||
const c0End = String.fromCharCode(0x1f);
|
||||
const del = String.fromCharCode(0x7f);
|
||||
const controlCharsRegex = new RegExp(`[${c0Start}-${c0End}${del}]`, "g");
|
||||
const c1Start = String.fromCharCode(0x80);
|
||||
const c1End = String.fromCharCode(0x9f);
|
||||
const controlCharsRegex = new RegExp(`[${c0Start}-${c0End}${del}${c1Start}-${c1End}]`, "g");
|
||||
return value
|
||||
.replace(osc8Regex, "")
|
||||
.replace(ansiCsiRegex, "")
|
||||
|
||||
@@ -333,6 +333,14 @@ describe("openai codex provider", () => {
|
||||
const logOutput = runtime.log.mock.calls.flat().join("\n");
|
||||
expect(logOutput).toContain("https://auth.openai.com/codex/device");
|
||||
expect(logOutput).not.toContain("CODE-12345");
|
||||
expect(note).toHaveBeenCalledWith(
|
||||
expect.stringContaining("Code: [shown on the local device only]"),
|
||||
"OpenAI Codex device code",
|
||||
);
|
||||
expect(note).not.toHaveBeenCalledWith(
|
||||
expect.stringContaining("Code: CODE-12345"),
|
||||
"OpenAI Codex device code",
|
||||
);
|
||||
});
|
||||
|
||||
it("exposes Codex CLI auth as a runtime-only external profile", () => {
|
||||
|
||||
@@ -319,13 +319,16 @@ async function runOpenAICodexDeviceCode(ctx: ProviderAuthContext) {
|
||||
onProgress: (message) => spin.update(message),
|
||||
onVerification: async ({ verificationUrl, userCode, expiresInMs }) => {
|
||||
const expiresInMinutes = Math.max(1, Math.round(expiresInMs / 60_000));
|
||||
const codeLine = ctx.isRemote
|
||||
? "Code: [shown on the local device only]"
|
||||
: `Code: ${userCode}`;
|
||||
await ctx.prompter.note(
|
||||
[
|
||||
ctx.isRemote
|
||||
? "Open this URL in your LOCAL browser and enter the code below."
|
||||
: "Open this URL in your browser and enter the code below.",
|
||||
`URL: ${verificationUrl}`,
|
||||
`Code: ${userCode}`,
|
||||
codeLine,
|
||||
`Code expires in ${expiresInMinutes} minutes. Never share it.`,
|
||||
].join("\n"),
|
||||
"OpenAI Codex device code",
|
||||
|
||||
Reference in New Issue
Block a user