@onyxsecurity/mcp-gateway 2.1.60 → 2.1.61

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. package/dist/bin/mcp-gateway.js +2 -2
  2. package/dist/{cli-D9rTurqJ.js → cli-DY9myAgd.js} +2 -2
  3. package/dist/{cli-D9rTurqJ.js.map → cli-DY9myAgd.js.map} +1 -1
  4. package/dist/{env-DEtMyxUe.js → env-CLi_cqSV.js} +2 -2
  5. package/dist/{env-DEtMyxUe.js.map → env-CLi_cqSV.js.map} +1 -1
  6. package/dist/{env-DCb5og68.js → env-Caa_Nk07.js} +1 -1
  7. package/dist/{helpers-CbGQeVsJ.js → helpers-BqX2GARv.js} +2 -2
  8. package/dist/{helpers-CbGQeVsJ.js.map → helpers-BqX2GARv.js.map} +1 -1
  9. package/dist/index.js +1 -1
  10. package/dist/{logger-B95noHdD.js → logger-DxFySFXs.js} +2 -2
  11. package/dist/{logger-B95noHdD.js.map → logger-DxFySFXs.js.map} +1 -1
  12. package/dist/{logger-Cirp4H0i.js → logger-NVfV85ZV.js} +1 -1
  13. package/dist/{main-wUiuERn-.js → main-Tws83hP-.js} +2 -2
  14. package/dist/{main-wUiuERn-.js.map → main-Tws83hP-.js.map} +1 -1
  15. package/dist/{oauthProvidersClient-DNPItcyB.js → oauthProvidersClient-B0tTiRr7.js} +2 -2
  16. package/dist/{oauthProvidersClient-DNPItcyB.js.map → oauthProvidersClient-B0tTiRr7.js.map} +1 -1
  17. package/dist/oauthProvidersClient-C6FcbvzB.js +1 -0
  18. package/dist/{providers-DijUIFfV.js → providers-BszAUoN_.js} +2 -2
  19. package/dist/{providers-DijUIFfV.js.map → providers-BszAUoN_.js.map} +1 -1
  20. package/dist/{proxyFetch-B2mideS8.js → proxyFetch-2QPdA_Ro.js} +2 -2
  21. package/dist/{proxyFetch-B2mideS8.js.map → proxyFetch-2QPdA_Ro.js.map} +1 -1
  22. package/dist/{transportDetection-BBbTAmtW.js → transportDetection-sJ8O5FAa.js} +2 -2
  23. package/dist/{transportDetection-BBbTAmtW.js.map → transportDetection-sJ8O5FAa.js.map} +1 -1
  24. package/dist/{upstreamNegotiation-Cn2P0EcF.js → upstreamNegotiation-BoBVllfs.js} +2 -2
  25. package/dist/{upstreamNegotiation-Cn2P0EcF.js.map → upstreamNegotiation-BoBVllfs.js.map} +1 -1
  26. package/package.json +2 -2
  27. package/dist/oauthProvidersClient-D_rvY3-E.js +0 -1
@@ -1,2 +1,2 @@
1
- import{proxiedFetch as e}from"./proxyFetch-B2mideS8.js";import{logger as t}from"./logger-B95noHdD.js";import{setOAuthProviders as n}from"./providers-DijUIFfV.js";async function r(t){let n=new URL(t.url),r=n.hostname===`localhost`||n.hostname===`127.0.0.1`||n.hostname===`::1`||n.hostname===`[::1]`;if(n.protocol!==`https:`&&!r)throw Error(`OAuth providers URL must use HTTPS, got ${n.protocol}`);let i=new AbortController,a=setTimeout(()=>i.abort(),t.timeoutMs);try{let n=await e(t.url,{headers:{Accept:`application/json`,apikey:t.apiKey},method:`GET`,signal:i.signal});if(!n.ok)throw Error(`OAuth providers service returned ${n.status}: ${n.statusText}`);let r=await n.json();return Array.isArray(r.providers)?r.providers:[]}catch(e){throw e instanceof Error&&e.name===`AbortError`?Error(`OAuth providers fetch timed out after ${t.timeoutMs}ms`):e}finally{clearTimeout(a)}}async function i(e){if(!e.enabled||!e.url||!e.apiKey){n([]),t.debug(`OAuth providers fetch disabled (missing URL or API key); skipping`);return}try{let i=await r({apiKey:e.apiKey,timeoutMs:e.timeoutMs,url:e.url});n(i),t.info(`OAuth providers loaded`,{count:i.length,ids:i.map(e=>e.id)})}catch(r){n([]),t.warn(`OAuth providers fetch failed; falling back to DCR`,{error:String(r),url:e.url})}}export{r as fetchOAuthProviders,i as initializeOAuthProviders};
2
- //# sourceMappingURL=oauthProvidersClient-DNPItcyB.js.map
1
+ import{proxiedFetch as e}from"./proxyFetch-2QPdA_Ro.js";import{logger as t}from"./logger-DxFySFXs.js";import{setOAuthProviders as n}from"./providers-BszAUoN_.js";async function r(t){let n=new URL(t.url),r=n.hostname===`localhost`||n.hostname===`127.0.0.1`||n.hostname===`::1`||n.hostname===`[::1]`;if(n.protocol!==`https:`&&!r)throw Error(`OAuth providers URL must use HTTPS, got ${n.protocol}`);let i=new AbortController,a=setTimeout(()=>i.abort(),t.timeoutMs);try{let n=await e(t.url,{headers:{Accept:`application/json`,apikey:t.apiKey},method:`GET`,signal:i.signal});if(!n.ok)throw Error(`OAuth providers service returned ${n.status}: ${n.statusText}`);let r=await n.json();return Array.isArray(r.providers)?r.providers:[]}catch(e){throw e instanceof Error&&e.name===`AbortError`?Error(`OAuth providers fetch timed out after ${t.timeoutMs}ms`):e}finally{clearTimeout(a)}}async function i(e){if(!e.enabled||!e.url||!e.apiKey){n([]),t.debug(`OAuth providers fetch disabled (missing URL or API key); skipping`);return}try{let i=await r({apiKey:e.apiKey,timeoutMs:e.timeoutMs,url:e.url});n(i),t.info(`OAuth providers loaded`,{count:i.length,ids:i.map(e=>e.id)})}catch(r){n([]),t.warn(`OAuth providers fetch failed; falling back to DCR`,{error:String(r),url:e.url})}}export{r as fetchOAuthProviders,i as initializeOAuthProviders};
2
+ //# sourceMappingURL=oauthProvidersClient-B0tTiRr7.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"oauthProvidersClient-DNPItcyB.js","names":[],"sources":["../src/auth/oauthProvidersClient.ts"],"sourcesContent":["import type { OAuthProviderConfig } from \"../config/config.js\";\n\nimport { logger } from \"../lib/logger.js\";\nimport { proxiedFetch } from \"../lib/proxyFetch.js\";\nimport { setOAuthProviders } from \"./providers.js\";\n\nexport interface InitializeOAuthProvidersConfig {\n apiKey: string | undefined;\n enabled: boolean;\n timeoutMs: number;\n url: null | string;\n}\n\nexport interface OAuthProvidersClientConfig {\n apiKey: string;\n timeoutMs: number;\n url: string;\n}\n\ninterface OAuthProvidersResponse {\n providers?: OAuthProviderConfig[];\n}\n\n/**\n * Fetches the OAuth provider list from the web extension service.\n *\n * The endpoint is gated by Kong's key-auth plugin, which validates the\n * `apikey` header and forwards a per-tenant `x-consumer-username` header\n * (e.g. `mcp-gateway@<tenantUUID>`) to the backend.\n *\n * Throws on network error, timeout, or non-2xx response. Callers are\n * expected to handle errors and decide on a fallback (initializeOAuthProviders\n * fails open by emptying the in-memory provider list, which causes the OAuth\n * flow to fall through to Dynamic Client Registration).\n */\nexport async function fetchOAuthProviders(\n cfg: OAuthProvidersClientConfig\n): Promise<OAuthProviderConfig[]> {\n // Reject non-HTTPS URLs before sending the API key over the wire.\n // Loopback addresses are allowed for local development against a non-TLS\n // stub: \"localhost\", IPv4 (127.0.0.1), and IPv6 (::1). Node returns the\n // bracketed form (\"[::1]\") for IPv6 hosts in URL.hostname, so match both.\n const parsed = new URL(cfg.url);\n const isLocal =\n parsed.hostname === \"localhost\" ||\n parsed.hostname === \"127.0.0.1\" ||\n parsed.hostname === \"::1\" ||\n parsed.hostname === \"[::1]\";\n if (parsed.protocol !== \"https:\" && !isLocal) {\n throw new Error(`OAuth providers URL must use HTTPS, got ${parsed.protocol}`);\n }\n\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), cfg.timeoutMs);\n\n try {\n const response = await proxiedFetch(cfg.url, {\n // GET with no body — Accept signals the desired response format,\n // Content-Type would be misleading.\n headers: {\n Accept: \"application/json\",\n apikey: cfg.apiKey,\n },\n method: \"GET\",\n signal: controller.signal,\n });\n\n if (!response.ok) {\n throw new Error(`OAuth providers service returned ${response.status}: ${response.statusText}`);\n }\n\n const body = (await response.json()) as OAuthProvidersResponse;\n return Array.isArray(body.providers) ? body.providers : [];\n } catch (error) {\n if (error instanceof Error && error.name === \"AbortError\") {\n throw new Error(`OAuth providers fetch timed out after ${cfg.timeoutMs}ms`);\n }\n throw error;\n } finally {\n clearTimeout(timer);\n }\n}\n\n/**\n * Initializes the in-memory OAuth provider store at gateway startup.\n *\n * On a successful fetch, populates the store with the providers returned by\n * the web extension service. On any failure (disabled config, network error,\n * timeout, malformed body), empties the store and logs a warning. The OAuth\n * flow at oauthFlow.ts then falls through to Dynamic Client Registration,\n * matching the gateway's \"fail-safe by default\" convention.\n */\nexport async function initializeOAuthProviders(cfg: InitializeOAuthProvidersConfig): Promise<void> {\n if (!cfg.enabled || !cfg.url || !cfg.apiKey) {\n setOAuthProviders([]);\n logger.debug(\"OAuth providers fetch disabled (missing URL or API key); skipping\");\n return;\n }\n\n try {\n const providers = await fetchOAuthProviders({\n apiKey: cfg.apiKey,\n timeoutMs: cfg.timeoutMs,\n url: cfg.url,\n });\n setOAuthProviders(providers);\n logger.info(\"OAuth providers loaded\", {\n count: providers.length,\n ids: providers.map((p) => p.id),\n });\n } catch (error) {\n setOAuthProviders([]);\n logger.warn(\"OAuth providers fetch failed; falling back to DCR\", {\n error: String(error),\n url: cfg.url,\n });\n }\n}\n"],"mappings":"kKAmCA,eAAsB,EACpB,EACgC,CAKhC,IAAM,EAAS,IAAI,IAAI,EAAI,IAAI,CACzB,EACJ,EAAO,WAAa,aACpB,EAAO,WAAa,aACpB,EAAO,WAAa,OACpB,EAAO,WAAa,QACtB,GAAI,EAAO,WAAa,UAAY,CAAC,EACnC,MAAU,MAAM,2CAA2C,EAAO,WAAW,CAG/E,IAAM,EAAa,IAAI,gBACjB,EAAQ,eAAiB,EAAW,OAAO,CAAE,EAAI,UAAU,CAEjE,GAAI,CACF,IAAM,EAAW,MAAM,EAAa,EAAI,IAAK,CAG3C,QAAS,CACP,OAAQ,mBACR,OAAQ,EAAI,OACb,CACD,OAAQ,MACR,OAAQ,EAAW,OACpB,CAAC,CAEF,GAAI,CAAC,EAAS,GACZ,MAAU,MAAM,oCAAoC,EAAS,OAAO,IAAI,EAAS,aAAa,CAGhG,IAAM,EAAQ,MAAM,EAAS,MAAM,CACnC,OAAO,MAAM,QAAQ,EAAK,UAAU,CAAG,EAAK,UAAY,EAAE,OACnD,EAAO,CAId,MAHI,aAAiB,OAAS,EAAM,OAAS,aACjC,MAAM,yCAAyC,EAAI,UAAU,IAAI,CAEvE,SACE,CACR,aAAa,EAAM,EAavB,eAAsB,EAAyB,EAAoD,CACjG,GAAI,CAAC,EAAI,SAAW,CAAC,EAAI,KAAO,CAAC,EAAI,OAAQ,CAC3C,EAAkB,EAAE,CAAC,CACrB,EAAO,MAAM,oEAAoE,CACjF,OAGF,GAAI,CACF,IAAM,EAAY,MAAM,EAAoB,CAC1C,OAAQ,EAAI,OACZ,UAAW,EAAI,UACf,IAAK,EAAI,IACV,CAAC,CACF,EAAkB,EAAU,CAC5B,EAAO,KAAK,yBAA0B,CACpC,MAAO,EAAU,OACjB,IAAK,EAAU,IAAK,GAAM,EAAE,GAAG,CAChC,CAAC,OACK,EAAO,CACd,EAAkB,EAAE,CAAC,CACrB,EAAO,KAAK,oDAAqD,CAC/D,MAAO,OAAO,EAAM,CACpB,IAAK,EAAI,IACV,CAAC"}
1
+ {"version":3,"file":"oauthProvidersClient-B0tTiRr7.js","names":[],"sources":["../src/auth/oauthProvidersClient.ts"],"sourcesContent":["import type { OAuthProviderConfig } from \"../config/config.js\";\n\nimport { logger } from \"../lib/logger.js\";\nimport { proxiedFetch } from \"../lib/proxyFetch.js\";\nimport { setOAuthProviders } from \"./providers.js\";\n\nexport interface InitializeOAuthProvidersConfig {\n apiKey: string | undefined;\n enabled: boolean;\n timeoutMs: number;\n url: null | string;\n}\n\nexport interface OAuthProvidersClientConfig {\n apiKey: string;\n timeoutMs: number;\n url: string;\n}\n\ninterface OAuthProvidersResponse {\n providers?: OAuthProviderConfig[];\n}\n\n/**\n * Fetches the OAuth provider list from the web extension service.\n *\n * The endpoint is gated by Kong's key-auth plugin, which validates the\n * `apikey` header and forwards a per-tenant `x-consumer-username` header\n * (e.g. `mcp-gateway@<tenantUUID>`) to the backend.\n *\n * Throws on network error, timeout, or non-2xx response. Callers are\n * expected to handle errors and decide on a fallback (initializeOAuthProviders\n * fails open by emptying the in-memory provider list, which causes the OAuth\n * flow to fall through to Dynamic Client Registration).\n */\nexport async function fetchOAuthProviders(\n cfg: OAuthProvidersClientConfig\n): Promise<OAuthProviderConfig[]> {\n // Reject non-HTTPS URLs before sending the API key over the wire.\n // Loopback addresses are allowed for local development against a non-TLS\n // stub: \"localhost\", IPv4 (127.0.0.1), and IPv6 (::1). Node returns the\n // bracketed form (\"[::1]\") for IPv6 hosts in URL.hostname, so match both.\n const parsed = new URL(cfg.url);\n const isLocal =\n parsed.hostname === \"localhost\" ||\n parsed.hostname === \"127.0.0.1\" ||\n parsed.hostname === \"::1\" ||\n parsed.hostname === \"[::1]\";\n if (parsed.protocol !== \"https:\" && !isLocal) {\n throw new Error(`OAuth providers URL must use HTTPS, got ${parsed.protocol}`);\n }\n\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), cfg.timeoutMs);\n\n try {\n const response = await proxiedFetch(cfg.url, {\n // GET with no body — Accept signals the desired response format,\n // Content-Type would be misleading.\n headers: {\n Accept: \"application/json\",\n apikey: cfg.apiKey,\n },\n method: \"GET\",\n signal: controller.signal,\n });\n\n if (!response.ok) {\n throw new Error(`OAuth providers service returned ${response.status}: ${response.statusText}`);\n }\n\n const body = (await response.json()) as OAuthProvidersResponse;\n return Array.isArray(body.providers) ? body.providers : [];\n } catch (error) {\n if (error instanceof Error && error.name === \"AbortError\") {\n throw new Error(`OAuth providers fetch timed out after ${cfg.timeoutMs}ms`);\n }\n throw error;\n } finally {\n clearTimeout(timer);\n }\n}\n\n/**\n * Initializes the in-memory OAuth provider store at gateway startup.\n *\n * On a successful fetch, populates the store with the providers returned by\n * the web extension service. On any failure (disabled config, network error,\n * timeout, malformed body), empties the store and logs a warning. The OAuth\n * flow at oauthFlow.ts then falls through to Dynamic Client Registration,\n * matching the gateway's \"fail-safe by default\" convention.\n */\nexport async function initializeOAuthProviders(cfg: InitializeOAuthProvidersConfig): Promise<void> {\n if (!cfg.enabled || !cfg.url || !cfg.apiKey) {\n setOAuthProviders([]);\n logger.debug(\"OAuth providers fetch disabled (missing URL or API key); skipping\");\n return;\n }\n\n try {\n const providers = await fetchOAuthProviders({\n apiKey: cfg.apiKey,\n timeoutMs: cfg.timeoutMs,\n url: cfg.url,\n });\n setOAuthProviders(providers);\n logger.info(\"OAuth providers loaded\", {\n count: providers.length,\n ids: providers.map((p) => p.id),\n });\n } catch (error) {\n setOAuthProviders([]);\n logger.warn(\"OAuth providers fetch failed; falling back to DCR\", {\n error: String(error),\n url: cfg.url,\n });\n }\n}\n"],"mappings":"kKAmCA,eAAsB,EACpB,EACgC,CAKhC,IAAM,EAAS,IAAI,IAAI,EAAI,IAAI,CACzB,EACJ,EAAO,WAAa,aACpB,EAAO,WAAa,aACpB,EAAO,WAAa,OACpB,EAAO,WAAa,QACtB,GAAI,EAAO,WAAa,UAAY,CAAC,EACnC,MAAU,MAAM,2CAA2C,EAAO,WAAW,CAG/E,IAAM,EAAa,IAAI,gBACjB,EAAQ,eAAiB,EAAW,OAAO,CAAE,EAAI,UAAU,CAEjE,GAAI,CACF,IAAM,EAAW,MAAM,EAAa,EAAI,IAAK,CAG3C,QAAS,CACP,OAAQ,mBACR,OAAQ,EAAI,OACb,CACD,OAAQ,MACR,OAAQ,EAAW,OACpB,CAAC,CAEF,GAAI,CAAC,EAAS,GACZ,MAAU,MAAM,oCAAoC,EAAS,OAAO,IAAI,EAAS,aAAa,CAGhG,IAAM,EAAQ,MAAM,EAAS,MAAM,CACnC,OAAO,MAAM,QAAQ,EAAK,UAAU,CAAG,EAAK,UAAY,EAAE,OACnD,EAAO,CAId,MAHI,aAAiB,OAAS,EAAM,OAAS,aACjC,MAAM,yCAAyC,EAAI,UAAU,IAAI,CAEvE,SACE,CACR,aAAa,EAAM,EAavB,eAAsB,EAAyB,EAAoD,CACjG,GAAI,CAAC,EAAI,SAAW,CAAC,EAAI,KAAO,CAAC,EAAI,OAAQ,CAC3C,EAAkB,EAAE,CAAC,CACrB,EAAO,MAAM,oEAAoE,CACjF,OAGF,GAAI,CACF,IAAM,EAAY,MAAM,EAAoB,CAC1C,OAAQ,EAAI,OACZ,UAAW,EAAI,UACf,IAAK,EAAI,IACV,CAAC,CACF,EAAkB,EAAU,CAC5B,EAAO,KAAK,yBAA0B,CACpC,MAAO,EAAU,OACjB,IAAK,EAAU,IAAK,GAAM,EAAE,GAAG,CAChC,CAAC,OACK,EAAO,CACd,EAAkB,EAAE,CAAC,CACrB,EAAO,KAAK,oDAAqD,CAC/D,MAAO,OAAO,EAAM,CACpB,IAAK,EAAI,IACV,CAAC"}
@@ -0,0 +1 @@
1
+ import"./proxyFetch-2QPdA_Ro.js";import"./env-CLi_cqSV.js";import"./logger-DxFySFXs.js";import"./providers-BszAUoN_.js";import{fetchOAuthProviders as e,initializeOAuthProviders as t}from"./oauthProvidersClient-B0tTiRr7.js";export{t as initializeOAuthProviders};
@@ -1,2 +1,2 @@
1
- import{logger as e}from"./logger-B95noHdD.js";let t=[];function n(n){let r=typeof n==`string`?new URL(n):n,o=r.hostname.toLowerCase();for(let n of t)for(let t of n.urlPatterns){let r=t.toLowerCase();if(a(o,r))return e.debug(`Found matching OAuth provider for URL`,{hostname:o,pattern:t,providerId:n.id,providerName:n.name}),i(n)}e.debug(`No pre-configured OAuth provider found for URL`,{hostname:o,serverUrl:r.toString()})}function r(e){t=e.map(i)}function i(e){return{...e,scopes:e.scopes?[...e.scopes]:void 0,urlPatterns:[...e.urlPatterns]}}function a(e,t){return!!(e===t||e.endsWith(`.${t}`))}export{n as findProviderForUrl,r as setOAuthProviders};
2
- //# sourceMappingURL=providers-DijUIFfV.js.map
1
+ import{logger as e}from"./logger-DxFySFXs.js";let t=[];function n(n){let r=typeof n==`string`?new URL(n):n,o=r.hostname.toLowerCase();for(let n of t)for(let t of n.urlPatterns){let r=t.toLowerCase();if(a(o,r))return e.debug(`Found matching OAuth provider for URL`,{hostname:o,pattern:t,providerId:n.id,providerName:n.name}),i(n)}e.debug(`No pre-configured OAuth provider found for URL`,{hostname:o,serverUrl:r.toString()})}function r(e){t=e.map(i)}function i(e){return{...e,scopes:e.scopes?[...e.scopes]:void 0,urlPatterns:[...e.urlPatterns]}}function a(e,t){return!!(e===t||e.endsWith(`.${t}`))}export{n as findProviderForUrl,r as setOAuthProviders};
2
+ //# sourceMappingURL=providers-BszAUoN_.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"providers-DijUIFfV.js","names":["oauthProviders: OAuthProviderConfig[]"],"sources":["../src/auth/providers.ts"],"sourcesContent":["import type { OAuthProviderConfig } from \"../config/config.js\";\n\nimport { logger } from \"../lib/logger.js\";\n\n// Mutable store. Populated by setOAuthProviders() during startup once the\n// gateway has fetched provider config from the web extension service.\n// Pre-startup or when the fetch fails, the array stays empty and OAuth flows\n// fall through to Dynamic Client Registration via oauthFlow.ts.\nlet oauthProviders: OAuthProviderConfig[] = [];\n\n/**\n * Finds a pre-configured OAuth provider that matches the given server URL.\n *\n * @param serverUrl The MCP server URL to match against provider URL patterns\n * @returns The matching provider config, or undefined if no match found\n */\nexport function findProviderForUrl(serverUrl: string | URL): OAuthProviderConfig | undefined {\n const serverUrlObj = typeof serverUrl === \"string\" ? new URL(serverUrl) : serverUrl;\n const hostname = serverUrlObj.hostname.toLowerCase();\n\n for (const provider of oauthProviders) {\n for (const pattern of provider.urlPatterns) {\n // Pattern can be a hostname or a hostname with path prefix\n const patternLower = pattern.toLowerCase();\n\n // Check if pattern matches hostname (with or without subdomains)\n if (matchesHostname(hostname, patternLower)) {\n logger.debug(\"Found matching OAuth provider for URL\", {\n hostname,\n pattern,\n providerId: provider.id,\n providerName: provider.name,\n });\n // Return a defensive clone — callers in oauthFlow.ts must not be\n // able to corrupt the in-memory store via the returned reference.\n return cloneProvider(provider);\n }\n }\n }\n\n logger.debug(\"No pre-configured OAuth provider found for URL\", {\n hostname,\n serverUrl: serverUrlObj.toString(),\n });\n return undefined;\n}\n\n/**\n * Gets a provider by its ID.\n *\n * @param providerId The provider ID (e.g., \"github\")\n * @returns The provider config, or undefined if not found\n */\nexport function getProviderById(providerId: string): OAuthProviderConfig | undefined {\n const found = oauthProviders.find((p) => p.id === providerId);\n return found ? cloneProvider(found) : undefined;\n}\n\n/**\n * Lists all configured OAuth providers.\n * Useful for debugging and admin interfaces.\n *\n * @returns Array of provider summaries (without secrets)\n */\nexport function listProviders(): Array<{\n hasClientSecret: boolean;\n id: string;\n name: string;\n urlPatterns: string[];\n}> {\n return oauthProviders.map((p) => ({\n hasClientSecret: !!p.clientSecret,\n id: p.id,\n name: p.name,\n urlPatterns: [...p.urlPatterns],\n }));\n}\n\n/**\n * Replaces the in-memory OAuth provider list. Called once at gateway startup\n * after fetching from the web extension service. Pass an empty array to clear.\n *\n * Stores defensive shallow copies of each provider so that callers cannot\n * mutate the in-memory store via the references they passed in. The\n * urlPatterns array is also cloned for the same reason.\n */\nexport function setOAuthProviders(providers: OAuthProviderConfig[]): void {\n oauthProviders = providers.map(cloneProvider);\n}\n\n// cloneProvider returns a defensive shallow copy with array fields cloned,\n// so callers can read and even mutate the result without corrupting the\n// in-memory store. Used on every read path (findProviderForUrl,\n// getProviderById) to match the write-side contract in setOAuthProviders.\nfunction cloneProvider(p: OAuthProviderConfig): OAuthProviderConfig {\n return {\n ...p,\n scopes: p.scopes ? [...p.scopes] : undefined,\n urlPatterns: [...p.urlPatterns],\n };\n}\n\n/**\n * Checks if a hostname matches a pattern.\n * Supports exact matches and subdomain matches.\n *\n * @param hostname The hostname to check (e.g., \"api.github.com\")\n * @param pattern The pattern to match against (e.g., \"github.com\")\n * @returns true if the hostname matches the pattern\n */\nfunction matchesHostname(hostname: string, pattern: string): boolean {\n // Exact match\n if (hostname === pattern) {\n return true;\n }\n\n // Subdomain match: hostname ends with \".pattern\"\n // e.g., \"api.github.com\" matches \"github.com\"\n if (hostname.endsWith(`.${pattern}`)) {\n return true;\n }\n\n return false;\n}\n"],"mappings":"8CAQA,IAAIA,EAAwC,EAAE,CAQ9C,SAAgB,EAAmB,EAA0D,CAC3F,IAAM,EAAe,OAAO,GAAc,SAAW,IAAI,IAAI,EAAU,CAAG,EACpE,EAAW,EAAa,SAAS,aAAa,CAEpD,IAAK,IAAM,KAAY,EACrB,IAAK,IAAM,KAAW,EAAS,YAAa,CAE1C,IAAM,EAAe,EAAQ,aAAa,CAG1C,GAAI,EAAgB,EAAU,EAAa,CASzC,OARA,EAAO,MAAM,wCAAyC,CACpD,WACA,UACA,WAAY,EAAS,GACrB,aAAc,EAAS,KACxB,CAAC,CAGK,EAAc,EAAS,CAKpC,EAAO,MAAM,iDAAkD,CAC7D,WACA,UAAW,EAAa,UAAU,CACnC,CAAC,CA2CJ,SAAgB,EAAkB,EAAwC,CACxE,EAAiB,EAAU,IAAI,EAAc,CAO/C,SAAS,EAAc,EAA6C,CAClE,MAAO,CACL,GAAG,EACH,OAAQ,EAAE,OAAS,CAAC,GAAG,EAAE,OAAO,CAAG,IAAA,GACnC,YAAa,CAAC,GAAG,EAAE,YAAY,CAChC,CAWH,SAAS,EAAgB,EAAkB,EAA0B,CAYnE,MAJA,GANI,IAAa,GAMb,EAAS,SAAS,IAAI,IAAU"}
1
+ {"version":3,"file":"providers-BszAUoN_.js","names":["oauthProviders: OAuthProviderConfig[]"],"sources":["../src/auth/providers.ts"],"sourcesContent":["import type { OAuthProviderConfig } from \"../config/config.js\";\n\nimport { logger } from \"../lib/logger.js\";\n\n// Mutable store. Populated by setOAuthProviders() during startup once the\n// gateway has fetched provider config from the web extension service.\n// Pre-startup or when the fetch fails, the array stays empty and OAuth flows\n// fall through to Dynamic Client Registration via oauthFlow.ts.\nlet oauthProviders: OAuthProviderConfig[] = [];\n\n/**\n * Finds a pre-configured OAuth provider that matches the given server URL.\n *\n * @param serverUrl The MCP server URL to match against provider URL patterns\n * @returns The matching provider config, or undefined if no match found\n */\nexport function findProviderForUrl(serverUrl: string | URL): OAuthProviderConfig | undefined {\n const serverUrlObj = typeof serverUrl === \"string\" ? new URL(serverUrl) : serverUrl;\n const hostname = serverUrlObj.hostname.toLowerCase();\n\n for (const provider of oauthProviders) {\n for (const pattern of provider.urlPatterns) {\n // Pattern can be a hostname or a hostname with path prefix\n const patternLower = pattern.toLowerCase();\n\n // Check if pattern matches hostname (with or without subdomains)\n if (matchesHostname(hostname, patternLower)) {\n logger.debug(\"Found matching OAuth provider for URL\", {\n hostname,\n pattern,\n providerId: provider.id,\n providerName: provider.name,\n });\n // Return a defensive clone — callers in oauthFlow.ts must not be\n // able to corrupt the in-memory store via the returned reference.\n return cloneProvider(provider);\n }\n }\n }\n\n logger.debug(\"No pre-configured OAuth provider found for URL\", {\n hostname,\n serverUrl: serverUrlObj.toString(),\n });\n return undefined;\n}\n\n/**\n * Gets a provider by its ID.\n *\n * @param providerId The provider ID (e.g., \"github\")\n * @returns The provider config, or undefined if not found\n */\nexport function getProviderById(providerId: string): OAuthProviderConfig | undefined {\n const found = oauthProviders.find((p) => p.id === providerId);\n return found ? cloneProvider(found) : undefined;\n}\n\n/**\n * Lists all configured OAuth providers.\n * Useful for debugging and admin interfaces.\n *\n * @returns Array of provider summaries (without secrets)\n */\nexport function listProviders(): Array<{\n hasClientSecret: boolean;\n id: string;\n name: string;\n urlPatterns: string[];\n}> {\n return oauthProviders.map((p) => ({\n hasClientSecret: !!p.clientSecret,\n id: p.id,\n name: p.name,\n urlPatterns: [...p.urlPatterns],\n }));\n}\n\n/**\n * Replaces the in-memory OAuth provider list. Called once at gateway startup\n * after fetching from the web extension service. Pass an empty array to clear.\n *\n * Stores defensive shallow copies of each provider so that callers cannot\n * mutate the in-memory store via the references they passed in. The\n * urlPatterns array is also cloned for the same reason.\n */\nexport function setOAuthProviders(providers: OAuthProviderConfig[]): void {\n oauthProviders = providers.map(cloneProvider);\n}\n\n// cloneProvider returns a defensive shallow copy with array fields cloned,\n// so callers can read and even mutate the result without corrupting the\n// in-memory store. Used on every read path (findProviderForUrl,\n// getProviderById) to match the write-side contract in setOAuthProviders.\nfunction cloneProvider(p: OAuthProviderConfig): OAuthProviderConfig {\n return {\n ...p,\n scopes: p.scopes ? [...p.scopes] : undefined,\n urlPatterns: [...p.urlPatterns],\n };\n}\n\n/**\n * Checks if a hostname matches a pattern.\n * Supports exact matches and subdomain matches.\n *\n * @param hostname The hostname to check (e.g., \"api.github.com\")\n * @param pattern The pattern to match against (e.g., \"github.com\")\n * @returns true if the hostname matches the pattern\n */\nfunction matchesHostname(hostname: string, pattern: string): boolean {\n // Exact match\n if (hostname === pattern) {\n return true;\n }\n\n // Subdomain match: hostname ends with \".pattern\"\n // e.g., \"api.github.com\" matches \"github.com\"\n if (hostname.endsWith(`.${pattern}`)) {\n return true;\n }\n\n return false;\n}\n"],"mappings":"8CAQA,IAAIA,EAAwC,EAAE,CAQ9C,SAAgB,EAAmB,EAA0D,CAC3F,IAAM,EAAe,OAAO,GAAc,SAAW,IAAI,IAAI,EAAU,CAAG,EACpE,EAAW,EAAa,SAAS,aAAa,CAEpD,IAAK,IAAM,KAAY,EACrB,IAAK,IAAM,KAAW,EAAS,YAAa,CAE1C,IAAM,EAAe,EAAQ,aAAa,CAG1C,GAAI,EAAgB,EAAU,EAAa,CASzC,OARA,EAAO,MAAM,wCAAyC,CACpD,WACA,UACA,WAAY,EAAS,GACrB,aAAc,EAAS,KACxB,CAAC,CAGK,EAAc,EAAS,CAKpC,EAAO,MAAM,iDAAkD,CAC7D,WACA,UAAW,EAAa,UAAU,CACnC,CAAC,CA2CJ,SAAgB,EAAkB,EAAwC,CACxE,EAAiB,EAAU,IAAI,EAAc,CAO/C,SAAS,EAAc,EAA6C,CAClE,MAAO,CACL,GAAG,EACH,OAAQ,EAAE,OAAS,CAAC,GAAG,EAAE,OAAO,CAAG,IAAA,GACnC,YAAa,CAAC,GAAG,EAAE,YAAY,CAChC,CAWH,SAAS,EAAgB,EAAkB,EAA0B,CAYnE,MAJA,GANI,IAAa,GAMb,EAAS,SAAS,IAAI,IAAU"}
@@ -1,2 +1,2 @@
1
- import{createRequire as e}from"node:module";import{isIP as t}from"node:net";var n=Object.create,r=Object.defineProperty,i=Object.getOwnPropertyDescriptor,a=Object.getOwnPropertyNames,o=Object.getPrototypeOf,s=Object.prototype.hasOwnProperty,c=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports),l=(e,t,n,o)=>{if(t&&typeof t==`object`||typeof t==`function`)for(var c=a(t),l=0,u=c.length,d;l<u;l++)d=c[l],!s.call(e,d)&&d!==n&&r(e,d,{get:(e=>t[e]).bind(null,d),enumerable:!(o=i(t,d))||o.enumerable});return e},u=(e,t,i)=>(i=e==null?{}:n(o(e)),l(t||!e||!e.__esModule?r(i,`default`,{value:e,enumerable:!0}):i,e)),d=e(import.meta.url);function f(){return globalThis.Bun!==void 0||typeof process<`u`&&typeof process.versions?.bun==`string`?`bun`:`node`}const p=f(),m=()=>import(`undici`);let h=p,g=m;const _=new Map;async function v(e,t={}){let n=t.timeoutMs??x,r=t.fetchImpl??globalThis.fetch,i=K(e);if(!J(i)||$(i))return`indeterminate`;let a=new AbortController,o,s=new Promise(e=>{o=setTimeout(()=>{a.abort(),e(`indeterminate`)},n)}),c=(async()=>{try{let e=await y({method:`HEAD`,signal:a.signal},i);return await r(S,e),`reachable`}catch(e){return a.signal.aborted?`indeterminate`:R(e)?`unreachable`:`reachable`}})();try{return await Promise.race([c,s])}finally{clearTimeout(o)}}async function y(e,t,n=h){if(n===`bun`)return{...e,proxy:t};let r=await b(t);return{...e,dispatcher:r}}function b(e){let t=_.get(e);if(t)return t;let n=g().then(({ProxyAgent:t})=>new t(e));return _.set(e,n),n.catch(()=>_.delete(e)),n}const x=2e3,S=`http://onyx-proxy-probe.invalid/`;let C={enabled:!1,noProxy:[]};function w(e){C=e,T.clear(),_.clear()}const T=new Set;function E(e){if(!e)return[];let n=[];for(let r of e.split(`,`)){let e=r.trim().toLowerCase();if(!e)continue;if(e===`*`)return[{kind:`wildcard`}];if(e.includes(`/`)){let t=q(e);t&&n.push(t);continue}let{host:i,port:a}=X(e);if(!i)continue;if(t(i)!==0){n.push({kind:`ip`,value:W(i),...a?{port:a}:{}});continue}let o=i,s;o.startsWith(`*.`)?(o=o.slice(1),s=!1):s=!o.startsWith(`.`);let c=o.startsWith(`.`)?o.slice(1):o;c&&n.push({kind:`domain`,matchHost:s,suffix:c,...a?{port:a}:{}})}return n}function D(e){return e instanceof Error?e.message:String(e)}function O(e,t,n){if(!k(`fallback:${e}`))return;let r=I(n).join(`,`)||D(n);M().then(({logger:i})=>{i.warn(`Configured proxy is unreachable; routing this egress DIRECT`,{error:D(n),event:`proxy_fallback`,op:`fetch`,outcome:`direct`,platform:process.platform,proxy_host:e,reason:r,target_host:t})}).catch(()=>{T.delete(`fallback:${e}`)})}function k(e){return T.has(e)?!1:(T.add(e),!0)}let A;function j(e,t){return t?.signal?.aborted||e instanceof DOMException&&e.name===`AbortError`?!0:e?.name===`AbortError`}function M(){return A??=import(`./logger-Cirp4H0i.js`).catch(e=>{throw A=void 0,e}),A}function N(e,t,n){let r=I(n).join(`,`)||D(n);k(`nofallback:${e}|${t}|${r}`)&&M().then(({logger:i})=>{i.warn(`Proxied request failed and was NOT retried direct; egress stays fail-closed`,{error:D(n),event:`proxy_no_fallback`,op:`fetch`,outcome:`failed`,platform:process.platform,proxy_host:e,reason:r,target_host:t})}).catch(()=>{T.delete(`nofallback:${e}|${t}|${r}`)})}function P(e,t){k(`selected:${e}`)&&M().then(({logger:n})=>{n.info(`Egress is routed through the configured proxy`,{event:`proxy_selected`,op:`fetch`,outcome:`proxied`,platform:process.platform,proxy_host:e,source:t??``})}).catch(()=>{T.delete(`selected:${e}`)})}const F=(async(e,t)=>{if(!C.enabled)return globalThis.fetch(e,t);let n;try{n=Q(e)}catch{return globalThis.fetch(e,t)}let r=Y(n);if(!r)return globalThis.fetch(e,t);let i=J(r);if(!i)throw Error(`Configured proxy URL has no parseable host; refusing to route direct around the egress proxy`);let a=$(r);if(a)throw Error(`Configured proxy URL uses an unsupported proxy scheme "${a}"; refusing to route direct around the egress proxy`);P(i,C.source);let o=await y(t,r);try{return await globalThis.fetch(e,o)}catch(r){if(!R(r))throw j(r,t)||N(i,n.hostname,r),r;if(t?.signal?.aborted)throw r;return O(i,n.hostname,r),globalThis.fetch(e,t)}});function I(e){let t=[],n=new Set,r=[{depth:0,node:e}];for(let e=0;r.length>0&&e<32;){let{depth:i,node:a}=r.shift();if(typeof a!=`object`||!a||n.has(a))continue;n.add(a),e++;let{cause:o,code:s,errors:c}=a;if(typeof s==`string`&&t.push(s),!(i+1>=6)&&(o!=null&&r.push({depth:i+1,node:o}),Array.isArray(c)))for(let e of c)r.push({depth:i+1,node:e})}return t}const L=new Set([`ConnectionRefused`,`EAI_AGAIN`,`EAI_NODATA`,`EAI_NONAME`,`ECONNREFUSED`,`EHOSTUNREACH`,`ENETUNREACH`,`ENOTFOUND`,`ETIMEDOUT`,`FailedToOpenSocket`,`UND_ERR_CONNECT_TIMEOUT`]);function R(e){return e==null||e instanceof DOMException&&e.name===`AbortError`||e.name===`AbortError`?!1:I(e).some(e=>L.has(e))}function z(e){let t=e.split(`.`);return(Number(t[0])<<24>>>0)+(Number(t[1])<<16)+(Number(t[2])<<8)+Number(t[3])>>>0}function B(e){let t=Z(e.toLowerCase()),n=t.indexOf(`%`);if(n!==-1&&(t=t.slice(0,n)),t.includes(`.`)){let e=t.lastIndexOf(`:`);if(e===-1)return;let n=t.slice(e+1).split(`.`).map(Number);if(n.length!==4||n.some(e=>!Number.isInteger(e)||e<0||e>255))return;let r=(n[0]<<8|n[1]).toString(16),i=(n[2]<<8|n[3]).toString(16);t=`${t.slice(0,e+1)}${r}:${i}`}let r=t.split(`::`);if(r.length>2)return;let i=r[0]===``?[]:r[0].split(`:`),a=r.length===2&&r[1]!==``?r[1].split(`:`):[],o;if(r.length===2){let e=8-i.length-a.length;if(e<0)return;o=[...i,...Array(e).fill(`0`),...a]}else o=i;if(o.length!==8)return;let s=0n;for(let e of o){if(!/^[0-9a-f]{1,4}$/.test(e))return;s=s<<16n|BigInt(parseInt(e,16))}return s}function V(e){let n=Z(e.toLowerCase());if(n===`localhost`)return!0;let r=t(n);return r===4?n===`127.0.0.1`||n.startsWith(`127.`):r===6?n===`::1`:!1}function H(e){return e===0?0:4294967295<<32-e>>>0}function U(e,n){let r=Z(e.toLowerCase()),i=t(r),a=i===0?void 0:W(r),o=i===4?z(r):void 0,s=i===6?B(r):void 0;for(let e of C.noProxy)switch(e.kind){case`cidr`:if(o!==void 0&&(o&H(e.bits))>>>0===e.base)return!0;break;case`cidr6`:if(s!==void 0){let t=BigInt(128-e.bits);if(s>>t===e.base>>t)return!0}break;case`domain`:if((r.endsWith(`.${e.suffix}`)||e.matchHost&&r===e.suffix)&&(e.port===void 0||e.port===n))return!0;break;case`ip`:if(a!==void 0&&a===e.value&&(e.port===void 0||e.port===n))return!0;break;case`wildcard`:return!0}return!1}function W(e){return Z(e).toLowerCase()}const G=new Set([`http:`,`https:`,`socks5:`,`socks5h:`]);function K(e){try{if(new URL(e).hostname)return e}catch{}try{if(new URL(`http://${e}`).hostname)return`http://${e}`}catch{}return e}function q(e){let n=e.indexOf(`/`);if(n<0)return;let r=e.slice(0,n),i=e.slice(n+1);if(!/^\d+$/.test(i))return;let a=Number(i);if(!Number.isInteger(a)||a<0)return;let o=t(r);if(o===4){if(a>32)return;let e=H(a);return{base:(z(r)&e)>>>0,bits:a,kind:`cidr`}}if(o===6){if(a>128)return;let e=B(r);if(e===void 0)return;let t=BigInt(128-a);return{base:e>>t<<t,bits:a,kind:`cidr6`}}}function J(e){try{return Z(new URL(e).hostname)}catch{return``}}function Y(e){let t=e.protocol===`https:`&&C.httpsProxy||C.httpProxy;if(!t||V(e.hostname))return;let n=e.port||(e.protocol===`https:`?`443`:`80`);if(!U(e.hostname,n))return K(t)}function X(e){if(e.startsWith(`[`)){let t=e.indexOf(`]`);if(t===-1)return{host:e,port:``};let n=e.slice(1,t),r=e.slice(t+1);return{host:n,port:r.startsWith(`:`)?r.slice(1):``}}let t=e.indexOf(`:`);return t!==-1&&t===e.lastIndexOf(`:`)?{host:e.slice(0,t),port:e.slice(t+1)}:{host:e,port:``}}function Z(e){return e.startsWith(`[`)&&e.endsWith(`]`)?e.slice(1,-1):e}function Q(e){return e instanceof URL?e:typeof e==`string`?new URL(e):new URL(e.url)}function $(e){try{let{protocol:t}=new URL(e);return G.has(t)?void 0:t}catch{return}}export{c as __commonJSMin,d as __require,u as __toESM,w as configureProxy,E as parseNoProxy,v as probeProxyReachable,F as proxiedFetch};
2
- //# sourceMappingURL=proxyFetch-B2mideS8.js.map
1
+ import{createRequire as e}from"node:module";import{isIP as t}from"node:net";var n=Object.create,r=Object.defineProperty,i=Object.getOwnPropertyDescriptor,a=Object.getOwnPropertyNames,o=Object.getPrototypeOf,s=Object.prototype.hasOwnProperty,c=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports),l=(e,t,n,o)=>{if(t&&typeof t==`object`||typeof t==`function`)for(var c=a(t),l=0,u=c.length,d;l<u;l++)d=c[l],!s.call(e,d)&&d!==n&&r(e,d,{get:(e=>t[e]).bind(null,d),enumerable:!(o=i(t,d))||o.enumerable});return e},u=(e,t,i)=>(i=e==null?{}:n(o(e)),l(t||!e||!e.__esModule?r(i,`default`,{value:e,enumerable:!0}):i,e)),d=e(import.meta.url);function f(){return globalThis.Bun!==void 0||typeof process<`u`&&typeof process.versions?.bun==`string`?`bun`:`node`}const p=f(),m=()=>import(`undici`);let h=p,g=m;const _=new Map;async function v(e,t={}){let n=t.timeoutMs??x,r=t.fetchImpl??globalThis.fetch,i=K(e);if(!J(i)||$(i))return`indeterminate`;let a=new AbortController,o,s=new Promise(e=>{o=setTimeout(()=>{a.abort(),e(`indeterminate`)},n)}),c=(async()=>{try{let e=await y({method:`HEAD`,signal:a.signal},i);return await r(S,e),`reachable`}catch(e){return a.signal.aborted?`indeterminate`:R(e)?`unreachable`:`reachable`}})();try{return await Promise.race([c,s])}finally{clearTimeout(o)}}async function y(e,t,n=h){if(n===`bun`)return{...e,proxy:t};let r=await b(t);return{...e,dispatcher:r}}function b(e){let t=_.get(e);if(t)return t;let n=g().then(({ProxyAgent:t})=>new t(e));return _.set(e,n),n.catch(()=>_.delete(e)),n}const x=2e3,S=`http://onyx-proxy-probe.invalid/`;let C={enabled:!1,noProxy:[]};function w(e){C=e,T.clear(),_.clear()}const T=new Set;function E(e){if(!e)return[];let n=[];for(let r of e.split(`,`)){let e=r.trim().toLowerCase();if(!e)continue;if(e===`*`)return[{kind:`wildcard`}];if(e.includes(`/`)){let t=q(e);t&&n.push(t);continue}let{host:i,port:a}=X(e);if(!i)continue;if(t(i)!==0){n.push({kind:`ip`,value:W(i),...a?{port:a}:{}});continue}let o=i,s;o.startsWith(`*.`)?(o=o.slice(1),s=!1):s=!o.startsWith(`.`);let c=o.startsWith(`.`)?o.slice(1):o;c&&n.push({kind:`domain`,matchHost:s,suffix:c,...a?{port:a}:{}})}return n}function D(e){return e instanceof Error?e.message:String(e)}function O(e,t,n){if(!k(`fallback:${e}`))return;let r=I(n).join(`,`)||D(n);M().then(({logger:i})=>{i.warn(`Configured proxy is unreachable; routing this egress DIRECT`,{error:D(n),event:`proxy_fallback`,op:`fetch`,outcome:`direct`,platform:process.platform,proxy_host:e,reason:r,target_host:t})}).catch(()=>{T.delete(`fallback:${e}`)})}function k(e){return T.has(e)?!1:(T.add(e),!0)}let A;function j(e,t){return t?.signal?.aborted||e instanceof DOMException&&e.name===`AbortError`?!0:e?.name===`AbortError`}function M(){return A??=import(`./logger-NVfV85ZV.js`).catch(e=>{throw A=void 0,e}),A}function N(e,t,n){let r=I(n).join(`,`)||D(n);k(`nofallback:${e}|${t}|${r}`)&&M().then(({logger:i})=>{i.warn(`Proxied request failed and was NOT retried direct; egress stays fail-closed`,{error:D(n),event:`proxy_no_fallback`,op:`fetch`,outcome:`failed`,platform:process.platform,proxy_host:e,reason:r,target_host:t})}).catch(()=>{T.delete(`nofallback:${e}|${t}|${r}`)})}function P(e,t){k(`selected:${e}`)&&M().then(({logger:n})=>{n.info(`Egress is routed through the configured proxy`,{event:`proxy_selected`,op:`fetch`,outcome:`proxied`,platform:process.platform,proxy_host:e,source:t??``})}).catch(()=>{T.delete(`selected:${e}`)})}const F=(async(e,t)=>{if(!C.enabled)return globalThis.fetch(e,t);let n;try{n=Q(e)}catch{return globalThis.fetch(e,t)}let r=Y(n);if(!r)return globalThis.fetch(e,t);let i=J(r);if(!i)throw Error(`Configured proxy URL has no parseable host; refusing to route direct around the egress proxy`);let a=$(r);if(a)throw Error(`Configured proxy URL uses an unsupported proxy scheme "${a}"; refusing to route direct around the egress proxy`);P(i,C.source);let o=await y(t,r);try{return await globalThis.fetch(e,o)}catch(r){if(!R(r))throw j(r,t)||N(i,n.hostname,r),r;if(t?.signal?.aborted)throw r;return O(i,n.hostname,r),globalThis.fetch(e,t)}});function I(e){let t=[],n=new Set,r=[{depth:0,node:e}];for(let e=0;r.length>0&&e<32;){let{depth:i,node:a}=r.shift();if(typeof a!=`object`||!a||n.has(a))continue;n.add(a),e++;let{cause:o,code:s,errors:c}=a;if(typeof s==`string`&&t.push(s),!(i+1>=6)&&(o!=null&&r.push({depth:i+1,node:o}),Array.isArray(c)))for(let e of c)r.push({depth:i+1,node:e})}return t}const L=new Set([`ConnectionRefused`,`EAI_AGAIN`,`EAI_NODATA`,`EAI_NONAME`,`ECONNREFUSED`,`EHOSTUNREACH`,`ENETUNREACH`,`ENOTFOUND`,`ETIMEDOUT`,`FailedToOpenSocket`,`UND_ERR_CONNECT_TIMEOUT`]);function R(e){return e==null||e instanceof DOMException&&e.name===`AbortError`||e.name===`AbortError`?!1:I(e).some(e=>L.has(e))}function z(e){let t=e.split(`.`);return(Number(t[0])<<24>>>0)+(Number(t[1])<<16)+(Number(t[2])<<8)+Number(t[3])>>>0}function B(e){let t=Z(e.toLowerCase()),n=t.indexOf(`%`);if(n!==-1&&(t=t.slice(0,n)),t.includes(`.`)){let e=t.lastIndexOf(`:`);if(e===-1)return;let n=t.slice(e+1).split(`.`).map(Number);if(n.length!==4||n.some(e=>!Number.isInteger(e)||e<0||e>255))return;let r=(n[0]<<8|n[1]).toString(16),i=(n[2]<<8|n[3]).toString(16);t=`${t.slice(0,e+1)}${r}:${i}`}let r=t.split(`::`);if(r.length>2)return;let i=r[0]===``?[]:r[0].split(`:`),a=r.length===2&&r[1]!==``?r[1].split(`:`):[],o;if(r.length===2){let e=8-i.length-a.length;if(e<0)return;o=[...i,...Array(e).fill(`0`),...a]}else o=i;if(o.length!==8)return;let s=0n;for(let e of o){if(!/^[0-9a-f]{1,4}$/.test(e))return;s=s<<16n|BigInt(parseInt(e,16))}return s}function V(e){let n=Z(e.toLowerCase());if(n===`localhost`)return!0;let r=t(n);return r===4?n===`127.0.0.1`||n.startsWith(`127.`):r===6?n===`::1`:!1}function H(e){return e===0?0:4294967295<<32-e>>>0}function U(e,n){let r=Z(e.toLowerCase()),i=t(r),a=i===0?void 0:W(r),o=i===4?z(r):void 0,s=i===6?B(r):void 0;for(let e of C.noProxy)switch(e.kind){case`cidr`:if(o!==void 0&&(o&H(e.bits))>>>0===e.base)return!0;break;case`cidr6`:if(s!==void 0){let t=BigInt(128-e.bits);if(s>>t===e.base>>t)return!0}break;case`domain`:if((r.endsWith(`.${e.suffix}`)||e.matchHost&&r===e.suffix)&&(e.port===void 0||e.port===n))return!0;break;case`ip`:if(a!==void 0&&a===e.value&&(e.port===void 0||e.port===n))return!0;break;case`wildcard`:return!0}return!1}function W(e){return Z(e).toLowerCase()}const G=new Set([`http:`,`https:`,`socks5:`,`socks5h:`]);function K(e){try{if(new URL(e).hostname)return e}catch{}try{if(new URL(`http://${e}`).hostname)return`http://${e}`}catch{}return e}function q(e){let n=e.indexOf(`/`);if(n<0)return;let r=e.slice(0,n),i=e.slice(n+1);if(!/^\d+$/.test(i))return;let a=Number(i);if(!Number.isInteger(a)||a<0)return;let o=t(r);if(o===4){if(a>32)return;let e=H(a);return{base:(z(r)&e)>>>0,bits:a,kind:`cidr`}}if(o===6){if(a>128)return;let e=B(r);if(e===void 0)return;let t=BigInt(128-a);return{base:e>>t<<t,bits:a,kind:`cidr6`}}}function J(e){try{return Z(new URL(e).hostname)}catch{return``}}function Y(e){let t=e.protocol===`https:`&&C.httpsProxy||C.httpProxy;if(!t||V(e.hostname))return;let n=e.port||(e.protocol===`https:`?`443`:`80`);if(!U(e.hostname,n))return K(t)}function X(e){if(e.startsWith(`[`)){let t=e.indexOf(`]`);if(t===-1)return{host:e,port:``};let n=e.slice(1,t),r=e.slice(t+1);return{host:n,port:r.startsWith(`:`)?r.slice(1):``}}let t=e.indexOf(`:`);return t!==-1&&t===e.lastIndexOf(`:`)?{host:e.slice(0,t),port:e.slice(t+1)}:{host:e,port:``}}function Z(e){return e.startsWith(`[`)&&e.endsWith(`]`)?e.slice(1,-1):e}function Q(e){return e instanceof URL?e:typeof e==`string`?new URL(e):new URL(e.url)}function $(e){try{let{protocol:t}=new URL(e);return G.has(t)?void 0:t}catch{return}}export{c as __commonJSMin,d as __require,u as __toESM,w as configureProxy,E as parseNoProxy,v as probeProxyReachable,F as proxiedFetch};
2
+ //# sourceMappingURL=proxyFetch-2QPdA_Ro.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"proxyFetch-B2mideS8.js","names":["DETECTED_RUNTIME: ProxyRuntimeKind","runtimeKind: ProxyRuntimeKind","loadUndici: () => Promise<UndiciProxyModule>","timer: ReturnType<typeof setTimeout> | undefined","config: ProxyRuntimeConfig","rules: NoProxyRule[]","matchHost: boolean","loggerModule: Promise<{ logger: ProxyFetchLogger }> | undefined","target: URL","codes: string[]","queue: { depth: number; node: unknown }[]","groups: string[]"],"sources":["../src/lib/proxyFetch.ts"],"sourcesContent":["/**\n * The single, shared, proxy-honoring `fetch` for the whole gateway.\n *\n * THE GATEWAY SHIPS TWO WAYS, AND EACH RUNTIME PROXIES DIFFERENTLY:\n * - a `bun build --compile` single-file binary, and\n * - an npx/Node package (`engines.node >= 24`, `bin` -> dist/bin/mcp-gateway.js).\n * There is NO single option both runtimes honour, so routing is decided once\n * at load (`detectProxyRuntime`) and applied in exactly one place\n * (`buildProxyInit`). Nothing else in the request path asks which runtime it\n * is on.\n *\n * WHY NOT undici's `setGlobalDispatcher`:\n * Under Bun, `globalThis.fetch` is Bun-native and does NOT consult undici's\n * `setGlobalDispatcher(...)`. The approach this module replaced installed a\n * global `EnvHttpProxyAgent`, which is a silent no-op in the shipped Bun\n * binary — so no egress was actually proxied there. That reasoning still\n * holds, and it is specifically about the GLOBAL dispatcher. It does NOT mean\n * the Node path needs nothing: Node's `fetch` ignores Bun's per-request\n * `proxy` option just as completely (measured on Node 24.18 — the request\n * reached the origin and the proxy listener saw no connection at all), and\n * startup strips the proxy env vars, so undici has no env proxy to fall back\n * on either. A Bun-only mechanism therefore leaves every npx call DIRECT\n * while reporting a configured proxy. Node is handled with an EXPLICIT\n * PER-REQUEST `dispatcher` instead (`ProxyAgent`, one cached instance per\n * proxy URL — see `nodeDispatcherFor`), which undici's fetch does honour,\n * verified against a real CONNECT listener rather than assumed.\n *\n * HOW ROUTING IS CONTROLLED UNDER BUN (Bun-native, empirically verified on Bun\n * 1.3.14 — the version embedded in the shipped linux binary — and 1.4.0):\n * - Bun snapshots HTTP(S)_PROXY / NO_PROXY from the OS environment at process\n * startup. Mutating `process.env` at runtime does NOT change that snapshot,\n * and there is no option value that forces a DIRECT connection while an env\n * proxy is snapshotted.\n * - Bun DOES honor a per-request `proxy` option that overrides the env\n * snapshot, for both https (CONNECT) and http (absolute-form GET) targets,\n * including credentials in the proxy URL (Proxy-Authorization).\n * Therefore the ONLY way to get deterministic DIRECT connections (needed for\n * NO_PROXY bypass and the direct fallback) is to launch the real gateway\n * with the standard proxy env vars STRIPPED from the OS environment, so Bun's\n * snapshot is empty, and then decide routing per request by passing (or not\n * passing) an explicit `proxy` option. That stripping + re-exec is done once\n * at startup by `setupProxyDispatcher()` in `./proxySetup.ts`; this module is\n * the request-time half that reads the captured config and applies it.\n * The same stripping serves the Node path: it is what makes \"no dispatcher\"\n * mean a genuinely direct connection, with nothing left for Node's opt-in\n * env-proxy support to read.\n *\n * The routing policy mirrors the scanner's Go helper\n * (`scanner/internal/proxyhttp/proxyhttp.go`) so every Onyx egress surface\n * behaves identically:\n * 1. https targets use HTTPS_PROXY (falling back to HTTP_PROXY when unset);\n * http targets use HTTP_PROXY. Upper- and lower-case env vars both count,\n * upper-case taking precedence.\n * 2. NO_PROXY is honored with the same rule grammar as httpproxy: a bare\n * domain (`walmart.com`) matches the domain AND its subdomains, while a\n * leading-dot / `*.` entry matches subdomains only; entries may carry a\n * literal port; plus IPv4 CIDR, IPv6 CIDR, single-IP, and `*` rules.\n * 3. A direct fallback for an UNREACHABLE proxy: the request is attempted\n * through the proxy, and if that attempt fails before reaching any server\n * — the proxy refuses the connection, its host does not resolve, the\n * connect times out — the SAME request is retried once, directly. A proxy\n * that ANSWERS is never bypassed, on either runtime, though they report it\n * differently: Bun surfaces a 407/403/502 from the proxy as an ordinary\n * Response, so the verdict reaches the caller untouched, while undici\n * REJECTS with `UND_ERR_ABORTED` (\"Proxy response (407) !== 200 when HTTP\n * Tunneling\"), which the allow-list below deliberately excludes so it\n * fails instead of retrying around. A connection the proxy accepted and\n * then broke (ECONNRESET) is outside the retry set for the same reason —\n * the request may already have been written to it.\n */\n\nimport { isIP } from \"node:net\";\n\n/** Which per-request mechanism the host runtime actually honours. */\n/**\n * What one bounded attempt through a proxy established. \"indeterminate\" is a\n * first-class outcome: see probeProxyReachable.\n */\nexport type ProxyReachability = \"indeterminate\" | \"reachable\" | \"unreachable\";\n\nexport interface ProxyRuntimeConfig {\n /** Whether any proxy is configured. When false, every request goes direct. */\n enabled: boolean;\n /** Raw HTTP_PROXY URL (may carry credentials). Used for http targets. */\n httpProxy?: string;\n /** Raw HTTPS_PROXY URL (may carry credentials). Used for https targets. */\n httpsProxy?: string;\n /** Parsed NO_PROXY rules. */\n noProxy: NoProxyRule[];\n /**\n * The environment variable the operator's proxy was read from\n * (`HTTPS_PROXY` / `https_proxy` / `HTTP_PROXY` / `http_proxy`), carried\n * through from startup purely so `proxy_selected` can NAME the spelling. A\n * proxy set under one spelling and read under the other is the usual reason\n * egress looks configured to a human and absent to a program, and it is not\n * recoverable from the URL alone. Routing never consults it.\n */\n source?: string;\n}\n\nexport type ProxyRuntimeKind = \"bun\" | \"node\";\n\n/**\n * Test seam for the two things that cannot be observed on the host runtime:\n * the OTHER runtime's branch, and a failing `undici` load. Passing `{}` restores\n * real detection and the real loader.\n */\nexport interface ProxyRuntimeOverrides {\n kind?: ProxyRuntimeKind;\n loadUndici?: () => Promise<UndiciProxyModule>;\n}\n\n/**\n * The slice of `undici` the Node path needs. Declared structurally, and loaded\n * through a DYNAMIC import, so the Bun binary never pulls undici in and tests\n * can substitute a counting stub.\n */\nexport interface UndiciProxyModule {\n ProxyAgent: new (uri: string) => unknown;\n}\n\n/**\n * `RequestInit` plus the two runtime-specific routing fields, neither of which\n * is in the lib types: `proxy` is Bun's per-request proxy URL, `dispatcher` is\n * undici's per-request dispatcher (an undici `ProxyAgent` on the Node path).\n * Exactly one is ever set, by `buildProxyInit`.\n */\n// `dispatcher` is omitted from the base and re-declared as `unknown` because\n// @types/node already types it as undici's `Dispatcher`, which the structurally\n// loaded ProxyAgent (see UndiciProxyModule) does not nominally satisfy.\ntype ProxyFetchInit = { dispatcher?: unknown; proxy?: string } & Omit<\n RequestInit,\n \"dispatcher\"\n>;\n\n/**\n * Which runtime this process is, decided ONCE at module load.\n *\n * Both signals are checked because neither is guaranteed alone: `globalThis.Bun`\n * is absent in some Bun contexts that still expose `process.versions.bun`, and a\n * Node process can be made to define a `Bun` global. Anything that is not\n * positively Bun is treated as Node, which is the fail-safe direction: the Node\n * branch passes an explicit dispatcher, so a misdetection proxies through undici\n * rather than silently going direct.\n */\nfunction detectProxyRuntime(): ProxyRuntimeKind {\n if (typeof (globalThis as { Bun?: unknown }).Bun !== \"undefined\") return \"bun\";\n if (typeof process !== \"undefined\" && typeof process.versions?.bun === \"string\") return \"bun\";\n return \"node\";\n}\n\nconst DETECTED_RUNTIME: ProxyRuntimeKind = detectProxyRuntime();\nconst loadUndiciModule = (): Promise<UndiciProxyModule> =>\n import(\"undici\") as unknown as Promise<UndiciProxyModule>;\n\nlet runtimeKind: ProxyRuntimeKind = DETECTED_RUNTIME;\nlet loadUndici: () => Promise<UndiciProxyModule> = loadUndiciModule;\n\n/**\n * undici `ProxyAgent`s, keyed by the exact proxy URL they were built for.\n *\n * One agent per proxy URL, NOT one per request: a `ProxyAgent` owns a connection\n * pool, so building one per call leaks sockets for the lifetime of a\n * long-running gateway. Promises are cached rather than resolved agents so\n * concurrent first requests to the same proxy share a single construction\n * instead of racing to build several.\n */\nconst proxyAgents = new Map<string, Promise<unknown>>();\n\n/**\n * A single parsed NO_PROXY entry, mirroring `golang.org/x/net/http/httpproxy`:\n * - `cidr` — an IPv4 network (base masked, unsigned int32).\n * - `cidr6` — an IPv6 network (base masked, 128-bit BigInt).\n * - `domain` — a domain suffix. `matchHost` distinguishes a bare entry\n * (`example.com`, matches the domain itself AND its subdomains) from a\n * leading-dot / `*.` entry (`.example.com` / `*.example.com`, subdomains\n * ONLY). An optional `port` restricts the rule to that target port.\n * - `ip` — a single IP literal, with an optional `port`.\n * - `wildcard` — `*`, bypasses everything.\n */\ntype NoProxyRule =\n | { base: bigint; bits: number; kind: \"cidr6\" }\n | { base: number; bits: number; kind: \"cidr\" }\n | { kind: \"domain\"; matchHost: boolean; port?: string; suffix: string; }\n | { kind: \"ip\"; port?: string; value: string; }\n | { kind: \"wildcard\" };\n\n/** Read-only view of the routing mechanism in force (for logging / tests). */\nexport function getProxyRuntimeKind(): ProxyRuntimeKind {\n return runtimeKind;\n}\n\n/**\n * Replace the detected runtime and/or the `undici` loader. Test-only; `{}`\n * resets both and clears the agent cache so no agent leaks across cases.\n */\nexport function overrideProxyRuntime(overrides: ProxyRuntimeOverrides = {}): void {\n runtimeKind = overrides.kind ?? DETECTED_RUNTIME;\n loadUndici = overrides.loadUndici ?? loadUndiciModule;\n proxyAgents.clear();\n}\n\n/**\n * Whether a proxy ANSWERS, decided by one bounded attempt through it.\n *\n * Lives here, beside `isProxyUnreachable` and `buildProxyInit`, so there is\n * exactly ONE definition of \"the proxy is not there\" and one way of dialling a\n * proxy in this codebase. A separate probe (a raw TCP connect, a DNS lookup)\n * would be a second mechanism that could disagree with the request path's own\n * verdict, and then a request would fall back where the probe said reachable, or\n * the reverse.\n *\n * The target is a RESERVED name (RFC 2606 `.invalid`) that can never resolve and\n * can never belong to anyone, because the only hop under test is the one to the\n * PROXY. A proxy that is up answers — very likely 502, since it cannot resolve\n * the target — and an answer of ANY status proves the hop. A proxy that is not\n * there fails the connect instead. So the probe reaches no real host and sends no\n * real traffic anywhere.\n *\n * Three outcomes, not two, and the third is the important one: a timeout, an\n * abort or an unrecognised error is \"indeterminate\" — \"could not observe\" is NOT\n * \"observed absent\", and a caller that collapses the two will act on a proxy's\n * absence it never established.\n */\nexport async function probeProxyReachable(\n proxyUrl: string,\n opts: { fetchImpl?: typeof fetch; timeoutMs?: number } = {},\n): Promise<ProxyReachability> {\n const timeoutMs = opts.timeoutMs ?? PROXY_PROBE_TIMEOUT_MS;\n const doFetch = opts.fetchImpl ?? globalThis.fetch;\n\n // Normalize FIRST, exactly as selectProxy does before handing a proxy to the\n // request path. A scheme-less `host:port` is a real arrival shape (the scanner\n // side treats it as one) and WHATWG parses it as protocol \"host:\" with an EMPTY\n // hostname — so without this the guard below rejected it as undialable and\n // returned \"indeterminate\", while the request path happily dialled the\n // http://-prefixed form. That combination is the worst one available: the gate\n // concludes nothing and the proxy is handed to the wrapped server, which is the\n // hang this probe exists to prevent, for the common scheme-less spelling.\n const dialable = normalizeProxyURL(proxyUrl);\n\n // A proxy URL this lane could never dial says nothing about reachability; it is\n // a misconfiguration, and the request path fails it CLOSED rather than treating\n // it as an absent proxy. Match that: never report such a value \"unreachable\".\n if (!proxyHostname(dialable) || unsupportedProxyProtocol(dialable)) return \"indeterminate\";\n\n const controller = new AbortController();\n let timer: ReturnType<typeof setTimeout> | undefined;\n // The bound is enforced by RACING the timer, not merely by aborting the signal.\n // Abort is cooperative — a transport that does not honour the signal would hang\n // here forever, and this sits on the startup path of a program an agent is\n // blocked on, so an unbounded wait is a hung gateway rather than a slow probe.\n const expiry = new Promise<\"indeterminate\">((resolve) => {\n timer = setTimeout(() => {\n // Still abort, so the in-flight request is cancelled rather than leaked.\n controller.abort();\n resolve(\"indeterminate\");\n }, timeoutMs);\n });\n\n const attempt = (async (): Promise<ProxyReachability> => {\n try {\n const init = await buildProxyInit({ method: \"HEAD\", signal: controller.signal }, dialable);\n await doFetch(PROXY_PROBE_TARGET, init as RequestInit);\n // Any response at all — including 407 and 502 — means the proxy answered.\n return \"reachable\";\n } catch (err) {\n // The bound expiring is exactly the ambiguous case: we learned nothing.\n if (controller.signal.aborted) return \"indeterminate\";\n if (isProxyUnreachable(err)) return \"unreachable\";\n // Under undici a 407/403 REJECTS rather than returning a Response, and\n // isProxyUnreachable deliberately excludes it. That is still a proxy that\n // ANSWERED.\n return \"reachable\";\n }\n })();\n\n try {\n return await Promise.race([attempt, expiry]);\n } finally {\n clearTimeout(timer);\n }\n}\n\n/**\n * The ONE place the runtime split is applied: attach the selected proxy to a\n * request in whichever form the host runtime honours.\n *\n * `kind` is a defaulted parameter purely so a test can assert the branch its own\n * runtime cannot reach; the request path never passes it.\n */\nasync function buildProxyInit(\n init: RequestInit | undefined,\n proxyUrl: string,\n kind: ProxyRuntimeKind = runtimeKind,\n): Promise<ProxyFetchInit> {\n if (kind === \"bun\") return { ...init, proxy: proxyUrl };\n // Node: a failure to obtain the dispatcher must FAIL CLOSED. Continuing\n // without one is a direct connection around the operator's egress control —\n // the precise leak this module exists to prevent — so the error propagates.\n const dispatcher = await nodeDispatcherFor(proxyUrl);\n return { ...init, dispatcher };\n}\n\n/**\n * The cached `ProxyAgent` for a proxy URL, built on first use.\n *\n * Credentials in the URL need no special handling: undici derives\n * `Proxy-Authorization` from the userinfo itself (verified against a real\n * listener — `http://user:pass@host` produced `Basic dXNlcjpwYXNz` with no\n * explicit `token` option), matching what Bun does on its own path.\n *\n * A failed load is NOT cached, so a transient failure does not poison the entry\n * for the process's lifetime.\n */\nfunction nodeDispatcherFor(proxyUrl: string): Promise<unknown> {\n const cached = proxyAgents.get(proxyUrl);\n if (cached) return cached;\n const pending = loadUndici().then(({ ProxyAgent }) => new ProxyAgent(proxyUrl));\n proxyAgents.set(proxyUrl, pending);\n pending.catch(() => proxyAgents.delete(proxyUrl));\n return pending;\n}\n\n/**\n * The bound on `probeProxyReachable`. Short on purpose: it sits on the startup\n * path of a program an agent is waiting on, and a proxy on a corporate LAN either\n * answers in a few hundred milliseconds or is not there. Overrunning it yields\n * \"indeterminate\", which preserves the caller's existing behaviour rather than\n * acting on a guess.\n */\nconst PROXY_PROBE_TIMEOUT_MS = 2000;\n\n/**\n * Reserved, never-resolvable probe target (RFC 2606). See probeProxyReachable:\n * only the hop to the proxy is under test, so the target must not be a real host.\n */\nconst PROXY_PROBE_TARGET = \"http://onyx-proxy-probe.invalid/\";\n\nconst DISABLED: ProxyRuntimeConfig = { enabled: false, noProxy: [] };\n\nlet config: ProxyRuntimeConfig = DISABLED;\n\n/**\n * Install the runtime proxy configuration. Called once at startup by\n * `setupProxyDispatcher()` (in the re-exec'd child, or with `enabled: false`\n * when no proxy env is present). Safe to call again in tests.\n */\nexport function configureProxy(next: ProxyRuntimeConfig): void {\n config = next;\n proxyEventLogged.clear();\n // The proxy URLs may have changed; agents built for the old ones are dead\n // weight holding open sockets.\n proxyAgents.clear();\n}\n\n/**\n * The ONE memo behind every proxy routing line here, so a long-lived gateway\n * making many requests through the same proxy logs each decision ONCE rather\n * than once per request. Keys are namespaced by event\n * (`selected:<proxy>`, `fallback:<proxy>`, `nofallback:<proxy>|<target>|<reason>`)\n * so the three lines cannot suppress one another. Cleared by configureProxy so a\n * config reload re-arms every warning.\n */\nconst proxyEventLogged = new Set<string>();\n\n/** Read-only view of the active configuration (for logging / tests). */\nexport function getProxyConfig(): ProxyRuntimeConfig {\n return config;\n}\n\n/**\n * Parse a raw NO_PROXY string into rules, mirroring\n * `golang.org/x/net/http/httpproxy`. Entries are comma-separated; each is an\n * IP, a CIDR block, a domain (bare or dot-prefixed, suffix-matched), or `*`.\n */\nexport function parseNoProxy(raw: string | undefined): NoProxyRule[] {\n if (!raw) return [];\n const rules: NoProxyRule[] = [];\n for (const rawEntry of raw.split(\",\")) {\n const entry = rawEntry.trim().toLowerCase();\n if (!entry) continue;\n if (entry === \"*\") return [{ kind: \"wildcard\" }];\n\n // CIDR first (IPv4 or IPv6), mirroring Go's ParseCIDR-first order. An\n // unparseable CIDR is DROPPED rather than reinterpreted as a domain rule\n // that would never match its intended network.\n if (entry.includes(\"/\")) {\n const cidr = parseCidr(entry);\n if (cidr) rules.push(cidr);\n continue;\n }\n\n // Split an optional literal port (Go consults net.SplitHostPort here), so\n // `example.com:80` and `[::1]:8080` keep their port for port-scoped rules.\n const { host, port } = splitHostPort(entry);\n if (!host) continue;\n\n if (isIP(host) !== 0) {\n rules.push({ kind: \"ip\", value: normalizeIp(host), ...(port ? { port } : {}) });\n continue;\n }\n\n // Domain rule. Mirror httpproxy exactly: a bare entry matches the domain\n // itself and its subdomains (matchHost); a leading-dot or `*.` entry is\n // subdomains ONLY. `*.example.com` normalizes to `.example.com`.\n let h = host;\n let matchHost: boolean;\n if (h.startsWith(\"*.\")) {\n h = h.slice(1); // \"*.example.com\" -> \".example.com\"\n matchHost = false;\n } else if (h.startsWith(\".\")) {\n matchHost = false;\n } else {\n matchHost = true;\n }\n const suffix = h.startsWith(\".\") ? h.slice(1) : h;\n if (suffix) rules.push({ kind: \"domain\", matchHost, suffix, ...(port ? { port } : {}) });\n }\n return rules;\n}\n\n/** Message of an error-ish value, for the log line. */\nfunction describeError(err: unknown): string {\n if (err instanceof Error) return err.message;\n return String(err);\n}\n\n/**\n * Emit a single structured warning the first time gateway egress falls back to a\n * DIRECT connection because a configured proxy could not be REACHED.\n *\n * This is a security-relevant decision — egress the operator pointed at a proxy\n * is instead leaving DIRECT — so it must be visible where it can silently do the\n * wrong thing. The field NAMES are shared verbatim with the scanner's Go helper\n * (`scanner/internal/proxyhttp/proxyhttp.go` → logDirectFallbackOnce) —\n * event/op/outcome/proxy_host/target_host/platform/error — so ONE Groundcover\n * query over event=proxy_fallback covers both Onyx egress surfaces. Two honest\n * differences, both intended: `op` is the lane discriminator, so its VALUE differs\n * (`fetch` here, `round_trip` there); and `reason` is TS-only, carrying the fetch\n * layer's error CODES (`ECONNREFUSED`, and each code of an AggregateError), which\n * is what `isProxyUnreachable` actually judged and which the Go side has no\n * analogue for — there the single proxyconnect Op is the whole verdict. Renaming\n * any shared field is a schema break on both surfaces.\n *\n * It routes through the winston `logger` (→ rotating file + the Groundcover\n * ingest transport), NEVER stdout/stderr, which are reserved for the JSON-RPC\n * stream in stdio mode. The logger is imported LAZILY and fire-and-forget: the\n * ingest transport's own egress uses proxiedFetch, so a static `import { logger }`\n * here would close an initialization cycle\n * (proxyFetch → logger → ingestTransport → proxyFetch). The dynamic import breaks\n * that cycle and keeps the fallback off any log-flush await; the host is marked\n * BEFORE the import resolves so concurrent requests to the same proxy host cannot\n * race a duplicate line.\n */\nfunction logDirectFallback(proxyHost: string, targetHost: string, cause: unknown): void {\n if (!logOnce(`fallback:${proxyHost}`)) return;\n const reason = errorCodes(cause).join(\",\") || describeError(cause);\n void loadLogger()\n .then(({ logger }) => {\n logger.warn(\"Configured proxy is unreachable; routing this egress DIRECT\", {\n error: describeError(cause),\n event: \"proxy_fallback\",\n op: \"fetch\",\n outcome: \"direct\",\n platform: process.platform,\n proxy_host: proxyHost,\n reason,\n target_host: targetHost,\n });\n })\n .catch(() => {\n // A logging failure must never crash egress. Re-arm this host so a later\n // request can still surface the fallback rather than losing it forever.\n proxyEventLogged.delete(`fallback:${proxyHost}`);\n });\n}\n\n/** True the first time `key` is seen; marks it seen. */\nfunction logOnce(key: string): boolean {\n if (proxyEventLogged.has(key)) return false;\n proxyEventLogged.add(key);\n return true;\n}\n\n/**\n * The winston logger, imported LAZILY and memoized, for the three routing lines\n * below.\n *\n * Lazy and dynamic because the ingest transport's own egress uses proxiedFetch, so\n * a static `import { logger }` here would close an initialization cycle\n * (proxyFetch -> logger -> ingestTransport -> proxyFetch).\n *\n * Memoized because two lines emitted in the SAME tick otherwise open two\n * concurrent `import()`s of the same module, and the second never settles under\n * the vitest module runner -- neither its `.then` nor its `.catch` ever runs, so\n * the line silently vanishes and any test asserting on it is unfalsifiable\n * (measured: 12 macrotask flushes, still nothing). One shared promise also drops\n * the repeated import from the request path. A REJECTION is not cached, so a\n * transient failure does not silence the lines for the process's lifetime.\n */\nlet loggerModule: Promise<{ logger: ProxyFetchLogger }> | undefined;\n\n/** The slice of the winston logger these lines use. */\ninterface ProxyFetchLogger {\n info: (message: string, meta?: Record<string, unknown>) => void;\n warn: (message: string, meta?: Record<string, unknown>) => void;\n}\n\n/**\n * Whether a rejection is the CALLER's cancellation rather than any statement\n * about the proxy. Used only to suppress a log line — the routing decision is\n * `isProxyUnreachable`'s alone, and this must never be consulted by it.\n *\n * Both signals are checked because either can arrive alone: the rejection may be\n * an AbortError with no signal on the init (an internal timeout controller the\n * caller did not hand us), and a signal may be aborted while the runtime reports\n * some other error for the interrupted connect.\n */\nfunction isCallerAbort(err: unknown, init: RequestInit | undefined): boolean {\n if (init?.signal?.aborted) return true;\n if (err instanceof DOMException && err.name === \"AbortError\") return true;\n return (err as { name?: unknown } | null)?.name === \"AbortError\";\n}\n\nfunction loadLogger(): Promise<{ logger: ProxyFetchLogger }> {\n loggerModule ??= (import(\"./logger.js\") as Promise<{ logger: ProxyFetchLogger }>).catch(\n (err: unknown) => {\n loggerModule = undefined;\n throw err;\n },\n );\n return loggerModule;\n}\n\n/**\n * Emit a single structured WARNING when a proxied request FAILED and the\n * allow-list deliberately did NOT treat it as an unreachable proxy — so no\n * direct retry ran and the request hard-failed.\n *\n * This is the state that burned a customer: a wrapped MCP server hung and no line\n * anywhere said why the fallback stayed shut. `reason` carries exactly what\n * `isProxyUnreachable` judged — the error CODES found on the rejection, its cause\n * chain and any aggregated `errors[]` — so a 407 under undici reads\n * `UND_ERR_ABORTED` and an ECONNRESET reads `ECONNRESET`, rather than both being\n * invisible. Keyed per (proxy, target, reason): unlike the fallback, whose verdict\n * is a property of the proxy alone, a fail-closed failure can differ per target,\n * and each distinct shape is a separate diagnosis.\n */\nfunction logNoFallback(proxyHost: string, targetHost: string, cause: unknown): void {\n const reason = errorCodes(cause).join(\",\") || describeError(cause);\n if (!logOnce(`nofallback:${proxyHost}|${targetHost}|${reason}`)) return;\n void loadLogger()\n .then(({ logger }) => {\n logger.warn(\"Proxied request failed and was NOT retried direct; egress stays fail-closed\", {\n error: describeError(cause),\n event: \"proxy_no_fallback\",\n op: \"fetch\",\n outcome: \"failed\",\n platform: process.platform,\n proxy_host: proxyHost,\n reason,\n target_host: targetHost,\n });\n })\n .catch(() => {\n proxyEventLogged.delete(`nofallback:${proxyHost}|${targetHost}|${reason}`);\n });\n}\n\n/**\n * Emit a single structured INFO line the first time gateway egress is actually\n * ROUTED through a configured proxy.\n *\n * It exists because the absence of `proxy_fallback` was ambiguous: \"the proxy is\n * working\" and \"no proxy was ever configured\" produced the same empty log, which\n * are the two states an operator most needs to separate when egress misbehaves.\n * Field names are the fallback line's, so one Groundcover query can join them —\n * `outcome` is \"proxied\" rather than \"direct\", and `source` names the env var\n * spelling startup read the proxy from.\n *\n * Same lazy, fire-and-forget logger import as logDirectFallback, and for the same\n * initialization-cycle reason; see that function. NEVER stdout/stderr, which are\n * the JSON-RPC stream in stdio mode.\n */\nfunction logProxySelected(proxyHost: string, source: string | undefined): void {\n if (!logOnce(`selected:${proxyHost}`)) return;\n void loadLogger()\n .then(({ logger }) => {\n logger.info(\"Egress is routed through the configured proxy\", {\n event: \"proxy_selected\",\n op: \"fetch\",\n outcome: \"proxied\",\n platform: process.platform,\n proxy_host: proxyHost,\n source: source ?? \"\",\n });\n })\n .catch(() => {\n proxyEventLogged.delete(`selected:${proxyHost}`);\n });\n}\n\n/**\n * The one proxy-honoring fetch. Typed as `typeof fetch`, so it is a drop-in for\n * `globalThis.fetch` and is assignable to the MCP SDK's `FetchLike`.\n */\nexport const proxiedFetch = (async (\n input: Parameters<typeof fetch>[0],\n init?: RequestInit,\n): Promise<Response> => {\n if (!config.enabled) return globalThis.fetch(input, init);\n\n let target: URL;\n try {\n target = toURL(input);\n } catch {\n // Unparseable target: nothing to route. The env is stripped, so a plain\n // fetch is a genuine direct connection (no accidental env proxying).\n return globalThis.fetch(input, init);\n }\n\n const proxy = selectProxy(target);\n if (!proxy) return globalThis.fetch(input, init);\n\n // The fallback is keyed on the ATTEMPT, not on a DNS probe of the proxy host\n // (mirroring the Go helper): whatever makes the proxy unreachable — it refuses\n // the connection, blackholes the SYN, or its host does not resolve — surfaces as\n // a fetch rejection that isProxyUnreachable recognizes, and only then is the\n // request retried direct. A proxy that ANSWERS is never bypassed. The host is\n // still derived HERE, up front, for the two fail-closed checks below and to name\n // the proxy in the log line.\n const proxyHost = proxyHostname(proxy);\n if (!proxyHost) {\n // An unparseable proxy URL (e.g. HTTPS_PROXY=not-a-url) names no proxy at\n // all. This is a misconfiguration, NOT a proxy that was tried and found\n // unreachable, so it must NOT silently leak the request direct around the\n // enterprise egress control. Fail closed with a clear error; only a real\n // attempt at a real proxy host may open the direct fallback.\n throw new Error(\n \"Configured proxy URL has no parseable host; refusing to route direct around the egress proxy\",\n );\n }\n // An explicit scheme this lane cannot dial (socks4://, a typo like htp://) must\n // fail CLOSED, before any attempt. Attempting it is the danger: the proxy host of\n // such a value is meaningless, so the attempt fails in exactly the way\n // isProxyUnreachable recognizes, which would open the direct fallback and leak\n // the request around the egress control. A misconfigured scheme is not a proxy\n // that was tried and found absent.\n const badProtocol = unsupportedProxyProtocol(proxy);\n if (badProtocol) {\n throw new Error(\n `Configured proxy URL uses an unsupported proxy scheme \"${badProtocol}\"; refusing to route direct around the egress proxy`,\n );\n }\n // Naming the proxy BEFORE the attempt is what makes state (a) — \"proxying\n // fine\" — visible at all; without it, no log line distinguishes it from \"no\n // proxy configured\". Routing is untouched: this runs after every selection and\n // fail-closed check, on a value already computed, and the memo keeps it to one\n // line per proxy rather than one per request.\n logProxySelected(proxyHost, config.source);\n const withProxy = await buildProxyInit(init, proxy);\n try {\n // Under Bun a 407/403/502 from the proxy arrives HERE as an ordinary\n // Response, not a rejection (verified on Bun 1.3.14, for both an http\n // absolute-form request and an https CONNECT), so an answering proxy's\n // verdict is returned to the caller untouched. Under undici the same\n // refusal REJECTS with `UND_ERR_ABORTED`, which `isProxyUnreachable`\n // excludes, so it surfaces as a failure rather than a direct retry. Either\n // way an answering proxy is never bypassed.\n return await globalThis.fetch(input, withProxy as RequestInit);\n } catch (err) {\n if (!isProxyUnreachable(err)) {\n // A proxied request failed and the allow-list rejected it, so nothing is\n // retried and the caller gets a hard failure. That decision used to be\n // entirely silent. Report it, then re-throw unchanged.\n //\n // Except a CANCELLATION. An abort also lands here (isProxyUnreachable\n // excludes AbortError deliberately), but it is the caller ending the\n // request, not a verdict about the proxy — reporting it as a fail-closed\n // proxy decision would blame the proxy for the caller's own choice and\n // bury the real fail-closed lines under cancellation noise.\n if (!isCallerAbort(err, init)) logNoFallback(proxyHost, target.hostname, err);\n throw err;\n }\n // A caller's own abort is a cancellation, not a routing decision (see\n // isProxyUnreachable), so it is neither a fallback nor a fail-closed verdict\n // and gets no line of its own.\n if (init?.signal?.aborted) throw err;\n // The proxy could not be reached, so nothing was sent: retry DIRECT. This is\n // deliberate egress AROUND the operator's control, surfaced once per proxy\n // target (this path runs on every request) so it is visible in the gateway\n // log / ingest rather than silent.\n logDirectFallback(proxyHost, target.hostname, err);\n return globalThis.fetch(input, init);\n }\n}) as typeof fetch;\n\n/** Depth of the cause/errors graph that is searched for a code. */\nconst CODE_WALK_MAX_DEPTH = 6;\n/** Nodes visited before the search gives up, so no graph can make it spin. */\nconst CODE_WALK_MAX_NODES = 32;\n\n/**\n * Error codes carried by `err`, its `cause` chain, and any aggregated\n * `errors[]` beneath either.\n *\n * Both runtimes nest the real cause: Bun sets `code` on the thrown Error\n * itself, undici (used when the gateway runs under Node, as it does in tests)\n * sets it on `cause`.\n *\n * The `errors[]` arm is what makes a DUAL-STACK proxy host work. Node dials\n * such a host once per address family (happy eyeballs) and reports the combined\n * failure as an `AggregateError` — `net`'s `internalConnectMultiple` destroys\n * the socket with `new NodeAggregateError(context.errors)`, and that class is\n * defined as `this.code = errors[0]?.code`. So the aggregate's own `code` is\n * the FIRST attempt's and nothing more, and it carries no `cause` at all:\n * walking only `code`/`cause` sees one attempt out of several. When that first\n * attempt failed for a reason not on the allow-list (or with no code, leaving\n * the aggregate codeless) the real \"the proxy was never reached\" verdict sits\n * unread in `errors[1]`, and an unreachable proxy hard-fails instead of falling\n * back — the exact condition the direct fallback exists for.\n *\n * Widening the search cannot weaken the decision: the caller is an allow-list,\n * so finding MORE codes can only ever find one that means the proxy was never\n * reached. A code meaning the proxy answered still opens nothing.\n *\n * The traversal is breadth-first over a graph rather than a walk down a chain,\n * so it is bounded three ways — depth, total nodes visited, and an identity set\n * — and a cyclic or pathologically wide chain terminates instead of spinning.\n */\nfunction errorCodes(err: unknown): string[] {\n const codes: string[] = [];\n const seen = new Set<object>();\n const queue: { depth: number; node: unknown }[] = [{ depth: 0, node: err }];\n for (let visited = 0; queue.length > 0 && visited < CODE_WALK_MAX_NODES; ) {\n const { depth, node } = queue.shift() as { depth: number; node: unknown };\n if (node == null || typeof node !== \"object\") continue;\n if (seen.has(node)) continue;\n seen.add(node);\n visited++;\n\n const { cause, code, errors } = node as {\n cause?: unknown;\n code?: unknown;\n errors?: unknown;\n };\n if (typeof code === \"string\") codes.push(code);\n if (depth + 1 >= CODE_WALK_MAX_DEPTH) continue;\n if (cause != null) queue.push({ depth: depth + 1, node: cause });\n if (Array.isArray(errors)) {\n for (const nested of errors) queue.push({ depth: depth + 1, node: nested });\n }\n }\n return codes;\n}\n\n/**\n * Error codes that mean the request never reached a server because the PROXY\n * could not be reached — the ONLY case that opens the direct retry.\n *\n * Measured on Bun 1.3.14 (the version embedded in the shipped linux binary),\n * driving real listeners through a per-request `proxy`:\n * - proxy port closed -> code \"ConnectionRefused\" (http and https)\n * - proxy host unresolvable -> \"ConnectionRefused\" (http target),\n * \"FailedToOpenSocket\" (https target)\n * - proxy address blackholed -> \"ConnectionRefused\", after the OS connect\n * timeout (measured: 75s on macOS) — so a\n * timed-out connect needs no code of its own\n * - proxy answers 407 -> NO rejection at all; a 407 Response\n * - proxy accepts then hangs up -> \"ECONNRESET\"\n * The undici/Node spellings are listed alongside because the gateway's own test\n * suite and any node-hosted run raise those instead.\n *\n * ECONNRESET and every unlisted code are deliberately ABSENT: this is an\n * allow-list, so an unrecognised failure retries nothing and the request fails\n * exactly as it does today rather than silently leaving the egress proxy.\n */\n// Sorted, not grouped by runtime: the lint rule orders this set alphabetically.\n// \"ConnectionRefused\" and \"FailedToOpenSocket\" are Bun's; the rest are undici's.\nconst UNREACHABLE_CODES = new Set([\n \"ConnectionRefused\",\n \"EAI_AGAIN\",\n // A name that exists with no address record (NODATA) reaches us as EAI_NODATA\n // or EAI_NONAME on the Node/npx path rather than as ENOTFOUND. That is the\n // real customer proxy shape -- present in the zone with MX records and no\n // A/AAAA -- so omitting these two made exactly that host hard-fail instead of\n // retrying direct.\n \"EAI_NODATA\",\n \"EAI_NONAME\",\n \"ECONNREFUSED\",\n \"EHOSTUNREACH\",\n \"ENETUNREACH\",\n \"ENOTFOUND\",\n \"ETIMEDOUT\",\n \"FailedToOpenSocket\",\n \"UND_ERR_CONNECT_TIMEOUT\",\n]);\n\n/**\n * Whether a fetch rejection means the proxy was never reached. Exported for\n * tests. A caller's own abort is NOT a routing decision — it is a cancellation\n * to surface — so `AbortError` is excluded even though it can interrupt a proxy\n * connect.\n */\nexport function isProxyUnreachable(err: unknown): boolean {\n if (err == null) return false;\n if (err instanceof DOMException && err.name === \"AbortError\") return false;\n if ((err as { name?: unknown }).name === \"AbortError\") return false;\n return errorCodes(err).some((code) => UNREACHABLE_CODES.has(code));\n}\n\nfunction ipv4ToInt(ip: string): number {\n const parts = ip.split(\".\");\n return (\n (((Number(parts[0]) << 24) >>> 0) +\n (Number(parts[1]) << 16) +\n (Number(parts[2]) << 8) +\n Number(parts[3])) >>>\n 0\n );\n}\n\n/**\n * Expand an IPv6 literal to its 128-bit unsigned value. Handles `::` zero\n * compression, an embedded IPv4 tail (`::ffff:1.2.3.4`), zone ids, and brackets.\n * Returns undefined for anything that is not a well-formed IPv6 address.\n */\nfunction ipv6ToBigInt(ip: string): bigint | undefined {\n let s = stripBrackets(ip.toLowerCase());\n const pct = s.indexOf(\"%\");\n if (pct !== -1) s = s.slice(0, pct); // drop a zone id (fe80::1%eth0)\n\n // An embedded IPv4 tail becomes the low two hextets.\n if (s.includes(\".\")) {\n const lastColon = s.lastIndexOf(\":\");\n if (lastColon === -1) return undefined;\n const octets = s.slice(lastColon + 1).split(\".\").map(Number);\n if (octets.length !== 4 || octets.some((n) => !Number.isInteger(n) || n < 0 || n > 255)) {\n return undefined;\n }\n const hi = ((octets[0] << 8) | octets[1]).toString(16);\n const lo = ((octets[2] << 8) | octets[3]).toString(16);\n s = `${s.slice(0, lastColon + 1)}${hi}:${lo}`;\n }\n\n const halves = s.split(\"::\");\n if (halves.length > 2) return undefined;\n const head = halves[0] === \"\" ? [] : halves[0].split(\":\");\n const tail = halves.length === 2 && halves[1] !== \"\" ? halves[1].split(\":\") : [];\n let groups: string[];\n if (halves.length === 2) {\n const missing = 8 - head.length - tail.length;\n if (missing < 0) return undefined; // `::` must stand in for ≥1 zero group\n groups = [...head, ...Array<string>(missing).fill(\"0\"), ...tail];\n } else {\n groups = head;\n }\n if (groups.length !== 8) return undefined;\n\n let value = 0n;\n for (const group of groups) {\n if (!/^[0-9a-f]{1,4}$/.test(group)) return undefined;\n value = (value << 16n) | BigInt(parseInt(group, 16));\n }\n return value;\n}\n\n/**\n * Loopback targets are ALWAYS direct, mirroring Go's httpproxy.useProxy, which\n * bypasses \"localhost\" and any loopback IP BEFORE consulting NO_PROXY. Without\n * this the gateway routes local MCP servers (localhost / 127.0.0.0/8 / ::1)\n * through the corporate proxy — which cannot reach them — whenever NO_PROXY does\n * not happen to list loopback.\n */\nfunction isLoopbackHost(hostname: string): boolean {\n const host = stripBrackets(hostname.toLowerCase());\n if (host === \"localhost\") return true;\n const v = isIP(host);\n if (v === 4) return host === \"127.0.0.1\" || host.startsWith(\"127.\");\n if (v === 6) return host === \"::1\";\n return false;\n}\n\nfunction maskFromBits(bits: number): number {\n return bits === 0 ? 0 : (0xffffffff << (32 - bits)) >>> 0;\n}\n\nfunction matchesNoProxy(hostname: string, port: string): boolean {\n const host = stripBrackets(hostname.toLowerCase());\n const fam = isIP(host);\n const hostIp = fam !== 0 ? normalizeIp(host) : undefined;\n const hostV4 = fam === 4 ? ipv4ToInt(host) : undefined;\n const hostV6 = fam === 6 ? ipv6ToBigInt(host) : undefined;\n\n for (const rule of config.noProxy) {\n switch (rule.kind) {\n case \"cidr\":\n // `&` returns a SIGNED int32 in JS, so for any host whose first octet is\n // >= 128 the masked value is negative while rule.base is stored unsigned\n // (`>>> 0`). Coerce back to unsigned before comparing, or every CIDR above\n // 127.x silently fails to match.\n if (\n hostV4 !== undefined &&\n ((hostV4 & maskFromBits(rule.bits)) >>> 0) === rule.base\n ) {\n return true;\n }\n break;\n case \"cidr6\":\n // IPv6 network match via a 128-bit BigInt prefix compare. `rule.base` is\n // already masked to the prefix; a /0 shifts everything away and matches all.\n if (hostV6 !== undefined) {\n const shift = BigInt(128 - rule.bits);\n if (hostV6 >> shift === rule.base >> shift) return true;\n }\n break;\n case \"domain\":\n if (\n (host.endsWith(`.${rule.suffix}`) || (rule.matchHost && host === rule.suffix)) &&\n (rule.port === undefined || rule.port === port)\n ) {\n return true;\n }\n break;\n case \"ip\":\n if (\n hostIp !== undefined &&\n hostIp === rule.value &&\n (rule.port === undefined || rule.port === port)\n ) {\n return true;\n }\n break;\n case \"wildcard\":\n return true;\n }\n }\n return false;\n}\n\n/** Canonicalize an IP so equal addresses compare equal (lower-cased). */\nfunction normalizeIp(ip: string): string {\n return stripBrackets(ip).toLowerCase();\n}\n\n/** Proxy schemes this lane can actually dial. */\nconst RECOGNIZED_PROXY_PROTOCOLS = new Set([\"http:\", \"https:\", \"socks5:\", \"socks5h:\"]);\n\n/**\n * Normalize a proxy value to a URL carrying an explicit scheme, mirroring Go's\n * httpproxy.parseProxy EXACTLY: a value that parses with a non-empty host is\n * returned verbatim; anything else — a scheme-less `host:port` such as\n * `proxy.example.com:8080` or `10.0.0.1:3128`, whose first token `new URL` mistakes\n * for a scheme and which therefore yields an empty hostname — is retried with\n * `http://` prepended, exactly what curl and the installer's shell helper accept. A\n * genuinely unparseable value is returned as-is so the caller's fail-closed path\n * still rejects it (proxyHostname → \"\" → throw), never leaking direct.\n *\n * The host test, rather than a recognized-scheme test, is what keeps an explicit but\n * UNSUPPORTED scheme intact. Retrying `socks4://proxy.corp:1080` produced\n * `http://socks4//proxy.corp:1080` — hostname the literal `socks4`, real host demoted\n * into the path — and the attempt against the nonexistent host `socks4` then failed\n * in exactly the way isProxyUnreachable recognizes, opening the direct fallback and\n * silently routing the request around the operator's egress control. Go keeps such a URL and errors at dial; proxiedFetch\n * rejects it up front instead, which is the same fail-closed outcome with a message\n * that names the cause.\n */\nfunction normalizeProxyURL(proxy: string): string {\n try {\n // Go's parseProxy retries only when Scheme or Host is empty. A WHATWG URL with a\n // non-empty hostname always has both, so this single test is that condition.\n if (new URL(proxy).hostname) return proxy;\n } catch {\n // Unparseable as-is (e.g. scheme-less IP:port); fall through to the retry.\n }\n try {\n if (new URL(`http://${proxy}`).hostname) return `http://${proxy}`;\n } catch {\n // Prepending http:// still does not yield a valid URL — leave unchanged.\n }\n return proxy;\n}\n\n/**\n * Parse a CIDR entry (IPv4 or IPv6). Mirrors Go's `net.ParseCIDR`: an IPv4 CIDR\n * yields an unsigned-int32 `cidr` rule, an IPv6 CIDR a 128-bit BigInt `cidr6`\n * rule. Returns undefined (caller drops the entry) for anything unparseable.\n */\nfunction parseCidr(entry: string): NoProxyRule | undefined {\n const slash = entry.indexOf(\"/\");\n if (slash < 0) return undefined;\n const addr = entry.slice(0, slash);\n const bitsText = entry.slice(slash + 1);\n // Number(\"\") === 0 (and Number(\" \") === 0, Number(\"0x1F\") === 31), so an\n // empty/blank/non-decimal prefix such as \"10.0.0.0/\" would yield bits=0 and a\n // /0 mask that matches EVERY host — a silent, proxy-wide NO_PROXY bypass.\n // Require an explicit run of decimal digits before trusting Number().\n if (!/^\\d+$/.test(bitsText)) return undefined;\n const bits = Number(bitsText);\n if (!Number.isInteger(bits) || bits < 0) return undefined;\n const fam = isIP(addr);\n if (fam === 4) {\n if (bits > 32) return undefined;\n const mask = maskFromBits(bits);\n return { base: (ipv4ToInt(addr) & mask) >>> 0, bits, kind: \"cidr\" };\n }\n if (fam === 6) {\n if (bits > 128) return undefined;\n const v = ipv6ToBigInt(addr);\n if (v === undefined) return undefined;\n const shift = BigInt(128 - bits);\n return { base: (v >> shift) << shift, bits, kind: \"cidr6\" };\n }\n return undefined;\n}\n\n/** Hostname of a proxy URL, without IPv6 brackets. */\nfunction proxyHostname(proxyUrl: string): string {\n try {\n return stripBrackets(new URL(proxyUrl).hostname);\n } catch {\n return \"\";\n }\n}\n\n/**\n * Selects the proxy URL for a target, or undefined for a direct connection.\n * Applies scheme selection (mirroring Go) and NO_PROXY. It says nothing about\n * whether that proxy is REACHABLE — that is decided by the attempt itself, in\n * proxiedFetch, from the error the fetch rejects with.\n */\nfunction selectProxy(target: URL): string | undefined {\n const proxy =\n target.protocol === \"https:\"\n ? config.httpsProxy || config.httpProxy\n : config.httpProxy;\n if (!proxy) return undefined;\n if (isLoopbackHost(target.hostname)) return undefined;\n // Canonicalize the port so a port-scoped NO_PROXY entry (example.com:443)\n // matches an https target whose URL omits the default port, mirroring Go's\n // canonicalAddr, which always carries an explicit port.\n const port = target.port || (target.protocol === \"https:\" ? \"443\" : \"80\");\n if (matchesNoProxy(target.hostname, port)) return undefined;\n return normalizeProxyURL(proxy);\n}\n\n/**\n * Split a NO_PROXY entry into host + optional port, mirroring net.SplitHostPort:\n * a bracketed IPv6 (`[::1]:80`) unwraps to `::1`/`80`; a single-colon entry\n * (`example.com:80`) splits; a bare IPv6 (`::1`, many colons, no brackets) or a\n * plain host keeps the whole string as the host with no port.\n */\nfunction splitHostPort(entry: string): { host: string; port: string } {\n if (entry.startsWith(\"[\")) {\n const end = entry.indexOf(\"]\");\n if (end === -1) return { host: entry, port: \"\" };\n const host = entry.slice(1, end);\n const rest = entry.slice(end + 1);\n return { host, port: rest.startsWith(\":\") ? rest.slice(1) : \"\" };\n }\n const first = entry.indexOf(\":\");\n if (first !== -1 && first === entry.lastIndexOf(\":\")) {\n return { host: entry.slice(0, first), port: entry.slice(first + 1) };\n }\n return { host: entry, port: \"\" };\n}\n\nfunction stripBrackets(host: string): string {\n return host.startsWith(\"[\") && host.endsWith(\"]\") ? host.slice(1, -1) : host;\n}\n\nfunction toURL(input: Parameters<typeof fetch>[0]): URL {\n if (input instanceof URL) return input;\n if (typeof input === \"string\") return new URL(input);\n // Request-like object.\n return new URL((input as Request).url);\n}\n\n/**\n * The proxy's scheme if it carries one this lane cannot dial, else undefined.\n * Returns undefined for an unparseable value too: that is the caller's separate\n * \"no parseable host\" fail-closed path, which reports the misconfiguration better.\n */\nfunction unsupportedProxyProtocol(proxyUrl: string): string | undefined {\n try {\n const { protocol } = new URL(proxyUrl);\n return RECOGNIZED_PROXY_PROTOCOLS.has(protocol) ? undefined : protocol;\n } catch {\n return undefined;\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"proxyFetch-2QPdA_Ro.js","names":["DETECTED_RUNTIME: ProxyRuntimeKind","runtimeKind: ProxyRuntimeKind","loadUndici: () => Promise<UndiciProxyModule>","timer: ReturnType<typeof setTimeout> | undefined","config: ProxyRuntimeConfig","rules: NoProxyRule[]","matchHost: boolean","loggerModule: Promise<{ logger: ProxyFetchLogger }> | undefined","target: URL","codes: string[]","queue: { depth: number; node: unknown }[]","groups: string[]"],"sources":["../src/lib/proxyFetch.ts"],"sourcesContent":["/**\n * The single, shared, proxy-honoring `fetch` for the whole gateway.\n *\n * THE GATEWAY SHIPS TWO WAYS, AND EACH RUNTIME PROXIES DIFFERENTLY:\n * - a `bun build --compile` single-file binary, and\n * - an npx/Node package (`engines.node >= 24`, `bin` -> dist/bin/mcp-gateway.js).\n * There is NO single option both runtimes honour, so routing is decided once\n * at load (`detectProxyRuntime`) and applied in exactly one place\n * (`buildProxyInit`). Nothing else in the request path asks which runtime it\n * is on.\n *\n * WHY NOT undici's `setGlobalDispatcher`:\n * Under Bun, `globalThis.fetch` is Bun-native and does NOT consult undici's\n * `setGlobalDispatcher(...)`. The approach this module replaced installed a\n * global `EnvHttpProxyAgent`, which is a silent no-op in the shipped Bun\n * binary — so no egress was actually proxied there. That reasoning still\n * holds, and it is specifically about the GLOBAL dispatcher. It does NOT mean\n * the Node path needs nothing: Node's `fetch` ignores Bun's per-request\n * `proxy` option just as completely (measured on Node 24.18 — the request\n * reached the origin and the proxy listener saw no connection at all), and\n * startup strips the proxy env vars, so undici has no env proxy to fall back\n * on either. A Bun-only mechanism therefore leaves every npx call DIRECT\n * while reporting a configured proxy. Node is handled with an EXPLICIT\n * PER-REQUEST `dispatcher` instead (`ProxyAgent`, one cached instance per\n * proxy URL — see `nodeDispatcherFor`), which undici's fetch does honour,\n * verified against a real CONNECT listener rather than assumed.\n *\n * HOW ROUTING IS CONTROLLED UNDER BUN (Bun-native, empirically verified on Bun\n * 1.3.14 — the version embedded in the shipped linux binary — and 1.4.0):\n * - Bun snapshots HTTP(S)_PROXY / NO_PROXY from the OS environment at process\n * startup. Mutating `process.env` at runtime does NOT change that snapshot,\n * and there is no option value that forces a DIRECT connection while an env\n * proxy is snapshotted.\n * - Bun DOES honor a per-request `proxy` option that overrides the env\n * snapshot, for both https (CONNECT) and http (absolute-form GET) targets,\n * including credentials in the proxy URL (Proxy-Authorization).\n * Therefore the ONLY way to get deterministic DIRECT connections (needed for\n * NO_PROXY bypass and the direct fallback) is to launch the real gateway\n * with the standard proxy env vars STRIPPED from the OS environment, so Bun's\n * snapshot is empty, and then decide routing per request by passing (or not\n * passing) an explicit `proxy` option. That stripping + re-exec is done once\n * at startup by `setupProxyDispatcher()` in `./proxySetup.ts`; this module is\n * the request-time half that reads the captured config and applies it.\n * The same stripping serves the Node path: it is what makes \"no dispatcher\"\n * mean a genuinely direct connection, with nothing left for Node's opt-in\n * env-proxy support to read.\n *\n * The routing policy mirrors the scanner's Go helper\n * (`scanner/internal/proxyhttp/proxyhttp.go`) so every Onyx egress surface\n * behaves identically:\n * 1. https targets use HTTPS_PROXY (falling back to HTTP_PROXY when unset);\n * http targets use HTTP_PROXY. Upper- and lower-case env vars both count,\n * upper-case taking precedence.\n * 2. NO_PROXY is honored with the same rule grammar as httpproxy: a bare\n * domain (`walmart.com`) matches the domain AND its subdomains, while a\n * leading-dot / `*.` entry matches subdomains only; entries may carry a\n * literal port; plus IPv4 CIDR, IPv6 CIDR, single-IP, and `*` rules.\n * 3. A direct fallback for an UNREACHABLE proxy: the request is attempted\n * through the proxy, and if that attempt fails before reaching any server\n * — the proxy refuses the connection, its host does not resolve, the\n * connect times out — the SAME request is retried once, directly. A proxy\n * that ANSWERS is never bypassed, on either runtime, though they report it\n * differently: Bun surfaces a 407/403/502 from the proxy as an ordinary\n * Response, so the verdict reaches the caller untouched, while undici\n * REJECTS with `UND_ERR_ABORTED` (\"Proxy response (407) !== 200 when HTTP\n * Tunneling\"), which the allow-list below deliberately excludes so it\n * fails instead of retrying around. A connection the proxy accepted and\n * then broke (ECONNRESET) is outside the retry set for the same reason —\n * the request may already have been written to it.\n */\n\nimport { isIP } from \"node:net\";\n\n/** Which per-request mechanism the host runtime actually honours. */\n/**\n * What one bounded attempt through a proxy established. \"indeterminate\" is a\n * first-class outcome: see probeProxyReachable.\n */\nexport type ProxyReachability = \"indeterminate\" | \"reachable\" | \"unreachable\";\n\nexport interface ProxyRuntimeConfig {\n /** Whether any proxy is configured. When false, every request goes direct. */\n enabled: boolean;\n /** Raw HTTP_PROXY URL (may carry credentials). Used for http targets. */\n httpProxy?: string;\n /** Raw HTTPS_PROXY URL (may carry credentials). Used for https targets. */\n httpsProxy?: string;\n /** Parsed NO_PROXY rules. */\n noProxy: NoProxyRule[];\n /**\n * The environment variable the operator's proxy was read from\n * (`HTTPS_PROXY` / `https_proxy` / `HTTP_PROXY` / `http_proxy`), carried\n * through from startup purely so `proxy_selected` can NAME the spelling. A\n * proxy set under one spelling and read under the other is the usual reason\n * egress looks configured to a human and absent to a program, and it is not\n * recoverable from the URL alone. Routing never consults it.\n */\n source?: string;\n}\n\nexport type ProxyRuntimeKind = \"bun\" | \"node\";\n\n/**\n * Test seam for the two things that cannot be observed on the host runtime:\n * the OTHER runtime's branch, and a failing `undici` load. Passing `{}` restores\n * real detection and the real loader.\n */\nexport interface ProxyRuntimeOverrides {\n kind?: ProxyRuntimeKind;\n loadUndici?: () => Promise<UndiciProxyModule>;\n}\n\n/**\n * The slice of `undici` the Node path needs. Declared structurally, and loaded\n * through a DYNAMIC import, so the Bun binary never pulls undici in and tests\n * can substitute a counting stub.\n */\nexport interface UndiciProxyModule {\n ProxyAgent: new (uri: string) => unknown;\n}\n\n/**\n * `RequestInit` plus the two runtime-specific routing fields, neither of which\n * is in the lib types: `proxy` is Bun's per-request proxy URL, `dispatcher` is\n * undici's per-request dispatcher (an undici `ProxyAgent` on the Node path).\n * Exactly one is ever set, by `buildProxyInit`.\n */\n// `dispatcher` is omitted from the base and re-declared as `unknown` because\n// @types/node already types it as undici's `Dispatcher`, which the structurally\n// loaded ProxyAgent (see UndiciProxyModule) does not nominally satisfy.\ntype ProxyFetchInit = { dispatcher?: unknown; proxy?: string } & Omit<\n RequestInit,\n \"dispatcher\"\n>;\n\n/**\n * Which runtime this process is, decided ONCE at module load.\n *\n * Both signals are checked because neither is guaranteed alone: `globalThis.Bun`\n * is absent in some Bun contexts that still expose `process.versions.bun`, and a\n * Node process can be made to define a `Bun` global. Anything that is not\n * positively Bun is treated as Node, which is the fail-safe direction: the Node\n * branch passes an explicit dispatcher, so a misdetection proxies through undici\n * rather than silently going direct.\n */\nfunction detectProxyRuntime(): ProxyRuntimeKind {\n if (typeof (globalThis as { Bun?: unknown }).Bun !== \"undefined\") return \"bun\";\n if (typeof process !== \"undefined\" && typeof process.versions?.bun === \"string\") return \"bun\";\n return \"node\";\n}\n\nconst DETECTED_RUNTIME: ProxyRuntimeKind = detectProxyRuntime();\nconst loadUndiciModule = (): Promise<UndiciProxyModule> =>\n import(\"undici\") as unknown as Promise<UndiciProxyModule>;\n\nlet runtimeKind: ProxyRuntimeKind = DETECTED_RUNTIME;\nlet loadUndici: () => Promise<UndiciProxyModule> = loadUndiciModule;\n\n/**\n * undici `ProxyAgent`s, keyed by the exact proxy URL they were built for.\n *\n * One agent per proxy URL, NOT one per request: a `ProxyAgent` owns a connection\n * pool, so building one per call leaks sockets for the lifetime of a\n * long-running gateway. Promises are cached rather than resolved agents so\n * concurrent first requests to the same proxy share a single construction\n * instead of racing to build several.\n */\nconst proxyAgents = new Map<string, Promise<unknown>>();\n\n/**\n * A single parsed NO_PROXY entry, mirroring `golang.org/x/net/http/httpproxy`:\n * - `cidr` — an IPv4 network (base masked, unsigned int32).\n * - `cidr6` — an IPv6 network (base masked, 128-bit BigInt).\n * - `domain` — a domain suffix. `matchHost` distinguishes a bare entry\n * (`example.com`, matches the domain itself AND its subdomains) from a\n * leading-dot / `*.` entry (`.example.com` / `*.example.com`, subdomains\n * ONLY). An optional `port` restricts the rule to that target port.\n * - `ip` — a single IP literal, with an optional `port`.\n * - `wildcard` — `*`, bypasses everything.\n */\ntype NoProxyRule =\n | { base: bigint; bits: number; kind: \"cidr6\" }\n | { base: number; bits: number; kind: \"cidr\" }\n | { kind: \"domain\"; matchHost: boolean; port?: string; suffix: string; }\n | { kind: \"ip\"; port?: string; value: string; }\n | { kind: \"wildcard\" };\n\n/** Read-only view of the routing mechanism in force (for logging / tests). */\nexport function getProxyRuntimeKind(): ProxyRuntimeKind {\n return runtimeKind;\n}\n\n/**\n * Replace the detected runtime and/or the `undici` loader. Test-only; `{}`\n * resets both and clears the agent cache so no agent leaks across cases.\n */\nexport function overrideProxyRuntime(overrides: ProxyRuntimeOverrides = {}): void {\n runtimeKind = overrides.kind ?? DETECTED_RUNTIME;\n loadUndici = overrides.loadUndici ?? loadUndiciModule;\n proxyAgents.clear();\n}\n\n/**\n * Whether a proxy ANSWERS, decided by one bounded attempt through it.\n *\n * Lives here, beside `isProxyUnreachable` and `buildProxyInit`, so there is\n * exactly ONE definition of \"the proxy is not there\" and one way of dialling a\n * proxy in this codebase. A separate probe (a raw TCP connect, a DNS lookup)\n * would be a second mechanism that could disagree with the request path's own\n * verdict, and then a request would fall back where the probe said reachable, or\n * the reverse.\n *\n * The target is a RESERVED name (RFC 2606 `.invalid`) that can never resolve and\n * can never belong to anyone, because the only hop under test is the one to the\n * PROXY. A proxy that is up answers — very likely 502, since it cannot resolve\n * the target — and an answer of ANY status proves the hop. A proxy that is not\n * there fails the connect instead. So the probe reaches no real host and sends no\n * real traffic anywhere.\n *\n * Three outcomes, not two, and the third is the important one: a timeout, an\n * abort or an unrecognised error is \"indeterminate\" — \"could not observe\" is NOT\n * \"observed absent\", and a caller that collapses the two will act on a proxy's\n * absence it never established.\n */\nexport async function probeProxyReachable(\n proxyUrl: string,\n opts: { fetchImpl?: typeof fetch; timeoutMs?: number } = {},\n): Promise<ProxyReachability> {\n const timeoutMs = opts.timeoutMs ?? PROXY_PROBE_TIMEOUT_MS;\n const doFetch = opts.fetchImpl ?? globalThis.fetch;\n\n // Normalize FIRST, exactly as selectProxy does before handing a proxy to the\n // request path. A scheme-less `host:port` is a real arrival shape (the scanner\n // side treats it as one) and WHATWG parses it as protocol \"host:\" with an EMPTY\n // hostname — so without this the guard below rejected it as undialable and\n // returned \"indeterminate\", while the request path happily dialled the\n // http://-prefixed form. That combination is the worst one available: the gate\n // concludes nothing and the proxy is handed to the wrapped server, which is the\n // hang this probe exists to prevent, for the common scheme-less spelling.\n const dialable = normalizeProxyURL(proxyUrl);\n\n // A proxy URL this lane could never dial says nothing about reachability; it is\n // a misconfiguration, and the request path fails it CLOSED rather than treating\n // it as an absent proxy. Match that: never report such a value \"unreachable\".\n if (!proxyHostname(dialable) || unsupportedProxyProtocol(dialable)) return \"indeterminate\";\n\n const controller = new AbortController();\n let timer: ReturnType<typeof setTimeout> | undefined;\n // The bound is enforced by RACING the timer, not merely by aborting the signal.\n // Abort is cooperative — a transport that does not honour the signal would hang\n // here forever, and this sits on the startup path of a program an agent is\n // blocked on, so an unbounded wait is a hung gateway rather than a slow probe.\n const expiry = new Promise<\"indeterminate\">((resolve) => {\n timer = setTimeout(() => {\n // Still abort, so the in-flight request is cancelled rather than leaked.\n controller.abort();\n resolve(\"indeterminate\");\n }, timeoutMs);\n });\n\n const attempt = (async (): Promise<ProxyReachability> => {\n try {\n const init = await buildProxyInit({ method: \"HEAD\", signal: controller.signal }, dialable);\n await doFetch(PROXY_PROBE_TARGET, init as RequestInit);\n // Any response at all — including 407 and 502 — means the proxy answered.\n return \"reachable\";\n } catch (err) {\n // The bound expiring is exactly the ambiguous case: we learned nothing.\n if (controller.signal.aborted) return \"indeterminate\";\n if (isProxyUnreachable(err)) return \"unreachable\";\n // Under undici a 407/403 REJECTS rather than returning a Response, and\n // isProxyUnreachable deliberately excludes it. That is still a proxy that\n // ANSWERED.\n return \"reachable\";\n }\n })();\n\n try {\n return await Promise.race([attempt, expiry]);\n } finally {\n clearTimeout(timer);\n }\n}\n\n/**\n * The ONE place the runtime split is applied: attach the selected proxy to a\n * request in whichever form the host runtime honours.\n *\n * `kind` is a defaulted parameter purely so a test can assert the branch its own\n * runtime cannot reach; the request path never passes it.\n */\nasync function buildProxyInit(\n init: RequestInit | undefined,\n proxyUrl: string,\n kind: ProxyRuntimeKind = runtimeKind,\n): Promise<ProxyFetchInit> {\n if (kind === \"bun\") return { ...init, proxy: proxyUrl };\n // Node: a failure to obtain the dispatcher must FAIL CLOSED. Continuing\n // without one is a direct connection around the operator's egress control —\n // the precise leak this module exists to prevent — so the error propagates.\n const dispatcher = await nodeDispatcherFor(proxyUrl);\n return { ...init, dispatcher };\n}\n\n/**\n * The cached `ProxyAgent` for a proxy URL, built on first use.\n *\n * Credentials in the URL need no special handling: undici derives\n * `Proxy-Authorization` from the userinfo itself (verified against a real\n * listener — `http://user:pass@host` produced `Basic dXNlcjpwYXNz` with no\n * explicit `token` option), matching what Bun does on its own path.\n *\n * A failed load is NOT cached, so a transient failure does not poison the entry\n * for the process's lifetime.\n */\nfunction nodeDispatcherFor(proxyUrl: string): Promise<unknown> {\n const cached = proxyAgents.get(proxyUrl);\n if (cached) return cached;\n const pending = loadUndici().then(({ ProxyAgent }) => new ProxyAgent(proxyUrl));\n proxyAgents.set(proxyUrl, pending);\n pending.catch(() => proxyAgents.delete(proxyUrl));\n return pending;\n}\n\n/**\n * The bound on `probeProxyReachable`. Short on purpose: it sits on the startup\n * path of a program an agent is waiting on, and a proxy on a corporate LAN either\n * answers in a few hundred milliseconds or is not there. Overrunning it yields\n * \"indeterminate\", which preserves the caller's existing behaviour rather than\n * acting on a guess.\n */\nconst PROXY_PROBE_TIMEOUT_MS = 2000;\n\n/**\n * Reserved, never-resolvable probe target (RFC 2606). See probeProxyReachable:\n * only the hop to the proxy is under test, so the target must not be a real host.\n */\nconst PROXY_PROBE_TARGET = \"http://onyx-proxy-probe.invalid/\";\n\nconst DISABLED: ProxyRuntimeConfig = { enabled: false, noProxy: [] };\n\nlet config: ProxyRuntimeConfig = DISABLED;\n\n/**\n * Install the runtime proxy configuration. Called once at startup by\n * `setupProxyDispatcher()` (in the re-exec'd child, or with `enabled: false`\n * when no proxy env is present). Safe to call again in tests.\n */\nexport function configureProxy(next: ProxyRuntimeConfig): void {\n config = next;\n proxyEventLogged.clear();\n // The proxy URLs may have changed; agents built for the old ones are dead\n // weight holding open sockets.\n proxyAgents.clear();\n}\n\n/**\n * The ONE memo behind every proxy routing line here, so a long-lived gateway\n * making many requests through the same proxy logs each decision ONCE rather\n * than once per request. Keys are namespaced by event\n * (`selected:<proxy>`, `fallback:<proxy>`, `nofallback:<proxy>|<target>|<reason>`)\n * so the three lines cannot suppress one another. Cleared by configureProxy so a\n * config reload re-arms every warning.\n */\nconst proxyEventLogged = new Set<string>();\n\n/** Read-only view of the active configuration (for logging / tests). */\nexport function getProxyConfig(): ProxyRuntimeConfig {\n return config;\n}\n\n/**\n * Parse a raw NO_PROXY string into rules, mirroring\n * `golang.org/x/net/http/httpproxy`. Entries are comma-separated; each is an\n * IP, a CIDR block, a domain (bare or dot-prefixed, suffix-matched), or `*`.\n */\nexport function parseNoProxy(raw: string | undefined): NoProxyRule[] {\n if (!raw) return [];\n const rules: NoProxyRule[] = [];\n for (const rawEntry of raw.split(\",\")) {\n const entry = rawEntry.trim().toLowerCase();\n if (!entry) continue;\n if (entry === \"*\") return [{ kind: \"wildcard\" }];\n\n // CIDR first (IPv4 or IPv6), mirroring Go's ParseCIDR-first order. An\n // unparseable CIDR is DROPPED rather than reinterpreted as a domain rule\n // that would never match its intended network.\n if (entry.includes(\"/\")) {\n const cidr = parseCidr(entry);\n if (cidr) rules.push(cidr);\n continue;\n }\n\n // Split an optional literal port (Go consults net.SplitHostPort here), so\n // `example.com:80` and `[::1]:8080` keep their port for port-scoped rules.\n const { host, port } = splitHostPort(entry);\n if (!host) continue;\n\n if (isIP(host) !== 0) {\n rules.push({ kind: \"ip\", value: normalizeIp(host), ...(port ? { port } : {}) });\n continue;\n }\n\n // Domain rule. Mirror httpproxy exactly: a bare entry matches the domain\n // itself and its subdomains (matchHost); a leading-dot or `*.` entry is\n // subdomains ONLY. `*.example.com` normalizes to `.example.com`.\n let h = host;\n let matchHost: boolean;\n if (h.startsWith(\"*.\")) {\n h = h.slice(1); // \"*.example.com\" -> \".example.com\"\n matchHost = false;\n } else if (h.startsWith(\".\")) {\n matchHost = false;\n } else {\n matchHost = true;\n }\n const suffix = h.startsWith(\".\") ? h.slice(1) : h;\n if (suffix) rules.push({ kind: \"domain\", matchHost, suffix, ...(port ? { port } : {}) });\n }\n return rules;\n}\n\n/** Message of an error-ish value, for the log line. */\nfunction describeError(err: unknown): string {\n if (err instanceof Error) return err.message;\n return String(err);\n}\n\n/**\n * Emit a single structured warning the first time gateway egress falls back to a\n * DIRECT connection because a configured proxy could not be REACHED.\n *\n * This is a security-relevant decision — egress the operator pointed at a proxy\n * is instead leaving DIRECT — so it must be visible where it can silently do the\n * wrong thing. The field NAMES are shared verbatim with the scanner's Go helper\n * (`scanner/internal/proxyhttp/proxyhttp.go` → logDirectFallbackOnce) —\n * event/op/outcome/proxy_host/target_host/platform/error — so ONE Groundcover\n * query over event=proxy_fallback covers both Onyx egress surfaces. Two honest\n * differences, both intended: `op` is the lane discriminator, so its VALUE differs\n * (`fetch` here, `round_trip` there); and `reason` is TS-only, carrying the fetch\n * layer's error CODES (`ECONNREFUSED`, and each code of an AggregateError), which\n * is what `isProxyUnreachable` actually judged and which the Go side has no\n * analogue for — there the single proxyconnect Op is the whole verdict. Renaming\n * any shared field is a schema break on both surfaces.\n *\n * It routes through the winston `logger` (→ rotating file + the Groundcover\n * ingest transport), NEVER stdout/stderr, which are reserved for the JSON-RPC\n * stream in stdio mode. The logger is imported LAZILY and fire-and-forget: the\n * ingest transport's own egress uses proxiedFetch, so a static `import { logger }`\n * here would close an initialization cycle\n * (proxyFetch → logger → ingestTransport → proxyFetch). The dynamic import breaks\n * that cycle and keeps the fallback off any log-flush await; the host is marked\n * BEFORE the import resolves so concurrent requests to the same proxy host cannot\n * race a duplicate line.\n */\nfunction logDirectFallback(proxyHost: string, targetHost: string, cause: unknown): void {\n if (!logOnce(`fallback:${proxyHost}`)) return;\n const reason = errorCodes(cause).join(\",\") || describeError(cause);\n void loadLogger()\n .then(({ logger }) => {\n logger.warn(\"Configured proxy is unreachable; routing this egress DIRECT\", {\n error: describeError(cause),\n event: \"proxy_fallback\",\n op: \"fetch\",\n outcome: \"direct\",\n platform: process.platform,\n proxy_host: proxyHost,\n reason,\n target_host: targetHost,\n });\n })\n .catch(() => {\n // A logging failure must never crash egress. Re-arm this host so a later\n // request can still surface the fallback rather than losing it forever.\n proxyEventLogged.delete(`fallback:${proxyHost}`);\n });\n}\n\n/** True the first time `key` is seen; marks it seen. */\nfunction logOnce(key: string): boolean {\n if (proxyEventLogged.has(key)) return false;\n proxyEventLogged.add(key);\n return true;\n}\n\n/**\n * The winston logger, imported LAZILY and memoized, for the three routing lines\n * below.\n *\n * Lazy and dynamic because the ingest transport's own egress uses proxiedFetch, so\n * a static `import { logger }` here would close an initialization cycle\n * (proxyFetch -> logger -> ingestTransport -> proxyFetch).\n *\n * Memoized because two lines emitted in the SAME tick otherwise open two\n * concurrent `import()`s of the same module, and the second never settles under\n * the vitest module runner -- neither its `.then` nor its `.catch` ever runs, so\n * the line silently vanishes and any test asserting on it is unfalsifiable\n * (measured: 12 macrotask flushes, still nothing). One shared promise also drops\n * the repeated import from the request path. A REJECTION is not cached, so a\n * transient failure does not silence the lines for the process's lifetime.\n */\nlet loggerModule: Promise<{ logger: ProxyFetchLogger }> | undefined;\n\n/** The slice of the winston logger these lines use. */\ninterface ProxyFetchLogger {\n info: (message: string, meta?: Record<string, unknown>) => void;\n warn: (message: string, meta?: Record<string, unknown>) => void;\n}\n\n/**\n * Whether a rejection is the CALLER's cancellation rather than any statement\n * about the proxy. Used only to suppress a log line — the routing decision is\n * `isProxyUnreachable`'s alone, and this must never be consulted by it.\n *\n * Both signals are checked because either can arrive alone: the rejection may be\n * an AbortError with no signal on the init (an internal timeout controller the\n * caller did not hand us), and a signal may be aborted while the runtime reports\n * some other error for the interrupted connect.\n */\nfunction isCallerAbort(err: unknown, init: RequestInit | undefined): boolean {\n if (init?.signal?.aborted) return true;\n if (err instanceof DOMException && err.name === \"AbortError\") return true;\n return (err as { name?: unknown } | null)?.name === \"AbortError\";\n}\n\nfunction loadLogger(): Promise<{ logger: ProxyFetchLogger }> {\n loggerModule ??= (import(\"./logger.js\") as Promise<{ logger: ProxyFetchLogger }>).catch(\n (err: unknown) => {\n loggerModule = undefined;\n throw err;\n },\n );\n return loggerModule;\n}\n\n/**\n * Emit a single structured WARNING when a proxied request FAILED and the\n * allow-list deliberately did NOT treat it as an unreachable proxy — so no\n * direct retry ran and the request hard-failed.\n *\n * This is the state that burned a customer: a wrapped MCP server hung and no line\n * anywhere said why the fallback stayed shut. `reason` carries exactly what\n * `isProxyUnreachable` judged — the error CODES found on the rejection, its cause\n * chain and any aggregated `errors[]` — so a 407 under undici reads\n * `UND_ERR_ABORTED` and an ECONNRESET reads `ECONNRESET`, rather than both being\n * invisible. Keyed per (proxy, target, reason): unlike the fallback, whose verdict\n * is a property of the proxy alone, a fail-closed failure can differ per target,\n * and each distinct shape is a separate diagnosis.\n */\nfunction logNoFallback(proxyHost: string, targetHost: string, cause: unknown): void {\n const reason = errorCodes(cause).join(\",\") || describeError(cause);\n if (!logOnce(`nofallback:${proxyHost}|${targetHost}|${reason}`)) return;\n void loadLogger()\n .then(({ logger }) => {\n logger.warn(\"Proxied request failed and was NOT retried direct; egress stays fail-closed\", {\n error: describeError(cause),\n event: \"proxy_no_fallback\",\n op: \"fetch\",\n outcome: \"failed\",\n platform: process.platform,\n proxy_host: proxyHost,\n reason,\n target_host: targetHost,\n });\n })\n .catch(() => {\n proxyEventLogged.delete(`nofallback:${proxyHost}|${targetHost}|${reason}`);\n });\n}\n\n/**\n * Emit a single structured INFO line the first time gateway egress is actually\n * ROUTED through a configured proxy.\n *\n * It exists because the absence of `proxy_fallback` was ambiguous: \"the proxy is\n * working\" and \"no proxy was ever configured\" produced the same empty log, which\n * are the two states an operator most needs to separate when egress misbehaves.\n * Field names are the fallback line's, so one Groundcover query can join them —\n * `outcome` is \"proxied\" rather than \"direct\", and `source` names the env var\n * spelling startup read the proxy from.\n *\n * Same lazy, fire-and-forget logger import as logDirectFallback, and for the same\n * initialization-cycle reason; see that function. NEVER stdout/stderr, which are\n * the JSON-RPC stream in stdio mode.\n */\nfunction logProxySelected(proxyHost: string, source: string | undefined): void {\n if (!logOnce(`selected:${proxyHost}`)) return;\n void loadLogger()\n .then(({ logger }) => {\n logger.info(\"Egress is routed through the configured proxy\", {\n event: \"proxy_selected\",\n op: \"fetch\",\n outcome: \"proxied\",\n platform: process.platform,\n proxy_host: proxyHost,\n source: source ?? \"\",\n });\n })\n .catch(() => {\n proxyEventLogged.delete(`selected:${proxyHost}`);\n });\n}\n\n/**\n * The one proxy-honoring fetch. Typed as `typeof fetch`, so it is a drop-in for\n * `globalThis.fetch` and is assignable to the MCP SDK's `FetchLike`.\n */\nexport const proxiedFetch = (async (\n input: Parameters<typeof fetch>[0],\n init?: RequestInit,\n): Promise<Response> => {\n if (!config.enabled) return globalThis.fetch(input, init);\n\n let target: URL;\n try {\n target = toURL(input);\n } catch {\n // Unparseable target: nothing to route. The env is stripped, so a plain\n // fetch is a genuine direct connection (no accidental env proxying).\n return globalThis.fetch(input, init);\n }\n\n const proxy = selectProxy(target);\n if (!proxy) return globalThis.fetch(input, init);\n\n // The fallback is keyed on the ATTEMPT, not on a DNS probe of the proxy host\n // (mirroring the Go helper): whatever makes the proxy unreachable — it refuses\n // the connection, blackholes the SYN, or its host does not resolve — surfaces as\n // a fetch rejection that isProxyUnreachable recognizes, and only then is the\n // request retried direct. A proxy that ANSWERS is never bypassed. The host is\n // still derived HERE, up front, for the two fail-closed checks below and to name\n // the proxy in the log line.\n const proxyHost = proxyHostname(proxy);\n if (!proxyHost) {\n // An unparseable proxy URL (e.g. HTTPS_PROXY=not-a-url) names no proxy at\n // all. This is a misconfiguration, NOT a proxy that was tried and found\n // unreachable, so it must NOT silently leak the request direct around the\n // enterprise egress control. Fail closed with a clear error; only a real\n // attempt at a real proxy host may open the direct fallback.\n throw new Error(\n \"Configured proxy URL has no parseable host; refusing to route direct around the egress proxy\",\n );\n }\n // An explicit scheme this lane cannot dial (socks4://, a typo like htp://) must\n // fail CLOSED, before any attempt. Attempting it is the danger: the proxy host of\n // such a value is meaningless, so the attempt fails in exactly the way\n // isProxyUnreachable recognizes, which would open the direct fallback and leak\n // the request around the egress control. A misconfigured scheme is not a proxy\n // that was tried and found absent.\n const badProtocol = unsupportedProxyProtocol(proxy);\n if (badProtocol) {\n throw new Error(\n `Configured proxy URL uses an unsupported proxy scheme \"${badProtocol}\"; refusing to route direct around the egress proxy`,\n );\n }\n // Naming the proxy BEFORE the attempt is what makes state (a) — \"proxying\n // fine\" — visible at all; without it, no log line distinguishes it from \"no\n // proxy configured\". Routing is untouched: this runs after every selection and\n // fail-closed check, on a value already computed, and the memo keeps it to one\n // line per proxy rather than one per request.\n logProxySelected(proxyHost, config.source);\n const withProxy = await buildProxyInit(init, proxy);\n try {\n // Under Bun a 407/403/502 from the proxy arrives HERE as an ordinary\n // Response, not a rejection (verified on Bun 1.3.14, for both an http\n // absolute-form request and an https CONNECT), so an answering proxy's\n // verdict is returned to the caller untouched. Under undici the same\n // refusal REJECTS with `UND_ERR_ABORTED`, which `isProxyUnreachable`\n // excludes, so it surfaces as a failure rather than a direct retry. Either\n // way an answering proxy is never bypassed.\n return await globalThis.fetch(input, withProxy as RequestInit);\n } catch (err) {\n if (!isProxyUnreachable(err)) {\n // A proxied request failed and the allow-list rejected it, so nothing is\n // retried and the caller gets a hard failure. That decision used to be\n // entirely silent. Report it, then re-throw unchanged.\n //\n // Except a CANCELLATION. An abort also lands here (isProxyUnreachable\n // excludes AbortError deliberately), but it is the caller ending the\n // request, not a verdict about the proxy — reporting it as a fail-closed\n // proxy decision would blame the proxy for the caller's own choice and\n // bury the real fail-closed lines under cancellation noise.\n if (!isCallerAbort(err, init)) logNoFallback(proxyHost, target.hostname, err);\n throw err;\n }\n // A caller's own abort is a cancellation, not a routing decision (see\n // isProxyUnreachable), so it is neither a fallback nor a fail-closed verdict\n // and gets no line of its own.\n if (init?.signal?.aborted) throw err;\n // The proxy could not be reached, so nothing was sent: retry DIRECT. This is\n // deliberate egress AROUND the operator's control, surfaced once per proxy\n // target (this path runs on every request) so it is visible in the gateway\n // log / ingest rather than silent.\n logDirectFallback(proxyHost, target.hostname, err);\n return globalThis.fetch(input, init);\n }\n}) as typeof fetch;\n\n/** Depth of the cause/errors graph that is searched for a code. */\nconst CODE_WALK_MAX_DEPTH = 6;\n/** Nodes visited before the search gives up, so no graph can make it spin. */\nconst CODE_WALK_MAX_NODES = 32;\n\n/**\n * Error codes carried by `err`, its `cause` chain, and any aggregated\n * `errors[]` beneath either.\n *\n * Both runtimes nest the real cause: Bun sets `code` on the thrown Error\n * itself, undici (used when the gateway runs under Node, as it does in tests)\n * sets it on `cause`.\n *\n * The `errors[]` arm is what makes a DUAL-STACK proxy host work. Node dials\n * such a host once per address family (happy eyeballs) and reports the combined\n * failure as an `AggregateError` — `net`'s `internalConnectMultiple` destroys\n * the socket with `new NodeAggregateError(context.errors)`, and that class is\n * defined as `this.code = errors[0]?.code`. So the aggregate's own `code` is\n * the FIRST attempt's and nothing more, and it carries no `cause` at all:\n * walking only `code`/`cause` sees one attempt out of several. When that first\n * attempt failed for a reason not on the allow-list (or with no code, leaving\n * the aggregate codeless) the real \"the proxy was never reached\" verdict sits\n * unread in `errors[1]`, and an unreachable proxy hard-fails instead of falling\n * back — the exact condition the direct fallback exists for.\n *\n * Widening the search cannot weaken the decision: the caller is an allow-list,\n * so finding MORE codes can only ever find one that means the proxy was never\n * reached. A code meaning the proxy answered still opens nothing.\n *\n * The traversal is breadth-first over a graph rather than a walk down a chain,\n * so it is bounded three ways — depth, total nodes visited, and an identity set\n * — and a cyclic or pathologically wide chain terminates instead of spinning.\n */\nfunction errorCodes(err: unknown): string[] {\n const codes: string[] = [];\n const seen = new Set<object>();\n const queue: { depth: number; node: unknown }[] = [{ depth: 0, node: err }];\n for (let visited = 0; queue.length > 0 && visited < CODE_WALK_MAX_NODES; ) {\n const { depth, node } = queue.shift() as { depth: number; node: unknown };\n if (node == null || typeof node !== \"object\") continue;\n if (seen.has(node)) continue;\n seen.add(node);\n visited++;\n\n const { cause, code, errors } = node as {\n cause?: unknown;\n code?: unknown;\n errors?: unknown;\n };\n if (typeof code === \"string\") codes.push(code);\n if (depth + 1 >= CODE_WALK_MAX_DEPTH) continue;\n if (cause != null) queue.push({ depth: depth + 1, node: cause });\n if (Array.isArray(errors)) {\n for (const nested of errors) queue.push({ depth: depth + 1, node: nested });\n }\n }\n return codes;\n}\n\n/**\n * Error codes that mean the request never reached a server because the PROXY\n * could not be reached — the ONLY case that opens the direct retry.\n *\n * Measured on Bun 1.3.14 (the version embedded in the shipped linux binary),\n * driving real listeners through a per-request `proxy`:\n * - proxy port closed -> code \"ConnectionRefused\" (http and https)\n * - proxy host unresolvable -> \"ConnectionRefused\" (http target),\n * \"FailedToOpenSocket\" (https target)\n * - proxy address blackholed -> \"ConnectionRefused\", after the OS connect\n * timeout (measured: 75s on macOS) — so a\n * timed-out connect needs no code of its own\n * - proxy answers 407 -> NO rejection at all; a 407 Response\n * - proxy accepts then hangs up -> \"ECONNRESET\"\n * The undici/Node spellings are listed alongside because the gateway's own test\n * suite and any node-hosted run raise those instead.\n *\n * ECONNRESET and every unlisted code are deliberately ABSENT: this is an\n * allow-list, so an unrecognised failure retries nothing and the request fails\n * exactly as it does today rather than silently leaving the egress proxy.\n */\n// Sorted, not grouped by runtime: the lint rule orders this set alphabetically.\n// \"ConnectionRefused\" and \"FailedToOpenSocket\" are Bun's; the rest are undici's.\nconst UNREACHABLE_CODES = new Set([\n \"ConnectionRefused\",\n \"EAI_AGAIN\",\n // A name that exists with no address record (NODATA) reaches us as EAI_NODATA\n // or EAI_NONAME on the Node/npx path rather than as ENOTFOUND. That is the\n // real customer proxy shape -- present in the zone with MX records and no\n // A/AAAA -- so omitting these two made exactly that host hard-fail instead of\n // retrying direct.\n \"EAI_NODATA\",\n \"EAI_NONAME\",\n \"ECONNREFUSED\",\n \"EHOSTUNREACH\",\n \"ENETUNREACH\",\n \"ENOTFOUND\",\n \"ETIMEDOUT\",\n \"FailedToOpenSocket\",\n \"UND_ERR_CONNECT_TIMEOUT\",\n]);\n\n/**\n * Whether a fetch rejection means the proxy was never reached. Exported for\n * tests. A caller's own abort is NOT a routing decision — it is a cancellation\n * to surface — so `AbortError` is excluded even though it can interrupt a proxy\n * connect.\n */\nexport function isProxyUnreachable(err: unknown): boolean {\n if (err == null) return false;\n if (err instanceof DOMException && err.name === \"AbortError\") return false;\n if ((err as { name?: unknown }).name === \"AbortError\") return false;\n return errorCodes(err).some((code) => UNREACHABLE_CODES.has(code));\n}\n\nfunction ipv4ToInt(ip: string): number {\n const parts = ip.split(\".\");\n return (\n (((Number(parts[0]) << 24) >>> 0) +\n (Number(parts[1]) << 16) +\n (Number(parts[2]) << 8) +\n Number(parts[3])) >>>\n 0\n );\n}\n\n/**\n * Expand an IPv6 literal to its 128-bit unsigned value. Handles `::` zero\n * compression, an embedded IPv4 tail (`::ffff:1.2.3.4`), zone ids, and brackets.\n * Returns undefined for anything that is not a well-formed IPv6 address.\n */\nfunction ipv6ToBigInt(ip: string): bigint | undefined {\n let s = stripBrackets(ip.toLowerCase());\n const pct = s.indexOf(\"%\");\n if (pct !== -1) s = s.slice(0, pct); // drop a zone id (fe80::1%eth0)\n\n // An embedded IPv4 tail becomes the low two hextets.\n if (s.includes(\".\")) {\n const lastColon = s.lastIndexOf(\":\");\n if (lastColon === -1) return undefined;\n const octets = s.slice(lastColon + 1).split(\".\").map(Number);\n if (octets.length !== 4 || octets.some((n) => !Number.isInteger(n) || n < 0 || n > 255)) {\n return undefined;\n }\n const hi = ((octets[0] << 8) | octets[1]).toString(16);\n const lo = ((octets[2] << 8) | octets[3]).toString(16);\n s = `${s.slice(0, lastColon + 1)}${hi}:${lo}`;\n }\n\n const halves = s.split(\"::\");\n if (halves.length > 2) return undefined;\n const head = halves[0] === \"\" ? [] : halves[0].split(\":\");\n const tail = halves.length === 2 && halves[1] !== \"\" ? halves[1].split(\":\") : [];\n let groups: string[];\n if (halves.length === 2) {\n const missing = 8 - head.length - tail.length;\n if (missing < 0) return undefined; // `::` must stand in for ≥1 zero group\n groups = [...head, ...Array<string>(missing).fill(\"0\"), ...tail];\n } else {\n groups = head;\n }\n if (groups.length !== 8) return undefined;\n\n let value = 0n;\n for (const group of groups) {\n if (!/^[0-9a-f]{1,4}$/.test(group)) return undefined;\n value = (value << 16n) | BigInt(parseInt(group, 16));\n }\n return value;\n}\n\n/**\n * Loopback targets are ALWAYS direct, mirroring Go's httpproxy.useProxy, which\n * bypasses \"localhost\" and any loopback IP BEFORE consulting NO_PROXY. Without\n * this the gateway routes local MCP servers (localhost / 127.0.0.0/8 / ::1)\n * through the corporate proxy — which cannot reach them — whenever NO_PROXY does\n * not happen to list loopback.\n */\nfunction isLoopbackHost(hostname: string): boolean {\n const host = stripBrackets(hostname.toLowerCase());\n if (host === \"localhost\") return true;\n const v = isIP(host);\n if (v === 4) return host === \"127.0.0.1\" || host.startsWith(\"127.\");\n if (v === 6) return host === \"::1\";\n return false;\n}\n\nfunction maskFromBits(bits: number): number {\n return bits === 0 ? 0 : (0xffffffff << (32 - bits)) >>> 0;\n}\n\nfunction matchesNoProxy(hostname: string, port: string): boolean {\n const host = stripBrackets(hostname.toLowerCase());\n const fam = isIP(host);\n const hostIp = fam !== 0 ? normalizeIp(host) : undefined;\n const hostV4 = fam === 4 ? ipv4ToInt(host) : undefined;\n const hostV6 = fam === 6 ? ipv6ToBigInt(host) : undefined;\n\n for (const rule of config.noProxy) {\n switch (rule.kind) {\n case \"cidr\":\n // `&` returns a SIGNED int32 in JS, so for any host whose first octet is\n // >= 128 the masked value is negative while rule.base is stored unsigned\n // (`>>> 0`). Coerce back to unsigned before comparing, or every CIDR above\n // 127.x silently fails to match.\n if (\n hostV4 !== undefined &&\n ((hostV4 & maskFromBits(rule.bits)) >>> 0) === rule.base\n ) {\n return true;\n }\n break;\n case \"cidr6\":\n // IPv6 network match via a 128-bit BigInt prefix compare. `rule.base` is\n // already masked to the prefix; a /0 shifts everything away and matches all.\n if (hostV6 !== undefined) {\n const shift = BigInt(128 - rule.bits);\n if (hostV6 >> shift === rule.base >> shift) return true;\n }\n break;\n case \"domain\":\n if (\n (host.endsWith(`.${rule.suffix}`) || (rule.matchHost && host === rule.suffix)) &&\n (rule.port === undefined || rule.port === port)\n ) {\n return true;\n }\n break;\n case \"ip\":\n if (\n hostIp !== undefined &&\n hostIp === rule.value &&\n (rule.port === undefined || rule.port === port)\n ) {\n return true;\n }\n break;\n case \"wildcard\":\n return true;\n }\n }\n return false;\n}\n\n/** Canonicalize an IP so equal addresses compare equal (lower-cased). */\nfunction normalizeIp(ip: string): string {\n return stripBrackets(ip).toLowerCase();\n}\n\n/** Proxy schemes this lane can actually dial. */\nconst RECOGNIZED_PROXY_PROTOCOLS = new Set([\"http:\", \"https:\", \"socks5:\", \"socks5h:\"]);\n\n/**\n * Normalize a proxy value to a URL carrying an explicit scheme, mirroring Go's\n * httpproxy.parseProxy EXACTLY: a value that parses with a non-empty host is\n * returned verbatim; anything else — a scheme-less `host:port` such as\n * `proxy.example.com:8080` or `10.0.0.1:3128`, whose first token `new URL` mistakes\n * for a scheme and which therefore yields an empty hostname — is retried with\n * `http://` prepended, exactly what curl and the installer's shell helper accept. A\n * genuinely unparseable value is returned as-is so the caller's fail-closed path\n * still rejects it (proxyHostname → \"\" → throw), never leaking direct.\n *\n * The host test, rather than a recognized-scheme test, is what keeps an explicit but\n * UNSUPPORTED scheme intact. Retrying `socks4://proxy.corp:1080` produced\n * `http://socks4//proxy.corp:1080` — hostname the literal `socks4`, real host demoted\n * into the path — and the attempt against the nonexistent host `socks4` then failed\n * in exactly the way isProxyUnreachable recognizes, opening the direct fallback and\n * silently routing the request around the operator's egress control. Go keeps such a URL and errors at dial; proxiedFetch\n * rejects it up front instead, which is the same fail-closed outcome with a message\n * that names the cause.\n */\nfunction normalizeProxyURL(proxy: string): string {\n try {\n // Go's parseProxy retries only when Scheme or Host is empty. A WHATWG URL with a\n // non-empty hostname always has both, so this single test is that condition.\n if (new URL(proxy).hostname) return proxy;\n } catch {\n // Unparseable as-is (e.g. scheme-less IP:port); fall through to the retry.\n }\n try {\n if (new URL(`http://${proxy}`).hostname) return `http://${proxy}`;\n } catch {\n // Prepending http:// still does not yield a valid URL — leave unchanged.\n }\n return proxy;\n}\n\n/**\n * Parse a CIDR entry (IPv4 or IPv6). Mirrors Go's `net.ParseCIDR`: an IPv4 CIDR\n * yields an unsigned-int32 `cidr` rule, an IPv6 CIDR a 128-bit BigInt `cidr6`\n * rule. Returns undefined (caller drops the entry) for anything unparseable.\n */\nfunction parseCidr(entry: string): NoProxyRule | undefined {\n const slash = entry.indexOf(\"/\");\n if (slash < 0) return undefined;\n const addr = entry.slice(0, slash);\n const bitsText = entry.slice(slash + 1);\n // Number(\"\") === 0 (and Number(\" \") === 0, Number(\"0x1F\") === 31), so an\n // empty/blank/non-decimal prefix such as \"10.0.0.0/\" would yield bits=0 and a\n // /0 mask that matches EVERY host — a silent, proxy-wide NO_PROXY bypass.\n // Require an explicit run of decimal digits before trusting Number().\n if (!/^\\d+$/.test(bitsText)) return undefined;\n const bits = Number(bitsText);\n if (!Number.isInteger(bits) || bits < 0) return undefined;\n const fam = isIP(addr);\n if (fam === 4) {\n if (bits > 32) return undefined;\n const mask = maskFromBits(bits);\n return { base: (ipv4ToInt(addr) & mask) >>> 0, bits, kind: \"cidr\" };\n }\n if (fam === 6) {\n if (bits > 128) return undefined;\n const v = ipv6ToBigInt(addr);\n if (v === undefined) return undefined;\n const shift = BigInt(128 - bits);\n return { base: (v >> shift) << shift, bits, kind: \"cidr6\" };\n }\n return undefined;\n}\n\n/** Hostname of a proxy URL, without IPv6 brackets. */\nfunction proxyHostname(proxyUrl: string): string {\n try {\n return stripBrackets(new URL(proxyUrl).hostname);\n } catch {\n return \"\";\n }\n}\n\n/**\n * Selects the proxy URL for a target, or undefined for a direct connection.\n * Applies scheme selection (mirroring Go) and NO_PROXY. It says nothing about\n * whether that proxy is REACHABLE — that is decided by the attempt itself, in\n * proxiedFetch, from the error the fetch rejects with.\n */\nfunction selectProxy(target: URL): string | undefined {\n const proxy =\n target.protocol === \"https:\"\n ? config.httpsProxy || config.httpProxy\n : config.httpProxy;\n if (!proxy) return undefined;\n if (isLoopbackHost(target.hostname)) return undefined;\n // Canonicalize the port so a port-scoped NO_PROXY entry (example.com:443)\n // matches an https target whose URL omits the default port, mirroring Go's\n // canonicalAddr, which always carries an explicit port.\n const port = target.port || (target.protocol === \"https:\" ? \"443\" : \"80\");\n if (matchesNoProxy(target.hostname, port)) return undefined;\n return normalizeProxyURL(proxy);\n}\n\n/**\n * Split a NO_PROXY entry into host + optional port, mirroring net.SplitHostPort:\n * a bracketed IPv6 (`[::1]:80`) unwraps to `::1`/`80`; a single-colon entry\n * (`example.com:80`) splits; a bare IPv6 (`::1`, many colons, no brackets) or a\n * plain host keeps the whole string as the host with no port.\n */\nfunction splitHostPort(entry: string): { host: string; port: string } {\n if (entry.startsWith(\"[\")) {\n const end = entry.indexOf(\"]\");\n if (end === -1) return { host: entry, port: \"\" };\n const host = entry.slice(1, end);\n const rest = entry.slice(end + 1);\n return { host, port: rest.startsWith(\":\") ? rest.slice(1) : \"\" };\n }\n const first = entry.indexOf(\":\");\n if (first !== -1 && first === entry.lastIndexOf(\":\")) {\n return { host: entry.slice(0, first), port: entry.slice(first + 1) };\n }\n return { host: entry, port: \"\" };\n}\n\nfunction stripBrackets(host: string): string {\n return host.startsWith(\"[\") && host.endsWith(\"]\") ? host.slice(1, -1) : host;\n}\n\nfunction toURL(input: Parameters<typeof fetch>[0]): URL {\n if (input instanceof URL) return input;\n if (typeof input === \"string\") return new URL(input);\n // Request-like object.\n return new URL((input as Request).url);\n}\n\n/**\n * The proxy's scheme if it carries one this lane cannot dial, else undefined.\n * Returns undefined for an unparseable value too: that is the caller's separate\n * \"no parseable host\" fail-closed path, which reports the misconfiguration better.\n */\nfunction unsupportedProxyProtocol(proxyUrl: string): string | undefined {\n try {\n const { protocol } = new URL(proxyUrl);\n return RECOGNIZED_PROXY_PROTOCOLS.has(protocol) ? undefined : protocol;\n } catch {\n return undefined;\n }\n}\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import{proxiedFetch as e}from"./proxyFetch-B2mideS8.js";import{config as t}from"./env-DEtMyxUe.js";import{logger as n}from"./logger-B95noHdD.js";import{OAuthError as r,OAuthErrorCode as i,SSEClientTransport as a,StreamableHTTPClientTransport as o,discoverAuthorizationServerMetadata as s,discoverOAuthProtectedResourceMetadata as c,exchangeAuthorization as l,normalizeUrl as u,refreshAuthorization as d,registerClient as f,startAuthorization as p}from"./normalizeUrl-DUUGubD4.js";import{findProviderForUrl as m}from"./providers-DijUIFfV.js";import{platform as h}from"node:os";import{URL as g}from"node:url";import{createServer as _}from"node:http";import{execFile as v}from"node:child_process";async function y(e){let t=e.toString(),r=h(),i=C(t);return new Promise((e,a)=>{let o,s;if(r===`win32`){let e=`Start-Process ${x(t)}`,n=b(e);o=S(),s=[`-NoProfile`,`-NonInteractive`,`-ExecutionPolicy`,`Bypass`,`-EncodedCommand`,n]}else r===`darwin`?(o=`open`,s=[t]):(o=`xdg-open`,s=[t]);v(o,s,(t,s,c)=>{if(t){let e={command:o,os:r,stderr:c,url:i};n.warn(`Failed to open browser automatically`,{...e,error:t.message});let s=Error(`Failed to open browser: ${t.message} (command: ${o}, os: ${r}, url: ${i})`);s.cause=t,a(s)}else n.debug(`Browser opened successfully`,{url:i}),e()})})}function b(e){return Buffer.from(e,`utf16le`).toString(`base64`)}function x(e){return`'${e.replaceAll(`'`,`''`)}'`}function S(){return`${process.env.SYSTEMROOT||process.env.windir||`C:\\Windows`}\\System32\\WindowsPowerShell\\v1.0\\powershell.exe`}function C(e){try{let t=new URL(e);return t.search?`${t.protocol}//${t.host}${t.pathname}?[REDACTED]`:`${t.protocol}//${t.host}${t.pathname}`}catch{return`[INVALID_URL]`}}const w={CALLBACK_FAILED:`mcp_auth_callback_failed`,DCR_FAILED:`mcp_auth_dcr_failed`,REFRESH_FAILED:`mcp_auth_refresh_failed`,SERVER_METADATA_FAILED:`mcp_auth_server_metadata_failed`,STATE_MISMATCH:`mcp_auth_state_mismatch`,TOKEN_EXCHANGE_FAILED:`mcp_auth_token_exchange_failed`},T=49152;function E(){return Math.floor(Math.random()*(65535-T+1))+T}function D(e,t){return new Promise((n,r)=>{let i=t=>{e.removeListener(`listening`,a),r(t)},a=()=>{e.removeListener(`error`,i),n()};e.once(`error`,i),e.once(`listening`,a),e.listen(t,`127.0.0.1`)})}async function O(e={}){let{maxAttempts:t=5,preferredPort:r,serverUrl:i,timeoutMs:a=3e5}=e,o=i?j(i):void 0,s,c,l={},u=new Promise((e,t)=>{s=e,c=t}),d,f=_((e,t)=>{if(e.url===`/favicon.ico`){t.writeHead(404),t.end();return}if(!e.url?.startsWith(`/callback`)){t.writeHead(404),t.end(`Not Found`);return}try{let r=new g(e.url,`http://localhost:${d}`),i=r.searchParams.get(`code`),a=r.searchParams.get(`error`),u=r.searchParams.get(`error_description`),p=r.searchParams.get(`state`);if(a){n.error(`OAuth authorization error`,{error:a,errorDescription:u,eventType:w.CALLBACK_FAILED,...o?{serverUrl:o}:{}}),t.writeHead(400,{"Content-Type":`text/html`}),t.end(A(a,u||void 0)),clearTimeout(l.id),c(Error(`OAuth authorization failed: ${a}${u?` - ${u}`:``}`));return}if(!i){n.error(`OAuth callback missing authorization code`,{eventType:w.CALLBACK_FAILED,reason:`missing_code`,...o?{serverUrl:o}:{}}),t.writeHead(400,{"Content-Type":`text/html`}),t.end(A(`missing_code`,`No authorization code was provided`)),clearTimeout(l.id),c(Error(`OAuth callback missing authorization code`));return}n.info(`OAuth authorization code received`,{codePrefix:`${i.substring(0,10)}...`,hasState:!!p}),t.writeHead(200,{"Content-Type":`text/html`}),t.end(`<!DOCTYPE html>
1
+ import{proxiedFetch as e}from"./proxyFetch-2QPdA_Ro.js";import{config as t}from"./env-CLi_cqSV.js";import{logger as n}from"./logger-DxFySFXs.js";import{OAuthError as r,OAuthErrorCode as i,SSEClientTransport as a,StreamableHTTPClientTransport as o,discoverAuthorizationServerMetadata as s,discoverOAuthProtectedResourceMetadata as c,exchangeAuthorization as l,normalizeUrl as u,refreshAuthorization as d,registerClient as f,startAuthorization as p}from"./normalizeUrl-DUUGubD4.js";import{findProviderForUrl as m}from"./providers-BszAUoN_.js";import{platform as h}from"node:os";import{URL as g}from"node:url";import{createServer as _}from"node:http";import{execFile as v}from"node:child_process";async function y(e){let t=e.toString(),r=h(),i=C(t);return new Promise((e,a)=>{let o,s;if(r===`win32`){let e=`Start-Process ${x(t)}`,n=b(e);o=S(),s=[`-NoProfile`,`-NonInteractive`,`-ExecutionPolicy`,`Bypass`,`-EncodedCommand`,n]}else r===`darwin`?(o=`open`,s=[t]):(o=`xdg-open`,s=[t]);v(o,s,(t,s,c)=>{if(t){let e={command:o,os:r,stderr:c,url:i};n.warn(`Failed to open browser automatically`,{...e,error:t.message});let s=Error(`Failed to open browser: ${t.message} (command: ${o}, os: ${r}, url: ${i})`);s.cause=t,a(s)}else n.debug(`Browser opened successfully`,{url:i}),e()})})}function b(e){return Buffer.from(e,`utf16le`).toString(`base64`)}function x(e){return`'${e.replaceAll(`'`,`''`)}'`}function S(){return`${process.env.SYSTEMROOT||process.env.windir||`C:\\Windows`}\\System32\\WindowsPowerShell\\v1.0\\powershell.exe`}function C(e){try{let t=new URL(e);return t.search?`${t.protocol}//${t.host}${t.pathname}?[REDACTED]`:`${t.protocol}//${t.host}${t.pathname}`}catch{return`[INVALID_URL]`}}const w={CALLBACK_FAILED:`mcp_auth_callback_failed`,DCR_FAILED:`mcp_auth_dcr_failed`,REFRESH_FAILED:`mcp_auth_refresh_failed`,SERVER_METADATA_FAILED:`mcp_auth_server_metadata_failed`,STATE_MISMATCH:`mcp_auth_state_mismatch`,TOKEN_EXCHANGE_FAILED:`mcp_auth_token_exchange_failed`},T=49152;function E(){return Math.floor(Math.random()*(65535-T+1))+T}function D(e,t){return new Promise((n,r)=>{let i=t=>{e.removeListener(`listening`,a),r(t)},a=()=>{e.removeListener(`error`,i),n()};e.once(`error`,i),e.once(`listening`,a),e.listen(t,`127.0.0.1`)})}async function O(e={}){let{maxAttempts:t=5,preferredPort:r,serverUrl:i,timeoutMs:a=3e5}=e,o=i?j(i):void 0,s,c,l={},u=new Promise((e,t)=>{s=e,c=t}),d,f=_((e,t)=>{if(e.url===`/favicon.ico`){t.writeHead(404),t.end();return}if(!e.url?.startsWith(`/callback`)){t.writeHead(404),t.end(`Not Found`);return}try{let r=new g(e.url,`http://localhost:${d}`),i=r.searchParams.get(`code`),a=r.searchParams.get(`error`),u=r.searchParams.get(`error_description`),p=r.searchParams.get(`state`);if(a){n.error(`OAuth authorization error`,{error:a,errorDescription:u,eventType:w.CALLBACK_FAILED,...o?{serverUrl:o}:{}}),t.writeHead(400,{"Content-Type":`text/html`}),t.end(A(a,u||void 0)),clearTimeout(l.id),c(Error(`OAuth authorization failed: ${a}${u?` - ${u}`:``}`));return}if(!i){n.error(`OAuth callback missing authorization code`,{eventType:w.CALLBACK_FAILED,reason:`missing_code`,...o?{serverUrl:o}:{}}),t.writeHead(400,{"Content-Type":`text/html`}),t.end(A(`missing_code`,`No authorization code was provided`)),clearTimeout(l.id),c(Error(`OAuth callback missing authorization code`));return}n.info(`OAuth authorization code received`,{codePrefix:`${i.substring(0,10)}...`,hasState:!!p}),t.writeHead(200,{"Content-Type":`text/html`}),t.end(`<!DOCTYPE html>
2
2
  <html lang="en">
3
3
  <head>
4
4
  <meta charset="UTF-8">
@@ -133,4 +133,4 @@ import{proxiedFetch as e}from"./proxyFetch-B2mideS8.js";import{config as t}from"
133
133
  </div>
134
134
  </body>
135
135
  </html>`}function j(e){try{let t=new g(e);return t.username=``,t.password=``,t.search=``,t.hash=``,t.toString()}catch{return e}}var M=class extends Error{constructor(e,t){let n=t instanceof Error?t.message:String(t);super(`Auth server metadata discovery failed for ${e}: ${n}`),this.authServerUrl=e,this.cause=t,this.name=`AuthServerDiscoveryError`}};async function N(t,n={}){let{logger:r,signal:i}=n,a=P(e,i);r?.debug(`Discovering OAuth protected resource metadata...`);let o;try{o=await c(t,void 0,a),r?.info(`Discovered protected resource metadata`,{authorizationServers:o?.authorization_servers,resource:o?.resource,scopesSupported:o?.scopes_supported})}catch(e){r?.warn(`Could not discover protected resource metadata, will try direct discovery`,{error:String(e)})}let l;o?.authorization_servers?.length?(l=new URL(o.authorization_servers[0]),r?.debug(`Using authorization server from resource metadata`,{authServerUrl:l.toString()})):(l=t,r?.debug(`Using server URL as authorization server`,{authServerUrl:l.toString()})),r?.debug(`Discovering authorization server metadata...`);let u;try{u=await s(l,{fetchFn:a,skipIssuerValidation:!0})}catch(e){throw new M(l,e)}if(!u&&l.pathname!==`/`){let e=new URL(l.origin);r?.debug(`Auth server metadata absent at path-aware well-knowns, retrying at origin root`,{authServerUrl:l.toString(),rootUrl:e.toString()});try{u=await s(e,{fetchFn:a,skipIssuerValidation:!0})}catch(t){if(i?.aborted)throw new M(e,t);r?.warn(`Origin-root auth server metadata fallback failed, treating metadata as absent`,{error:String(t),rootUrl:e.toString()})}u?(l=e,r?.info(`Origin-root auth server metadata fallback succeeded`,{authServerUrl:l.toString()})):r?.debug(`Origin-root auth server metadata fallback found nothing`,{rootUrl:e.toString()})}return u&&r?.info(`Discovered authorization server metadata`,{authorizationEndpoint:u.authorization_endpoint,issuer:u.issuer,registrationEndpoint:u.registration_endpoint,tokenEndpoint:u.token_endpoint}),{authMetadata:u,authServerUrl:l,resourceMetadata:o}}function P(e,t){return((n,r)=>{if(!t)return e(n,r);let i=r?.signal,a=i?AbortSignal.any([t,i]):t;return e(n,{...r,signal:a})})}function F(e){return L(e,i.InvalidClient,new WeakSet)}function I(e){return L(e,i.InvalidGrant,new WeakSet)}function L(e,t,n){if(e==null)return!1;if(e instanceof r)return e.code===t;if(typeof e==`object`){if(n.has(e))return!1;n.add(e);let r=e.error;if(typeof r==`string`&&r===t)return!0;for(let r of[`response`,`data`,`body`,`cause`]){let i=e[r];if(i!==void 0&&L(i,t,n))return!0}}return e instanceof Error&&typeof e.message==`string`?RegExp(`"error"\\s*:\\s*"${t}"`).test(e.message)||RegExp(`\\berror\\s*=\\s*${t}\\b`).test(e.message)||RegExp(`\\berror\\s*:\\s*${t}\\b`).test(e.message):!1}const R={"claude-code":`Claude Code (mcp-gateway)`,codex:`Codex`,copilot:`Visual Studio Code`,cursor:`Cursor`,opencode:`OpenCode`},z={copilot:`https://code.visualstudio.com`},B={cursor:{"mcp.figma.com":`cursor://anysphere.cursor-mcp/oauth/callback`}};function V(){return G(t.clientAppName)}function H(){return K(t.clientAppName)}function U(e,n){return q(t.clientAppName,e,n)}async function W(t,r={}){let{credentialStore:i,scope:a,timeoutMs:o}=r,s=u(t),c=new URL(s);if(n.info(`Starting OAuth flow`,{serverUrl:s}),i){let e=await Z(c,i);if(e)return n.info(`Using existing OAuth credentials`,{clientId:e.clientInfo.client_id,serverUrl:s}),e}n.debug(`Starting OAuth callback server...`);let d=await O({serverUrl:s,timeoutMs:o}),h=d.callbackUrl;n.info(`OAuth callback server started`,{callbackUrl:h,port:d.port});try{let t=m(c);if(t)return n.info(`Using pre-configured OAuth provider`,{providerId:t.id,providerName:t.name}),await X(c,t,d,i,a);let o,u,g;try{({authMetadata:o,authServerUrl:u,resourceMetadata:g}=await N(c,{logger:n}))}catch(e){throw n.error(`Auth server metadata discovery failed`,{error:String(e),eventType:w.SERVER_METADATA_FAILED,serverUrl:s,...e instanceof M?{authServer:e.authServerUrl.toString()}:{}}),e}if(!o)throw n.error(`Auth server metadata absent (server may not support OAuth 2.0)`,{authServer:u.toString(),eventType:w.SERVER_METADATA_FAILED,reason:`metadata_absent`,serverUrl:s}),Error(`Could not discover OAuth metadata from ${u}. The server may not support OAuth 2.0 or the metadata endpoint is not accessible.`);let _=g?.scopes_supported,v=_?.includes(`offline_access`)??!1,b=a??(v?_?.join(` `):void 0);b&&!a?n.info(`Defaulting OAuth scope to the resource's advertised scopes_supported`,{scope:b,serverUrl:s}):!a&&_?.length&&n.debug(`Resource advertises scopes but not offline_access; requesting no scope`,{advertised:_,serverUrl:s});let x,S=i?await i.load(s):void 0,C=S?.clientInfo?.redirect_uris||[];if(S?.clientInfo&&C.includes(h)&&S?.clientInfo)n.info(`Reusing existing DCR client registration`,{clientId:S.clientInfo.client_id,clientName:S.clientInfo.client_name,redirectUri:h}),x=S.clientInfo;else{S?.clientInfo&&n.info(`Existing DCR client has different redirect_uri, re-registering`,{existingRedirectUris:C,newRedirectUri:h});let t=r.clientMetadata?.client_uri??H(),a={client_name:r.clientMetadata?.client_name||V(),...t&&{client_uri:t},grant_types:r.clientMetadata?.grant_types||[`authorization_code`,`refresh_token`],redirect_uris:U(c,h),response_types:r.clientMetadata?.response_types||[`code`],token_endpoint_auth_method:r.clientMetadata?.token_endpoint_auth_method||`none`,...b&&{scope:b}};if(n.debug(`Client metadata for DCR`,{clientMetadata:a}),!o.registration_endpoint)throw n.error(`DCR registration unsupported by auth server`,{authServer:u.toString(),eventType:w.DCR_FAILED,reason:`no_registration_endpoint`,serverUrl:s}),Error(`Authorization server does not support Dynamic Client Registration. No registration_endpoint found in metadata.`);n.info(`Registering client via DCR...`,{registrationEndpoint:o.registration_endpoint});try{x=await f(u,{clientMetadata:a,fetchFn:e,metadata:o})}catch(e){throw n.error(`DCR registration failed`,{authServer:u.toString(),error:String(e),eventType:w.DCR_FAILED,registrationEndpoint:o.registration_endpoint,serverUrl:s}),e}n.info(`Client registered successfully via DCR`,{clientId:x.client_id,clientName:x.client_name,clientSecretExpiresAt:x.client_secret_expires_at}),i&&await i.update(s,{clientInfo:x})}let T=crypto.randomUUID();n.debug(`Starting authorization flow...`);let{authorizationUrl:E,codeVerifier:D}=await p(u,{clientInformation:x,metadata:o,redirectUrl:h,scope:b,state:T});await y(E),n.debug(`Waiting for OAuth callback...`);let O=await d.waitForCallback();if(O.state!==T)throw n.error(`OAuth state mismatch — possible CSRF attack`,{authServer:u.toString(),eventType:w.STATE_MISMATCH,serverUrl:s}),Error(`OAuth state mismatch - possible CSRF attack`);n.info(`Authorization code received`,{codePrefix:`${O.code.substring(0,10)}...`}),n.debug(`Exchanging authorization code for tokens...`);let k;try{k=await l(u,{authorizationCode:O.code,clientInformation:x,codeVerifier:D,fetchFn:e,metadata:o,redirectUri:h})}catch(e){throw n.error(`Token exchange failed`,{authServer:u.toString(),clientId:x.client_id,error:String(e),eventType:w.TOKEN_EXCHANGE_FAILED,grantType:`authorization_code`,serverUrl:s}),e}if(n.info(`OAuth tokens obtained successfully`,{expiresIn:k.expires_in,grantedScope:k.scope,hasRefreshToken:!!k.refresh_token,requestedScope:b,tokenType:k.token_type}),i){let e=new Date().toISOString();await i.update(s,{tokens:k,tokensObtainedAt:e}),n.info(`OAuth credentials persisted to disk`,{serverUrl:s})}return{clientInfo:x,fromCache:!1,tokens:k}}finally{await d.close()}}function G(e){let t=e?.trim().toLowerCase();return t&&Object.hasOwn(R,t)?R[t]:`MCP Gateway`}function K(e){let t=e?.trim().toLowerCase();if(t)return Object.hasOwn(z,t)?z[t]:void 0}function q(e,t,n){let r=e?.trim().toLowerCase();if(!r)return[n];let i=B[r];if(!i)return[n];let a;try{a=(t instanceof URL?t:new URL(t)).hostname.toLowerCase()}catch{return[n]}let o=i[a];return o?[o,n]:[n]}async function J(e,t){let n=typeof e==`string`?new URL(e):e,r=await t.load(n.toString());if(!(!r?.clientInfo||!r.tokens?.refresh_token))return Q(n,r,t)}async function Y(t,r,i,a){let o=t.tokenEndpoint,s=t.tokenEndpointAuthMethod||(t.clientSecret?`client_secret_post`:`none`),c=new URLSearchParams;c.set(`grant_type`,`authorization_code`),c.set(`code`,r),c.set(`redirect_uri`,i),a&&c.set(`code_verifier`,a);let l={Accept:`application/json`,"Content-Type":`application/x-www-form-urlencoded`};s===`client_secret_basic`&&t.clientSecret?l.Authorization=`Basic ${Buffer.from(`${t.clientId}:${t.clientSecret}`).toString(`base64`)}`:s===`client_secret_post`&&t.clientSecret?(c.set(`client_id`,t.clientId),c.set(`client_secret`,t.clientSecret)):c.set(`client_id`,t.clientId),n.debug(`Sending token request`,{authMethod:s,providerId:t.id,tokenEndpoint:o});let u=await e(o,{body:c.toString(),headers:l,method:`POST`});if(!u.ok){let e=await u.text();throw n.error(`Token exchange failed`,{authMethod:s,error:e,eventType:w.TOKEN_EXCHANGE_FAILED,grantType:`authorization_code`,httpStatus:u.status,providerId:t.id,tokenEndpoint:o}),Error(`Token exchange failed: ${u.status} ${e}`)}let d=u.headers.get(`content-type`)||``,f;if(d.includes(`application/json`))f=await u.json();else if(d.includes(`application/x-www-form-urlencoded`)||d.includes(`text/plain`)){let e=await u.text(),t=new URLSearchParams(e);f=Object.fromEntries(t.entries())}else{let e=await u.text();try{f=JSON.parse(e)}catch{let t=new URLSearchParams(e);f=Object.fromEntries(t.entries())}}return typeof f.expires_in==`string`&&(f.expires_in=parseInt(f.expires_in,10)),{access_token:f.access_token,expires_in:f.expires_in,refresh_token:f.refresh_token,scope:f.scope,token_type:f.token_type||`bearer`}}async function X(e,t,r,i,a){let o=e.toString(),s=r.callbackUrl,c={client_id:t.clientId,client_name:t.name,client_secret:t.clientSecret,redirect_uris:[s],token_endpoint_auth_method:t.tokenEndpointAuthMethod||(t.clientSecret?`client_secret_post`:`none`)};n.info(`Using pre-configured OAuth client`,{clientId:t.clientId,hasClientSecret:!!t.clientSecret,providerId:t.id,tokenEndpointAuthMethod:c.token_endpoint_auth_method});let l=a||t.scopes?.join(` `),u=t.usePkce!==!1,d,f,p;if(u){let{generateCodeChallenge:e,generateCodeVerifier:t}=await import(`./pkce-C7ZxslWt.js`);d=t(),f=await e(d),p=`S256`}let m=new URL(t.authorizationEndpoint);m.searchParams.set(`client_id`,t.clientId),m.searchParams.set(`redirect_uri`,s),m.searchParams.set(`response_type`,`code`),l&&m.searchParams.set(`scope`,l);let h=crypto.randomUUID();m.searchParams.set(`state`,h),f&&p&&(m.searchParams.set(`code_challenge`,f),m.searchParams.set(`code_challenge_method`,p)),await y(m),n.debug(`Waiting for OAuth callback...`);let g=await r.waitForCallback();if(g.state!==h)throw n.error(`OAuth state mismatch — possible CSRF attack`,{eventType:w.STATE_MISMATCH,providerId:t.id,serverUrl:o}),Error(`OAuth state mismatch - possible CSRF attack`);n.info(`Authorization code received`,{codePrefix:`${g.code.substring(0,10)}...`,providerId:t.id}),n.debug(`Exchanging authorization code for tokens...`);let _=await Y(t,g.code,s,d);if(n.info(`OAuth tokens obtained successfully`,{expiresIn:_.expires_in,hasRefreshToken:!!_.refresh_token,providerId:t.id,tokenType:_.token_type}),i){let e=new Date().toISOString();await i.update(o,{clientInfo:c,tokens:_,tokensObtainedAt:e}),n.info(`OAuth credentials persisted to disk`,{providerId:t.id,serverUrl:o})}return{clientInfo:c,fromCache:!1,tokens:_}}async function Z(e,t){let r=await t.load(e.toString());if(!r){n.debug(`No stored credentials found`,{serverUrl:e.toString()});return}if(!r.clientInfo){n.debug(`Stored credentials have no client info`,{serverUrl:e.toString()});return}if(t.hasValidAccessToken(r)&&r.tokens)return n.info(`Using cached OAuth credentials with valid access token`,{clientId:r.clientInfo.client_id,serverUrl:e.toString()}),{clientInfo:r.clientInfo,fromCache:!0,tokens:r.tokens};if(t.hasRefreshToken(r)){let n=await Q(e,r,t);if(n)return n}n.debug(`Stored credentials are expired and no valid refresh token`,{serverUrl:e.toString()})}async function Q(t,r,i){if(!(!r.clientInfo||!r.tokens?.refresh_token)){n.info(`Attempting to refresh expired access token`,{clientId:r.clientInfo.client_id,serverUrl:t.toString()});try{let a=new AbortController,o=setTimeout(()=>a.abort(),15e3),s,c;try{({authMetadata:s,authServerUrl:c}=await N(t,{logger:n,signal:a.signal}))}finally{clearTimeout(o)}let l=await d(c,{clientInformation:r.clientInfo,fetchFn:e,metadata:s,refreshToken:r.tokens.refresh_token});n.info(`Successfully refreshed access token`,{clientId:r.clientInfo.client_id,expiresIn:l.expires_in,grantedScope:l.scope,hasNewRefreshToken:!!l.refresh_token});let u=l.refresh_token?l:{...l,refresh_token:r.tokens.refresh_token},f=new Date().toISOString();return await i.update(t.toString(),{tokens:u,tokensObtainedAt:f}),{clientInfo:r.clientInfo,fromCache:!0,tokens:u}}catch(e){let a=I(e),o=F(e),s=a&&o,c=a&&!s,l=o&&!s;n.warn(s?`Refresh failed with both invalid_client and invalid_grant; ambiguous, retaining credentials`:l?`Client registration rejected by OAuth server (invalid_client); clearing stored client and tokens`:c?`Refresh token rejected by OAuth server (invalid_grant); clearing stored tokens`:`Token refresh failed transiently; retaining refresh_token for the next attempt`,{ambiguous:s,clientId:r.clientInfo.client_id,error:String(e),eventType:w.REFRESH_FAILED,invalidClient:l,invalidGrant:c,serverUrl:t.toString()}),l?await i.update(t.toString(),{clientInfo:void 0,tokens:void 0,tokensObtainedAt:void 0}):c&&await i.update(t.toString(),{tokens:void 0,tokensObtainedAt:void 0});return}}}const ee=[`streamable-http`,`sse`];async function te(e,t,n,r){let{result:i,transportType:a}=await $(async i=>{let a=e(),o=ne(t,i,n,r);return await a.connect(o),a});return{client:i,transportType:a}}function ne(t,n,r,i){let s=new URL(t),c=i??e;return n===`streamable-http`?new o(s,{fetch:c,requestInit:r}):new a(s,{fetch:c,requestInit:r})}function re(e){if(!e||typeof e!=`object`)return!1;let t=e.status,n=e.code,r=e.message;return!!(t===404||t===405||n===404||n===405||r&&/404|not found|405|method not allowed/i.test(String(r)))}async function $(e){let t=[];for(let r of ee)try{return{result:await e(r),transportType:r}}catch(e){if(re(e)){n.info(`${r} not supported, trying next transport...`),t.push({err:e,transport:r});continue}throw e}let r=t.map(({err:e,transport:t})=>{let n=e?.status;if(n!==void 0)return`${t}: ${n}`;let r=e instanceof Error?e.message:String(e);return`${t}: ${r}`}).join(`, `),i=t.length>0?Error(`all transports returned not-supported (${r})`):Error(`No compatible transport found`);throw i.cause=t,i}export{M as AuthServerDiscoveryError,te as connectWithTransportFallback,N as discoverAuthServer,V as getDcrClientName,H as getDcrClientUri,U as getDcrRedirectUris,W as performOAuthFlow,J as tryRefreshCachedToken,$ as walkTransports};
136
- //# sourceMappingURL=transportDetection-BBbTAmtW.js.map
136
+ //# sourceMappingURL=transportDetection-sJ8O5FAa.js.map