From 55b02074f2b226c581b0c7679ac1c1376676cc3b Mon Sep 17 00:00:00 2001 From: jatmn Date: Wed, 4 Nov 2026 16:54:00 -0800 Subject: [PATCH] fix(providers): bind saved Requesty credentials to a canonical saved URL The Requesty retarget boundary only checked the launch URL. A profile saved for a non-canonical Requesty-route URL and relaunched with a shell OPENAI_BASE_URL on canonical Requesty was not treated as a retarget, so its proxy key, REQUESTY_API_KEY, custom-auth trio and custom headers all reached Requesty. Profile apply already treats only a canonically saved Requesty profile's key as a Requesty key. Use the same rule on relaunch: saved credentials follow only between canonical Requesty URLs. Any other launch drops them, including the dedicated-key fallback. Explicit shell credentials still apply. Co-Authored-By: Claude Opus 5.5 --- src/utils/providerProfile.test.ts | 47 +++++++++++++++++++++++++++++++ src/utils/providerProfile.ts | 28 +++++++++++------- 2 files changed, 65 insertions(+), 10 deletions(-) diff --git a/src/utils/providerProfile.test.ts b/src/utils/providerProfile.test.ts index 8b534ef5c..4feb3c8e2 100644 --- a/src/utils/providerProfile.test.ts +++ b/src/utils/providerProfile.test.ts @@ -3943,3 +3943,50 @@ test('a shell URL retarget drops a saved Requesty profile custom auth and header assert.equal(ownProxy.OPENAI_AUTH_HEADER_VALUE, 'saved-auth-secret') assert.equal(ownProxy.ANTHROPIC_CUSTOM_HEADERS, 'X-Requesty-Auth: saved-header-secret') }) + +test('a shell URL retarget onto canonical Requesty drops a saved proxy profile credentials', async () => { + // Credentials saved for a non-canonical Requesty-route URL belong to that + // proxy, so moving the profile onto canonical Requesty is a retarget too. + const persisted = profile('openai', { + CLAUDE_CODE_PROVIDER_ROUTE_ID: 'requesty', + OPENAI_BASE_URL: 'https://requesty-proxy.example/v1', + OPENAI_MODEL: 'openai/gpt-5-mini', + OPENAI_API_KEY: 'saved-proxy-key', + REQUESTY_API_KEY: 'saved-proxy-requesty-key', + OPENAI_AUTH_HEADER: 'X-Proxy-Auth', + OPENAI_AUTH_SCHEME: 'raw', + OPENAI_AUTH_HEADER_VALUE: 'saved-proxy-secret', + ANTHROPIC_CUSTOM_HEADERS: 'X-Proxy-Auth: saved-proxy-header', + }) + + const env = await buildStartupEnvFromProfile({ + persisted, + processEnv: { OPENAI_BASE_URL: 'https://router.requesty.ai/v1' }, + }) + assert.equal(env.OPENAI_BASE_URL, 'https://router.requesty.ai/v1') + assert.equal(env.OPENAI_API_KEY, undefined) + assert.equal(env.REQUESTY_API_KEY, undefined) + assert.equal(env.OPENAI_AUTH_HEADER, undefined) + assert.equal(env.OPENAI_AUTH_SCHEME, undefined) + assert.equal(env.OPENAI_AUTH_HEADER_VALUE, undefined) + assert.equal(env.ANTHROPIC_CUSTOM_HEADERS, undefined) + assert.equal( + resolveRouteCredentialValue({ + routeId: 'requesty', + baseUrl: env.OPENAI_BASE_URL, + processEnv: env, + }), + undefined, + ) + + // An explicit shell Requesty key still authenticates the canonical launch. + const explicit = await buildStartupEnvFromProfile({ + persisted, + processEnv: { + OPENAI_BASE_URL: 'https://router.requesty.ai/v1', + REQUESTY_API_KEY: 'shell-requesty-key', + }, + }) + assert.equal(explicit.REQUESTY_API_KEY, 'shell-requesty-key') + assert.equal(explicit.OPENAI_API_KEY, undefined) +}) diff --git a/src/utils/providerProfile.ts b/src/utils/providerProfile.ts index e29d35ce7..96d97a9df 100644 --- a/src/utils/providerProfile.ts +++ b/src/utils/providerProfile.ts @@ -2260,14 +2260,19 @@ export async function buildLaunchEnv(options: { (persistedEnv.CLAUDE_CODE_PROVIDER_ROUTE_ID === 'merge-gateway' || isCanonicalMergeGatewayInferenceBaseUrl(persistedOpenAIBaseUrl)) && !isCanonicalMergeGatewayInferenceBaseUrl(env.OPENAI_BASE_URL) - // Same for a saved Requesty profile: its key is valid only on the canonical - // Requesty inference URLs, so a shell retarget elsewhere drops it. + // Same for a saved Requesty profile: its key is a Requesty key only when the + // profile was saved for a canonical Requesty inference URL, and it is valid + // only on those URLs. Any other launch drops it, including one that moves a + // profile saved for a non-canonical URL onto canonical Requesty. const persistedRequestyProfileIdentity = persistedEnv.CLAUDE_CODE_PROVIDER_ROUTE_ID === 'requesty' || isCanonicalRequestyInferenceBaseUrl(persistedOpenAIBaseUrl) const retargetedRequestyProfile = persistedRequestyProfileIdentity && - !isCanonicalRequestyInferenceBaseUrl(env.OPENAI_BASE_URL) + !( + isCanonicalRequestyInferenceBaseUrl(persistedOpenAIBaseUrl) && + isCanonicalRequestyInferenceBaseUrl(env.OPENAI_BASE_URL) + ) // A saved Runcrate profile mirrors its dedicated key into the generic // OPENAI_API_KEY alias. A shell URL retarget must not hand that saved key to // the new endpoint; only an explicit shell credential may follow the URL. @@ -2278,8 +2283,8 @@ export async function buildLaunchEnv(options: { // Requesty supports custom auth headers, so its saved credential can also // ride in the custom-auth trio or custom headers. Those belong to the // endpoint the profile was saved for: a shell retarget away from it drops - // them, while a saved non-canonical profile relaunched at its own URL keeps - // them, matching profile apply. + // them in either direction, while a saved non-canonical profile relaunched + // at its own URL keeps them, matching profile apply. const shellRetargetedRequestyProfile = retargetedRequestyProfile && !isSameConfiguredEndpoint(env.OPENAI_BASE_URL, persistedOpenAIBaseUrl) @@ -2657,12 +2662,13 @@ export async function buildLaunchEnv(options: { : undefined // Same ownership rule for a selected canonical Requesty profile, including // startup files that only persisted the saved key as OPENAI_API_KEY. Only - // a persisted Requesty profile owns that key; another profile's generic - // key is not a Requesty credential. + // a Requesty profile saved for a canonical URL owns that key; another + // profile's generic key is not a Requesty credential. const persistedRequestyProfileKey = dedicatedKey === 'REQUESTY_API_KEY' && effectiveOpenAIRouteId === 'requesty' && persistedRequestyProfileIdentity && + !retargetedRequestyProfile && !!dedicatedBaseUrl && isCanonicalRequestyInferenceBaseUrl(dedicatedBaseUrl) ? sanitizeApiKey(persistedEnv.REQUESTY_API_KEY) || @@ -2726,10 +2732,12 @@ export async function buildLaunchEnv(options: { ? sanitizeApiKey(persistedOpenAICredential.value) : undefined) : undefined - // A retargeted Runcrate launch may only use an explicit shell credential; - // the saved profile key belongs to the endpoint it was saved for. + // A retargeted Runcrate or Requesty launch may only use an explicit shell + // credential; the saved profile key belongs to the endpoint it was saved + // for. const persistedDedicatedKey = - retargetedRuncrateProfile && dedicatedKey === 'RUNCRATE_API_KEY' + (retargetedRuncrateProfile && dedicatedKey === 'RUNCRATE_API_KEY') || + (retargetedRequestyProfile && dedicatedKey === 'REQUESTY_API_KEY') ? undefined : sanitizeApiKey(persistedEnv[dedicatedKey]) const shellDedicatedKey =