Repository navigation
fix(providers): direct-URL boot backfills, pinned CIMD fallback lookup, 502 for blocked provider URLs - #156
Conversation
…n lookups - BootBackfillConnection: when MIGRATION_DATABASE_URL, DATABASE_UNPOOLED_URL or BACKFILL_DATABASE_URL is set, the post-deploy backfills take their advisory lock and run their SQL on a lazily opened direct DataSource (max 2 connections) instead of the DATABASE_URL pool; closed when done or at shutdown. The URL is never logged. - CIMD with THINKER_CIMD_ALLOW_PRIVATE=true: after an up-front DNS failure the request resolves again at connect time through a validating lookup, so the address is checked (cloud metadata refused) and pinned like the default path. Part of #74. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ocked and keep fetch causes - ProviderUrlBlockedError for the forward-time revalidation refusal and for a connect-time SsrfBlockedError (the cause of undici's "fetch failed"). It counts as a transport failure, so fallbacks run, and the response is 502 with error.code "provider_url_blocked" instead of an M500. - Other fetch failures keep a sanitized cause (message and code only). - Comment why ssrfSafeFetch casts undici's Response. - Guide: boot backfill connection and that ssrfSafeFetch ignores HTTP(S)_PROXY / NODE_USE_ENV_PROXY. Closes #74 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Full backend unit run ( |
Review: #156 fix(providers): direct-URL boot backfills, pinned CIMD fallback lookup, 502 for blocked provider URLsReviewed High risk: this PR changes the SSRF defenses of the OAuth/MCP authorization flow ( Blockers: none. The new proxy error handling ( Should-fix
Nits
Security checks
Under Head revisado: 3bcba8b |
|
Review follow-ups are tracked in #158. |
qa-docker: PR #156 (FAIL on one item)Image
Failure: the client never sees
|
…nitizer QA of #156 found that the client got the generic 502 "Upstream provider returned bad gateway" with code null: buildOpenAiCompatibleError collapses 5xx bodies in production. classifyProviderError now recognizes the gateway's provider_url_blocked 502 and keeps its type and code with a fixed message, for the OpenAI, Anthropic and Responses error shapes. - e2e (custom-provider-dns-rebinding): a blocked URL answers 502 provider_url_blocked for chat (streaming and not) and /v1/messages, and the fallback chain still serves the request. - BootBackfillConnection.close() waits for an open() in flight and destroys that pool instead of leaking it. Part of #158. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Re-review: #156 (delta since 3bcba8b)Reviewed Previous findings
New code
Nits
Blockers: none. Should-fix: none open. High risk (unchanged): this PR changes the SSRF checks in the OAuth/MCP CIMD metadata fetch and adds new DB connections from credential-bearing URLs (criterion: auth/key handling). The maintainer decides this merge. CI on 94ec3c8: all 20 checks green. qa-docker must pass again on this head, because the sanitizer change came out of a qa-docker failure. Head revisado: 94ec3c8 |
qa-docker re-check (image built from this head)Image
Response body for the blocked cases (chat): Notes: the item 4 backfill was re-triggered by deleting the Tested SHA: 94ec3c8 |
Closes #74
Item 2 (setup status probing Ollama) was done by #113 and is covered by
no-phonehome-egress.e2e-spec.ts; skipped here.What changed
BootBackfillConnection(database/backfills/boot-backfill-connection.ts): whenMIGRATION_DATABASE_URL,DATABASE_UNPOOLED_URLorBACKFILL_DATABASE_URLis set,MessageProviderBackfillBootServiceandRequestBackfillBootServicetake their session advisory lock and run their SQL on a lazily opened direct DataSource (max 2 connections) instead of theDATABASE_URLpool (PgBouncer in transaction mode). It closes when the backfill (and the tail sweep) finishes and at shutdown. Without a direct URL nothing changes. The URL is never logged.THINKER_CIMD_ALLOW_PRIVATE=true, a failed up-front lookup no longer lets the request resolve on its own: it resolves again at connect time through a validating lookup that refuses cloud-metadata answers and pins the validated address, as on the default path.ProviderClient.executeFetchkeeps a sanitized cause (message and code,key=redacted) on the error it rethrows, so a connect failure is no longer justfetch failedin the logs.provider_url_blocked. The forward-time revalidation refusal and a connect-timeSsrfBlockedError(the cause of undici'sfetch failed) becomeProviderUrlBlockedError. It is handled as a transport failure, so fallbacks run, and the response is HTTP 502 witherror.code/typeprovider_url_blockedand a message saying the provider URL was blocked by the SSRF policy, instead of the 500 internal error.ssrfSafeFetchcast is commented (undici'sResponseis not the global class; callers only use the interface).ssrfSafeFetchignoresHTTP(S)_PROXY/NODE_USE_ENV_PROXYon purpose, since a forward proxy would bypass the pinned address.Tests
boot-backfill-connection.spec.ts(precedence, lazy single open, close, no URL in logs, both services lock and run on the direct DataSource), CIMD connect-time validation and pinning, provider-client SSRF mapping and cause sanitization.npx jest src/database/backfills src/auth/cimd-client src/routing/proxy src/common/utils/ssrf: all passed; full backend run in the report.npm run test:e2e:docker -- test/custom-provider-dns-rebinding.e2e-spec.ts test/mcp-cimd-ssrf.e2e-spec.ts test/custom-providers.url-validation.e2e-spec.ts test/request-backfill-fallbacks.e2e-spec.ts test/user-provider-backfill-migrations.e2e-spec.ts: 5 suites, 51 passed.tsc -p tsconfig.test.json, eslint: clean.qa-docker: yes. Suggested: (a) a custom provider whose host resolves to a metadata address returns 502
provider_url_blocked(streaming and non-streaming); (b) boot withMIGRATION_DATABASE_URLpointing at the direct Postgres andDATABASE_URLthrough a pooler, check the backfill log line "post-deploy backfills use the direct database URL"; (c) the pinned forward with streaming through a regional override, from the issue.🤖 Generated with Claude Code