-
Notifications
You must be signed in to change notification settings - Fork 856
fix(oauth): redact public authentication errors (#1842) #2043
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
e910919
47f4c1a
1656da5
3eb47d2
5b8c024
d5dbb28
e1e4313
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,7 +3,7 @@ import { CLAUDE_CODE_HEADERS, claudeCodeSessionId } from "../adapters/client-fin | |
| import { signalWithTimeout, cancelBodyOnAbort } from "../lib/abort"; | ||
| import { sidecarEnter } from "../lib/sidecar-tracker"; | ||
| import { fetchWithResetRetry } from "../lib/upstream-retry"; | ||
| import { getValidAccessToken } from "../oauth"; | ||
| import { getValidAccessToken, publicOAuthAuthenticationErrorMessage } from "../oauth"; | ||
| import { ANTHROPIC_OAUTH_BETA, CLAUDE_CODE_SYSTEM_INSTRUCTION } from "../oauth/anthropic"; | ||
| import type { DescribeOutcome, VisionSettings } from "./describe"; | ||
|
|
||
|
|
@@ -67,8 +67,8 @@ export async function parseAnthropicVisionSSE(res: Response): Promise<DescribeOu | |
| const delta = isRecord(data.delta) ? data.delta : {}; | ||
| if (delta.type === "text_delta" && typeof delta.text === "string") text += delta.text; | ||
| } else if (data.type === "error") { | ||
| const error = isRecord(data.error) ? data.error : {}; | ||
| terminalError = typeof error.message === "string" ? error.message : "anthropic vision sidecar stream error"; | ||
| // Provider-authored stream errors can contain credentials, paths, or response bodies. | ||
| terminalError = "anthropic vision sidecar stream error"; | ||
| } | ||
| }; | ||
|
|
||
|
|
@@ -116,7 +116,7 @@ export async function describeImageAnthropic( | |
| try { | ||
| token = await getValidAccessToken(providerName); | ||
| } catch (error) { | ||
| return { text: "", error: `anthropic vision sidecar auth failed: ${error instanceof Error ? error.message : String(error)}` }; | ||
| return { text: "", error: `anthropic vision sidecar auth failed: ${publicOAuthAuthenticationErrorMessage(error)}` }; | ||
| } | ||
|
|
||
| const headers: Record<string, string> = { | ||
|
|
@@ -166,7 +166,11 @@ export async function describeImageAnthropic( | |
| if (!res.ok) { | ||
| const responseText = await res.text().catch(() => ""); | ||
| console.warn(`[vision] anthropic sidecar HTTP ${res.status} (${Date.now() - startedAt}ms)`); | ||
| return { text: "", error: `anthropic vision sidecar HTTP ${res.status}: ${responseText.slice(0, 200)}` }; | ||
| if (res.status === 401) { | ||
| return { text: "", error: `anthropic vision sidecar auth failed: ${publicOAuthAuthenticationErrorMessage(new Error(responseText))}` }; | ||
| } | ||
| // Upstream bodies are untrusted and may contain credentials, paths, or provider diagnostics. | ||
| return { text: "", error: `anthropic vision sidecar HTTP ${res.status}` }; | ||
|
Comment on lines
+169
to
+173
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win Both sidecars launder an untrusted 401 body through a projection that cannot consume it.
Existing assertions at 📍 Affects 2 files
🤖 Prompt for AI Agents |
||
| } | ||
| const detachBodyGuard = cancelBodyOnAbort(res.body, linkedSignal.signal); | ||
| try { | ||
|
|
@@ -177,7 +181,7 @@ export async function describeImageAnthropic( | |
| } catch (error) { | ||
| const kind = error instanceof Error && error.name === "TimeoutError" ? "timeout" : "connect_error"; | ||
| console.warn(`[vision] anthropic sidecar ${kind} (${Date.now() - startedAt}ms)`); | ||
| return { text: "", error: error instanceof Error ? error.message : String(error) }; | ||
| return { text: "", error: `anthropic vision sidecar ${kind}` }; | ||
| } finally { | ||
| sidecarExit(); | ||
| linkedSignal.cleanup(); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
Do not return the unsupported provider identifier.
Line 2166 serializes
err.messageandroute.providerName.UnsupportedOAuthProviderErrorembeds the provider identifier in its message.redactSecretStringonly removes secret-shaped values. It does not remove an arbitrary configured provider name.A request that reaches an unrecognized OAuth provider can disclose operator configuration data. Return a fixed message in this branch. Keep the 400 status if it represents invalid configuration.
Proposed fix
if (err instanceof UnsupportedOAuthProviderError) { - const safeProviderName = redactSecretString(route.providerName); return formatErrorResponse( 400, "invalid_request_error", - `${redactSecretString(err.message)}. Remove or reconfigure provider '${safeProviderName}' in the OpenCodex configuration.`, + "OAuth provider is not configured. Update the OpenCodex configuration and retry.", ); }📝 Committable suggestion
🤖 Prompt for AI Agents