From d94d57a70588102feb3858243bb7bfa656d14d45 Mon Sep 17 00:00:00 2001 From: Madhavendra Rathore Date: Wed, 10 Jun 2026 07:19:46 +0000 Subject: [PATCH 1/4] [SEA-NodeJS] feat(kernel): support the explicit proxy ConnectionOption on the kernel backend Map the public Thrift-shaped `ConnectionOptions.proxy` (`{protocol, host, port, auth}`) onto the kernel napi binding's `proxy?: string`, so the SAME proxy connection option that works on the Thrift backend now also routes kernel/SEA traffic through a proxy. `buildKernelProxyOptions` composes `protocol://[user:pass@]host:port`, percent-encoding any `auth.{username,password}` into the URL userinfo so credentials with reserved characters survive; the kernel parses the userinfo off and applies it as proxy basic-auth. Wired into `buildKernelConnectionOptions` alongside the TLS / HTTP option builders. The regenerated napi contract (`native/kernel/index.d.ts`) carries the new `proxy?: string` and `socketTimeoutMs?: number` fields exposed by the kernel PR (databricks/databricks-sql-kernel#129). Note: env-var proxying (`HTTPS_PROXY` / `HTTP_PROXY` / `NO_PROXY`) already worked on the kernel backend (reqwest honours it natively); this adds the *programmatic* path for callers who cannot set process env vars. Verified end-to-end via mitmproxy against a live serverless warehouse: explicit `proxy` option (no env var) routes all SEA calls through the proxy; a dead proxy port fails the connection (proving the proxy is used). Co-authored-by: Isaac Signed-off-by: Madhavendra Rathore --- lib/kernel/KernelAuth.ts | 41 ++++++++++++++++++++++++++++++++++++++- native/kernel/index.d.ts | 42 +++++++++++++--------------------------- 2 files changed, 53 insertions(+), 30 deletions(-) diff --git a/lib/kernel/KernelAuth.ts b/lib/kernel/KernelAuth.ts index e4aa7c0c..0841e652 100644 --- a/lib/kernel/KernelAuth.ts +++ b/lib/kernel/KernelAuth.ts @@ -192,9 +192,22 @@ export interface KernelHttpOptions { customHeaders?: Array<{ name: string; value: string }>; } +/** + * HTTP(S) proxy forwarded to the napi binding's `ConnectionOptions.proxy` + * (kernel `ProxyConfig.url`). The public `ConnectionOptions.proxy` is the + * Thrift-shaped `{protocol, host, port, auth}`; `buildKernelProxyOptions` + * composes a single proxy URL string (with any basic-auth credentials + * percent-encoded into the `userinfo`) so the SAME connection option works + * on both backends. The napi contract takes a flat `proxy?: string`. + */ +export interface KernelProxyOptions { + proxy?: string; +} + export type KernelNativeConnectionOptions = KernelSessionDefaults & KernelTlsOptions & KernelHttpOptions & + KernelProxyOptions & ( | { hostName: string; @@ -514,6 +527,29 @@ export function buildKernelRetryOptions(config: { return out; } +/** + * Map the public `ConnectionOptions.proxy` (`{protocol, host, port, auth}` — + * the same shape the Thrift backend accepts) onto the kernel's napi + * `proxy?: string`. Composes `protocol://[user:pass@]host:port`, percent- + * encoding any `auth.{username,password}` into the URL `userinfo` so + * credentials containing reserved characters (`@`, `:`, `/`) survive intact — + * the kernel parses the userinfo off and applies it as basic-auth. The kernel + * accepts only `http://` / `https://`; a SOCKS protocol surfaces a clear + * kernel error at connect (reqwest SOCKS support is not compiled in). + */ +export function buildKernelProxyOptions(options: ConnectionOptions): KernelProxyOptions { + const { proxy } = options; + if (!proxy) { + return {}; + } + const { username, password } = proxy.auth ?? {}; + const userinfo = + username !== undefined ? `${encodeURIComponent(username)}:${encodeURIComponent(password ?? '')}@` : ''; + return { + proxy: `${proxy.protocol}://${userinfo}${proxy.host}:${proxy.port}`, + }; +} + export function buildKernelConnectionOptions(options: ConnectionOptions): KernelNativeConnectionOptions { const { authType } = options as { authType?: string }; @@ -523,7 +559,8 @@ export function buildKernelConnectionOptions(options: ConnectionOptions): Kernel intervalsAsString: boolean; maxConnections?: number; } & KernelTlsOptions & - KernelHttpOptions = { + KernelHttpOptions & + KernelProxyOptions = { hostName: options.host, httpPath: prependSlash(options.path), // Match the NodeJS Thrift driver, which surfaces INTERVAL columns as @@ -539,6 +576,8 @@ export function buildKernelConnectionOptions(options: ConnectionOptions): Kernel ...buildKernelTlsOptions(options), // HTTP headers (caller `customHeaders` + composed `User-Agent`). ...buildKernelHttpOptions(options), + // HTTP(S) proxy — the same `ConnectionOptions.proxy` the Thrift path uses. + ...buildKernelProxyOptions(options), }; // kernel-only pool sizing; read via cast to match how this function reads the diff --git a/native/kernel/index.d.ts b/native/kernel/index.d.ts index 0b042121..1b8aee31 100644 --- a/native/kernel/index.d.ts +++ b/native/kernel/index.d.ts @@ -133,25 +133,6 @@ export interface HeaderEntry { name: string value: string } -/** - * Programmatic HTTP/HTTPS proxy configuration, mirroring the kernel's - * internal [`ProxyConfig`]. Supplied as a structured object rather than a - * flattened URL so credentials never have to be percent-encoded into the URL - * and the bypass-host list can be expressed. - * - * - `url` — proxy endpoint, e.g. `"http://proxy.corp.example.com:8080"`. Must - * use the `http://` or `https://` scheme. - * - `username` / `password` — optional proxy basic-auth, applied via - * `reqwest`'s `Proxy::basic_auth` (not embedded in the URL). - * - `bypassHosts` — optional comma-separated host/domain list that should - * bypass the proxy (e.g. `"localhost,*.internal.corp"`). - */ -export interface ProxyInput { - url: string - username?: string - password?: string - bypassHosts?: string -} /** * JS-visible options for opening a Databricks SQL session. * @@ -380,20 +361,23 @@ export interface ConnectionOptions { */ retryOverallTimeoutSecs?: number /** - * Programmatic HTTP/HTTPS proxy ([`ProxyInput`]) to route all kernel - * traffic through. Carries the proxy `url`, optional basic-auth - * `username` / `password`, and an optional `bypassHosts` list — mapped - * field-for-field onto the kernel [`ProxyConfig`]. + * HTTP/HTTPS proxy URL to route all kernel traffic through, e.g. + * `http://proxy.corp.example.com:8080`. Basic-auth credentials may + * be embedded in the URL (`http://user:pass@host:port`); the + * `userinfo` is parsed off and applied as a `Proxy-Authorization` + * header. Must use the `http://` or `https://` scheme. * * Omitted ⇒ the kernel does NOT configure a proxy explicitly and * `reqwest`'s standard behaviour applies — the `HTTPS_PROXY` / - * `HTTP_PROXY` / `NO_PROXY` environment variables are still honoured. - * Setting this **overrides** those env vars. This complements the env-var - * path: callers who cannot set process env vars (e.g. a long-lived Node - * server) can now route a single connection through a proxy - * programmatically. + * `HTTP_PROXY` / `NO_PROXY` environment variables are still + * honoured. Setting this **overrides** those env vars. This + * complements the env-var path: callers who cannot set process + * env vars (e.g. a long-lived Node server) can now route a single + * connection through a proxy programmatically. + * + * Maps onto the kernel [`ProxyConfig::url`]. */ - proxy?: ProxyInput + proxy?: string /** * Per-connection socket read timeout, in milliseconds. Caps how * long a single HTTP round-trip may block waiting on the server From 6e75e49e9d327d6192c9c20a9f05b0d0b8bfb2b3 Mon Sep 17 00:00:00 2001 From: Madhavendra Rathore Date: Wed, 10 Jun 2026 07:49:43 +0000 Subject: [PATCH 2/4] [SEA-NodeJS] feat(kernel): map the socketTimeout ConnectionOption onto the kernel The kernel napi binding exposes `socketTimeoutMs` (kernel `HttpConfig::request_timeout` / reqwest `Client::timeout`, kernel #129). Map the public `socketTimeout` ConnectionOption (ms) onto it in `buildKernelHttpOptions`, so the per-connection read timeout works on the kernel backend just like the Thrift path. Only a positive value is forwarded: `socketTimeout: 0` means "disabled / wait indefinitely" on Thrift, but forwarding `0` would make reqwest time out immediately, so it is omitted (kernel keeps its large default). Verified directly against a live serverless warehouse: `socketTimeout: 1` makes a SEA request time out. Co-authored-by: Isaac Signed-off-by: Madhavendra Rathore --- lib/kernel/KernelAuth.ts | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/lib/kernel/KernelAuth.ts b/lib/kernel/KernelAuth.ts index 0841e652..c94a6a2c 100644 --- a/lib/kernel/KernelAuth.ts +++ b/lib/kernel/KernelAuth.ts @@ -190,6 +190,7 @@ export interface KernelTlsOptions { */ export interface KernelHttpOptions { customHeaders?: Array<{ name: string; value: string }>; + socketTimeoutMs?: number; } /** @@ -414,7 +415,7 @@ function validateHeaderToken(kind: 'name' | 'value', headerName: string, token: } export function buildKernelHttpOptions(options: ConnectionOptions): KernelHttpOptions { - const { customHeaders, userAgentEntry } = options; + const { customHeaders, userAgentEntry, socketTimeout } = options; const headers: Array<{ name: string; value: string }> = []; if (customHeaders) { @@ -436,7 +437,18 @@ export function buildKernelHttpOptions(options: ConnectionOptions): KernelHttpOp // Python connector's unconditional `base_headers` append. headers.push({ name: 'User-Agent', value: buildUserAgentString(userAgentEntry) }); - return { customHeaders: headers }; + const http: KernelHttpOptions = { customHeaders: headers }; + // Per-connection socket read timeout (ms). The public `socketTimeout` + // ConnectionOption maps onto the kernel napi `socketTimeoutMs` + // (kernel `HttpConfig::request_timeout` / reqwest `Client::timeout`). + // Only forward a POSITIVE value: `socketTimeout: 0` means "disabled / wait + // indefinitely" on the Thrift path, but forwarding `0` would make reqwest + // time out immediately, so we omit it and let the kernel keep its (large) + // default — preserving the "effectively no idle timeout" semantics. + if (typeof socketTimeout === 'number' && socketTimeout > 0) { + http.socketTimeoutMs = socketTimeout; + } + return http; } /** From d049d5d186f0af14e4f0da688eb950614ba7af2b Mon Sep 17 00:00:00 2001 From: Madhavendra Rathore Date: Wed, 10 Jun 2026 09:10:51 +0000 Subject: [PATCH 3/4] [SEA-NodeJS] refactor(kernel): pass proxy to the kernel as a structured object MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Change the kernel proxy mapping from a flattened URL string to the structured napi `proxy` object (kernel #129), mirroring the kernel's internal ProxyConfig: `{ url, username?, password?, bypassHosts? }`. `buildKernelProxyOptions` now composes `url` from `protocol://host:port` (no embedded credentials) and forwards `auth.{username,password}` as separate basic-auth fields — eliminating the URL percent-encoding of credentials. The `noProxy` host list is forwarded as `bypassHosts` (previously unexpressible through the URL-string form). Regenerated napi contract (native/kernel/index.d.ts) carries the new `ProxyInput` object type. Verified via mitmproxy (HttpProxyTests, SEA leg): http / https / proxy-with-auth all route through the proxy and the query succeeds. Co-authored-by: Isaac Signed-off-by: Madhavendra Rathore --- lib/kernel/KernelAuth.ts | 42 +++++++++++++++++++++++++--------------- native/kernel/index.d.ts | 42 +++++++++++++++++++++++++++------------- 2 files changed, 55 insertions(+), 29 deletions(-) diff --git a/lib/kernel/KernelAuth.ts b/lib/kernel/KernelAuth.ts index c94a6a2c..295fbb9b 100644 --- a/lib/kernel/KernelAuth.ts +++ b/lib/kernel/KernelAuth.ts @@ -195,14 +195,21 @@ export interface KernelHttpOptions { /** * HTTP(S) proxy forwarded to the napi binding's `ConnectionOptions.proxy` - * (kernel `ProxyConfig.url`). The public `ConnectionOptions.proxy` is the + * (kernel `ProxyConfig`). The public `ConnectionOptions.proxy` is the * Thrift-shaped `{protocol, host, port, auth}`; `buildKernelProxyOptions` - * composes a single proxy URL string (with any basic-auth credentials - * percent-encoded into the `userinfo`) so the SAME connection option works - * on both backends. The napi contract takes a flat `proxy?: string`. + * maps it onto the kernel's structured proxy input — `url` composed from + * `protocol://host:port`, with `auth.{username,password}` forwarded as + * separate basic-auth fields (NOT embedded in the URL, so no percent-encoding + * footgun) and the `noProxy` host list forwarded as `bypassHosts`. The same + * connection option therefore works identically on both backends. */ export interface KernelProxyOptions { - proxy?: string; + proxy?: { + url: string; + username?: string; + password?: string; + bypassHosts?: string; + }; } export type KernelNativeConnectionOptions = KernelSessionDefaults & @@ -541,12 +548,13 @@ export function buildKernelRetryOptions(config: { /** * Map the public `ConnectionOptions.proxy` (`{protocol, host, port, auth}` — - * the same shape the Thrift backend accepts) onto the kernel's napi - * `proxy?: string`. Composes `protocol://[user:pass@]host:port`, percent- - * encoding any `auth.{username,password}` into the URL `userinfo` so - * credentials containing reserved characters (`@`, `:`, `/`) survive intact — - * the kernel parses the userinfo off and applies it as basic-auth. The kernel - * accepts only `http://` / `https://`; a SOCKS protocol surfaces a clear + * the same shape the Thrift backend accepts) onto the kernel's structured napi + * proxy input. The `url` is composed from `protocol://host:port` (no embedded + * credentials); `auth.{username,password}` are forwarded as separate + * basic-auth fields (the kernel applies them via reqwest `Proxy::basic_auth`), + * avoiding any URL percent-encoding footgun. The `noProxy` host list (a driver + * option, not on the published `.d.ts`) is forwarded as `bypassHosts`. The + * kernel accepts only `http://` / `https://`; a SOCKS protocol surfaces a clear * kernel error at connect (reqwest SOCKS support is not compiled in). */ export function buildKernelProxyOptions(options: ConnectionOptions): KernelProxyOptions { @@ -554,12 +562,14 @@ export function buildKernelProxyOptions(options: ConnectionOptions): KernelProxy if (!proxy) { return {}; } - const { username, password } = proxy.auth ?? {}; - const userinfo = - username !== undefined ? `${encodeURIComponent(username)}:${encodeURIComponent(password ?? '')}@` : ''; - return { - proxy: `${proxy.protocol}://${userinfo}${proxy.host}:${proxy.port}`, + const { noProxy } = options as ConnectionOptions & { noProxy?: string }; + const out: NonNullable = { + url: `${proxy.protocol}://${proxy.host}:${proxy.port}`, }; + if (proxy.auth?.username !== undefined) out.username = proxy.auth.username; + if (proxy.auth?.password !== undefined) out.password = proxy.auth.password; + if (typeof noProxy === 'string' && noProxy.length > 0) out.bypassHosts = noProxy; + return { proxy: out }; } export function buildKernelConnectionOptions(options: ConnectionOptions): KernelNativeConnectionOptions { diff --git a/native/kernel/index.d.ts b/native/kernel/index.d.ts index 1b8aee31..0b042121 100644 --- a/native/kernel/index.d.ts +++ b/native/kernel/index.d.ts @@ -133,6 +133,25 @@ export interface HeaderEntry { name: string value: string } +/** + * Programmatic HTTP/HTTPS proxy configuration, mirroring the kernel's + * internal [`ProxyConfig`]. Supplied as a structured object rather than a + * flattened URL so credentials never have to be percent-encoded into the URL + * and the bypass-host list can be expressed. + * + * - `url` — proxy endpoint, e.g. `"http://proxy.corp.example.com:8080"`. Must + * use the `http://` or `https://` scheme. + * - `username` / `password` — optional proxy basic-auth, applied via + * `reqwest`'s `Proxy::basic_auth` (not embedded in the URL). + * - `bypassHosts` — optional comma-separated host/domain list that should + * bypass the proxy (e.g. `"localhost,*.internal.corp"`). + */ +export interface ProxyInput { + url: string + username?: string + password?: string + bypassHosts?: string +} /** * JS-visible options for opening a Databricks SQL session. * @@ -361,23 +380,20 @@ export interface ConnectionOptions { */ retryOverallTimeoutSecs?: number /** - * HTTP/HTTPS proxy URL to route all kernel traffic through, e.g. - * `http://proxy.corp.example.com:8080`. Basic-auth credentials may - * be embedded in the URL (`http://user:pass@host:port`); the - * `userinfo` is parsed off and applied as a `Proxy-Authorization` - * header. Must use the `http://` or `https://` scheme. + * Programmatic HTTP/HTTPS proxy ([`ProxyInput`]) to route all kernel + * traffic through. Carries the proxy `url`, optional basic-auth + * `username` / `password`, and an optional `bypassHosts` list — mapped + * field-for-field onto the kernel [`ProxyConfig`]. * * Omitted ⇒ the kernel does NOT configure a proxy explicitly and * `reqwest`'s standard behaviour applies — the `HTTPS_PROXY` / - * `HTTP_PROXY` / `NO_PROXY` environment variables are still - * honoured. Setting this **overrides** those env vars. This - * complements the env-var path: callers who cannot set process - * env vars (e.g. a long-lived Node server) can now route a single - * connection through a proxy programmatically. - * - * Maps onto the kernel [`ProxyConfig::url`]. + * `HTTP_PROXY` / `NO_PROXY` environment variables are still honoured. + * Setting this **overrides** those env vars. This complements the env-var + * path: callers who cannot set process env vars (e.g. a long-lived Node + * server) can now route a single connection through a proxy + * programmatically. */ - proxy?: string + proxy?: ProxyInput /** * Per-connection socket read timeout, in milliseconds. Caps how * long a single HTTP round-trip may block waiting on the server From e80af04a1d97779f5b338ae87afabd4220237438 Mon Sep 17 00:00:00 2001 From: Madhavendra Rathore Date: Wed, 10 Jun 2026 11:19:32 +0000 Subject: [PATCH 4/4] chore(kernel): bump KERNEL_REV to kernel v0.2.0 (0d46716) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Point the bundled kernel binding at the latest kernel main — the v0.2.0 beta release (databricks/databricks-sql-kernel@0d46716), which includes the merged proxy + per-connection socketTimeout napi surface (#129) this PR maps onto, plus the query-tags wire support (#150). The kernel-e2e CI gate builds the napi from this SHA. Verified: proxy routes through mitmproxy and SELECT round-trips on the v0.2.0 binding. Co-authored-by: Isaac Signed-off-by: Madhavendra Rathore --- KERNEL_REV | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/KERNEL_REV b/KERNEL_REV index cd127107..7dd91996 100644 --- a/KERNEL_REV +++ b/KERNEL_REV @@ -1 +1 @@ -34b4c202cc127021c923903d59c841daa2b44fef \ No newline at end of file +0d46716c466897148dfc1d2976ff03bdf097998c