fix(ai): block aiBaseUrl from leaking across providers

The user row stores a single `aiBaseUrl` column that is meaningful only
for the LOCAL provider, but `buildUserProvider` and
`resolveProviderForTest` were forwarding that value to OpenAI and
Anthropic clients too. A user who had configured LOCAL pointing at e.g.
`http://192.168.x.x/v1` and later switched to OPENAI would have their
OpenAI API key sent to that LAN host on the next request.

Defence in three layers:

- PATCH /api/user/ai-provider — when the provider switches to anything
  other than LOCAL and the body does not include an explicit
  `aiBaseUrl` write, set the column back to NULL.
- `buildUserProvider` — for OPENAI hardcode `https://api.openai.com/v1`,
  for ANTHROPIC do not forward a base URL at all (the SDK default is
  correct and the UI never exposed an Anthropic base-URL input).
- `resolveProviderForTest` — same belt-and-braces: ignore the merged
  base URL for OPENAI/ANTHROPIC so a stored LOCAL value cannot bleed
  into a connection-test request.

Adds two regression tests covering the LOCAL → OPENAI and LOCAL →
ANTHROPIC paths with a stale `aiBaseUrl`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Marc-André BombeckandClaude Opus 4.7 committed 2026-05-09 01:26:48 +02:00
1 parent a75fbc6aaf
commit eba898f4c1
3 files changed
+71 -5

No files matched your search

+14
View File
@@ -117,6 +117,20 @@ export const PATCH = apiHandler(async (request: NextRequest) => {
throw new HttpError(422, "No valid fields");
}
// When the provider switches away from LOCAL, drop any stored
// `aiBaseUrl`. The column is shared across providers, so without
// this a user who once configured LOCAL → http://192.168.x.x and
// then switched to OPENAI/ANTHROPIC would have their cloud key
// sent to that URL on the next request. Only LOCAL legitimately
// uses a custom base URL.
if (
typeof updates.aiProvider === "string" &&
updates.aiProvider !== "LOCAL" &&
!("aiBaseUrl" in updates)
) {
updates.aiBaseUrl = null;
}
await prisma.user.update({ where: { id: user.id }, data: updates });
return apiSuccess({ updated: true });