From f98b6e939eecc1b9fac47a90161696f2d3e66b56 Mon Sep 17 00:00:00 2001 From: Faur Ioan-Aurel Date: Wed, 27 May 2026 23:42:09 +0300 Subject: [PATCH 1/2] fix: don't apply Coder TLS alternate hostname to JetBrains IDE feed client The IdeFeedManager fetches IDE metadata from data.services.jetbrains.com but was reusing the default Coder OkHttp client, which applies the user-configured TLS alternate hostname. That setting is meant for the Coder deployment and should not affect requests to JetBrains. Introduce CoderHttpClientBuilder.defaultWithoutTlsAlternateHostname and use it from IdeFeedManager so the JetBrains feed client verifies the real hostname. --- CHANGELOG.md | 4 +++ .../com/coder/toolbox/feed/IdeFeedManager.kt | 2 +- .../toolbox/sdk/CoderHttpClientBuilder.kt | 28 +++++++++++++++++-- 3 files changed, 30 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 62cf35c..1dd84b7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### Changed + +- skip the Coder TLS alternate hostname when fetching IDE metadata from JetBrains + ## 0.9.0 - 2026-05-14 ### Added diff --git a/src/main/kotlin/com/coder/toolbox/feed/IdeFeedManager.kt b/src/main/kotlin/com/coder/toolbox/feed/IdeFeedManager.kt index 985c11c..2849021 100644 --- a/src/main/kotlin/com/coder/toolbox/feed/IdeFeedManager.kt +++ b/src/main/kotlin/com/coder/toolbox/feed/IdeFeedManager.kt @@ -40,7 +40,7 @@ class IdeFeedManager( private val feedService: JetBrainsFeedService by lazy { if (feedService != null) return@lazy feedService - val okHttpClient = CoderHttpClientBuilder.default(context) + val okHttpClient = CoderHttpClientBuilder.defaultWithoutTlsAlternateHostname(context) val retrofit = Retrofit.Builder() .baseUrl("https://data.services.jetbrains.com/") diff --git a/src/main/kotlin/com/coder/toolbox/sdk/CoderHttpClientBuilder.kt b/src/main/kotlin/com/coder/toolbox/sdk/CoderHttpClientBuilder.kt index 67d70c2..ccd2ec6 100644 --- a/src/main/kotlin/com/coder/toolbox/sdk/CoderHttpClientBuilder.kt +++ b/src/main/kotlin/com/coder/toolbox/sdk/CoderHttpClientBuilder.kt @@ -3,6 +3,7 @@ package com.coder.toolbox.sdk import com.coder.toolbox.CoderToolboxContext import com.coder.toolbox.plugin.PluginManager import com.coder.toolbox.sdk.interceptors.Interceptors +import com.coder.toolbox.settings.ReadOnlyTLSSettings import com.coder.toolbox.util.CoderHostnameVerifier import com.coder.toolbox.util.ReloadableTlsContext import com.jetbrains.toolbox.api.remoteDev.connection.ProxyAuth @@ -14,7 +15,8 @@ object CoderHttpClientBuilder { fun build( context: CoderToolboxContext, interceptors: List, - tlsContext: ReloadableTlsContext + tlsContext: ReloadableTlsContext, + tlsAlternateHostname: String? = context.settingsStore.tls.altHostname ): OkHttpClient { val builder = OkHttpClient.Builder() @@ -41,7 +43,7 @@ object CoderHttpClientBuilder { } builder.sslSocketFactory(tlsContext.sslSocketFactory, tlsContext.trustManager) - .hostnameVerifier(CoderHostnameVerifier(context.settingsStore.tls.altHostname)) + .hostnameVerifier(CoderHostnameVerifier(tlsAlternateHostname)) .retryOnConnectionFailure(true) interceptors.forEach { interceptor -> @@ -62,4 +64,24 @@ object CoderHttpClientBuilder { ReloadableTlsContext(context.settingsStore.readOnly().tls) ) } -} \ No newline at end of file + + fun defaultWithoutTlsAlternateHostname(context: CoderToolboxContext): OkHttpClient { + val interceptors = buildList { + add((Interceptors.userAgent(PluginManager.pluginInfo.version))) + add(Interceptors.logging(context)) + } + return build( + context, + interceptors, + ReloadableTlsContext(context.settingsStore.readOnly().tls.withoutAlternateHostname()), + tlsAlternateHostname = null + ) + } + + private fun ReadOnlyTLSSettings.withoutAlternateHostname(): ReadOnlyTLSSettings { + val settings = this + return object : ReadOnlyTLSSettings by settings { + override val altHostname: String? = null + } + } +} From 15e2c1c725caaa902ada88b0d3f78a83302d38be Mon Sep 17 00:00:00 2001 From: Faur Ioan-Aurel Date: Thu, 28 May 2026 18:56:49 +0300 Subject: [PATCH 2/2] refactor: build JetBrains feed OkHttp client inline, share proxy setup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CoderHttpClientBuilder is for Coder endpoints — having it grow a "without TLS alternate hostname" variant just to serve the JetBrains data services feed was a smell. Move the JetBrains client construction into IdeFeedManager where it belongs, with only what it actually needs: user-agent, logging, proxy, retry. No Coder TLS context or hostname verifier touches that traffic anymore. --- .../com/coder/toolbox/feed/IdeFeedManager.kt | 12 +++- .../toolbox/sdk/CoderHttpClientBuilder.kt | 56 ++----------------- .../com/coder/toolbox/sdk/proxy/Proxies.kt | 36 ++++++++++++ 3 files changed, 51 insertions(+), 53 deletions(-) create mode 100644 src/main/kotlin/com/coder/toolbox/sdk/proxy/Proxies.kt diff --git a/src/main/kotlin/com/coder/toolbox/feed/IdeFeedManager.kt b/src/main/kotlin/com/coder/toolbox/feed/IdeFeedManager.kt index 2849021..6cfcc80 100644 --- a/src/main/kotlin/com/coder/toolbox/feed/IdeFeedManager.kt +++ b/src/main/kotlin/com/coder/toolbox/feed/IdeFeedManager.kt @@ -1,11 +1,14 @@ package com.coder.toolbox.feed import com.coder.toolbox.CoderToolboxContext -import com.coder.toolbox.sdk.CoderHttpClientBuilder +import com.coder.toolbox.plugin.PluginManager +import com.coder.toolbox.sdk.interceptors.Interceptors +import com.coder.toolbox.sdk.proxy.applyProxySettings import com.squareup.moshi.Moshi import com.squareup.moshi.Types import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext +import okhttp3.OkHttpClient import retrofit2.Retrofit import retrofit2.converter.moshi.MoshiConverterFactory import java.nio.file.Path @@ -40,7 +43,12 @@ class IdeFeedManager( private val feedService: JetBrainsFeedService by lazy { if (feedService != null) return@lazy feedService - val okHttpClient = CoderHttpClientBuilder.defaultWithoutTlsAlternateHostname(context) + val okHttpClient = OkHttpClient.Builder() + .applyProxySettings(context) + .addInterceptor(Interceptors.userAgent(PluginManager.pluginInfo.version)) + .addInterceptor(Interceptors.logging(context)) + .retryOnConnectionFailure(true) + .build() val retrofit = Retrofit.Builder() .baseUrl("https://data.services.jetbrains.com/") diff --git a/src/main/kotlin/com/coder/toolbox/sdk/CoderHttpClientBuilder.kt b/src/main/kotlin/com/coder/toolbox/sdk/CoderHttpClientBuilder.kt index ccd2ec6..594587b 100644 --- a/src/main/kotlin/com/coder/toolbox/sdk/CoderHttpClientBuilder.kt +++ b/src/main/kotlin/com/coder/toolbox/sdk/CoderHttpClientBuilder.kt @@ -3,11 +3,9 @@ package com.coder.toolbox.sdk import com.coder.toolbox.CoderToolboxContext import com.coder.toolbox.plugin.PluginManager import com.coder.toolbox.sdk.interceptors.Interceptors -import com.coder.toolbox.settings.ReadOnlyTLSSettings +import com.coder.toolbox.sdk.proxy.applyProxySettings import com.coder.toolbox.util.CoderHostnameVerifier import com.coder.toolbox.util.ReloadableTlsContext -import com.jetbrains.toolbox.api.remoteDev.connection.ProxyAuth -import okhttp3.Credentials import okhttp3.Interceptor import okhttp3.OkHttpClient @@ -15,40 +13,16 @@ object CoderHttpClientBuilder { fun build( context: CoderToolboxContext, interceptors: List, - tlsContext: ReloadableTlsContext, - tlsAlternateHostname: String? = context.settingsStore.tls.altHostname + tlsContext: ReloadableTlsContext ): OkHttpClient { val builder = OkHttpClient.Builder() - - context.proxySettings.getProxy()?.let { proxy -> - context.logger.info("proxy: $proxy") - builder.proxy(proxy) - } ?: context.proxySettings.getProxySelector()?.let { proxySelector -> - context.logger.info("proxy selector: $proxySelector") - builder.proxySelector(proxySelector) - } - - // Note: This handles only HTTP/HTTPS proxy authentication. - // SOCKS5 proxy authentication is currently not supported due to limitations described in: - // https://youtrack.jetbrains.com/issue/TBX-14532/Missing-proxy-authentication-settings#focus=Comments-27-12265861.0-0 - builder.proxyAuthenticator { _, response -> - val proxyAuth = context.proxySettings.getProxyAuth() - if (proxyAuth == null || proxyAuth !is ProxyAuth.Basic) { - return@proxyAuthenticator null - } - val credentials = Credentials.basic(proxyAuth.username, proxyAuth.password) - response.request.newBuilder() - .header("Proxy-Authorization", credentials) - .build() - } - - builder.sslSocketFactory(tlsContext.sslSocketFactory, tlsContext.trustManager) - .hostnameVerifier(CoderHostnameVerifier(tlsAlternateHostname)) + .applyProxySettings(context) + .sslSocketFactory(tlsContext.sslSocketFactory, tlsContext.trustManager) + .hostnameVerifier(CoderHostnameVerifier(context.settingsStore.tls.altHostname)) .retryOnConnectionFailure(true) interceptors.forEach { interceptor -> builder.addInterceptor(interceptor) - } return builder.build() } @@ -64,24 +38,4 @@ object CoderHttpClientBuilder { ReloadableTlsContext(context.settingsStore.readOnly().tls) ) } - - fun defaultWithoutTlsAlternateHostname(context: CoderToolboxContext): OkHttpClient { - val interceptors = buildList { - add((Interceptors.userAgent(PluginManager.pluginInfo.version))) - add(Interceptors.logging(context)) - } - return build( - context, - interceptors, - ReloadableTlsContext(context.settingsStore.readOnly().tls.withoutAlternateHostname()), - tlsAlternateHostname = null - ) - } - - private fun ReadOnlyTLSSettings.withoutAlternateHostname(): ReadOnlyTLSSettings { - val settings = this - return object : ReadOnlyTLSSettings by settings { - override val altHostname: String? = null - } - } } diff --git a/src/main/kotlin/com/coder/toolbox/sdk/proxy/Proxies.kt b/src/main/kotlin/com/coder/toolbox/sdk/proxy/Proxies.kt new file mode 100644 index 0000000..90476ab --- /dev/null +++ b/src/main/kotlin/com/coder/toolbox/sdk/proxy/Proxies.kt @@ -0,0 +1,36 @@ +package com.coder.toolbox.sdk.proxy + +import com.coder.toolbox.CoderToolboxContext +import com.jetbrains.toolbox.api.remoteDev.connection.ProxyAuth +import okhttp3.Credentials +import okhttp3.OkHttpClient + +/** + * Applies the user's Toolbox proxy and proxy authentication settings to this builder. + * + * Note: This handles only HTTP/HTTPS proxy authentication. SOCKS5 proxy authentication is currently + * not supported due to limitations described in: + * https://youtrack.jetbrains.com/issue/TBX-14532/Missing-proxy-authentication-settings#focus=Comments-27-12265861.0-0 + */ +fun OkHttpClient.Builder.applyProxySettings(context: CoderToolboxContext): OkHttpClient.Builder { + context.proxySettings.getProxy()?.let { proxy -> + context.logger.info("proxy: $proxy") + proxy(proxy) + } ?: context.proxySettings.getProxySelector()?.let { proxySelector -> + context.logger.info("proxy selector: $proxySelector") + proxySelector(proxySelector) + } + + proxyAuthenticator { _, response -> + val proxyAuth = context.proxySettings.getProxyAuth() + if (proxyAuth == null || proxyAuth !is ProxyAuth.Basic) { + return@proxyAuthenticator null + } + val credentials = Credentials.basic(proxyAuth.username, proxyAuth.password) + response.request.newBuilder() + .header("Proxy-Authorization", credentials) + .build() + } + + return this +}