fix: propagate system proxy settings to user requests (INS-2943) (#10228)

* fix: propagate system proxy settings to user requests (INS-2943)

When proxyEnabled is false in Insomnia Preferences, the request engine
(node-libcurl) now falls back to the OS system proxy via Electron's
session.defaultSession.resolveProxy(), matching the behavior of
Insomnia's internal API calls.

Previously, disabling the proxy toggle explicitly set CURLOPT_PROXY to
an empty string, which prevented libcurl from using any proxy including
the system proxy. This caused user requests to bypass the OS proxy while
Insomnia's own network calls (via Chromium's network stack) correctly
respected it.

Changes:
- Add parseResolvedProxy() helper to parse PAC-format proxy strings
- Call resolveProxy() inside createConfiguredCurlInstance() when proxy
  is not explicitly configured
- Refactor api.protocol.ts to use the shared helper (removes ~50 lines
  of duplicated proxy parsing logic)

* fix: catch resolveProxy errors and fall back to direct connection

* fix: catch resolveProxy errors in api.protocol.ts

* fix: remove redundant comment about proxy mode in updateProxy function
This commit is contained in:
yaoweiprc
2026-07-09 18:11:01 +08:00
committed by GitHub
parent 1a3bed2d77
commit aff48ee919
4 changed files with 89 additions and 66 deletions

View File

@@ -2,12 +2,12 @@ import path from 'node:path';
import { Readable } from 'node:stream';
import { parse as urlParse } from 'node:url';
import { Curl, CurlAuth, CurlFeature, CurlProxy, CurlSslOpt, type HeaderInfo } from '@getinsomnia/node-libcurl';
import { Curl, CurlAuth, CurlFeature, CurlSslOpt, type HeaderInfo } from '@getinsomnia/node-libcurl';
import { app, net, protocol, session } from 'electron';
import { services } from 'insomnia-data';
import { getApiBaseURL } from '../common/constants';
import { setDefaultProtocol, shouldBypassProxyForHost } from './network/libcurl-promise';
import { parseResolvedProxy, setDefaultProtocol, shouldBypassProxyForHost } from './network/libcurl-promise';
import { resolveDbByKey } from './templating-worker-database';
export interface RegisterProtocolOptions {
@@ -54,7 +54,12 @@ export async function registerInsomniaProtocols() {
const settings = await services.settings.get();
// systemProxy follows the PAC return value format.
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Proxy_servers_and_tunneling/Proxy_Auto-Configuration_PAC_file#return_value_format
let systemProxyStr = await session.defaultSession.resolveProxy(urlStr);
let systemProxyStr: string | undefined;
try {
systemProxyStr = await session.defaultSession.resolveProxy(urlStr);
} catch {
// If resolveProxy fails, fall back to direct connection
}
// here we use libcurl to forward the SSE request because the SSE request sent by net.fetch can not be disconnected correctly in some cases
// see https://github.com/electron/electron/issues/47097
@@ -68,64 +73,12 @@ export async function registerInsomniaProtocols() {
if (!settings.proxyEnabled) {
// follow system proxy
if (!systemProxyStr) {
// if systemProxy is empty, it means no proxy is used
systemProxyStr = 'DIRECT';
}
const proxy = systemProxyStr
.trim()
.split(/\s*;\s*/g)
.find(Boolean);
// only the first proxy specified will be used
const firstProxy = proxy;
const parts = firstProxy?.split(/\s+/);
const proxyType = parts?.[0];
if (proxyType === 'DIRECT') {
curl.setOpt(Curl.option.PROXY, '');
const resolved = parseResolvedProxy(systemProxyStr);
if (resolved) {
curl.setOpt(Curl.option.PROXYTYPE, resolved.proxyType);
curl.setOpt(Curl.option.PROXY, resolved.proxyUrl);
} else {
let unknownProxy = false;
let curlOptProxyType = CurlProxy.Http;
switch (proxyType) {
case 'PROXY': {
curlOptProxyType = CurlProxy.Http;
break;
}
case 'HTTP': {
curlOptProxyType = CurlProxy.Http;
break;
}
case 'SOCKS': {
curlOptProxyType = CurlProxy.Socks4;
break;
}
case 'HTTPS': {
curlOptProxyType = CurlProxy.Https;
break;
}
case 'SOCKS4': {
curlOptProxyType = CurlProxy.Socks4;
break;
}
case 'SOCKS5': {
curlOptProxyType = CurlProxy.Socks5;
break;
}
default: {
// unknown proxy type
unknownProxy = true;
break;
}
}
if (unknownProxy) {
curl.setOpt(Curl.option.PROXY, '');
} else if (parts?.[1]) {
curl.setOpt(Curl.option.PROXYTYPE, curlOptProxyType);
curl.setOpt(Curl.option.PROXY, parts[1]);
}
curl.setOpt(Curl.option.PROXY, '');
}
} else {
const { protocol, hostname } = urlParse(urlStr);

View File

@@ -152,14 +152,15 @@ const openCurlConnection = async (
try {
invariant(options.url, 'URL must be defined');
invariant(!options.url.startsWith('file://'), 'Local file URIs are not supported');
const readyStateChannel = `${protocolName}.${request._id}.${REALTIME_EVENTS_CHANNELS.READY_STATE}`;
const settings = await services.settings.get();
const start = performance.now();
const clientCertificates = await services.clientCertificate.findByParentId(options.workspaceId);
const filteredClientCertificates = filterClientCertificates(clientCertificates, options.url, 'https:');
const { curl, debugTimeline } = createConfiguredCurlInstance({
const { curl, debugTimeline } = await createConfiguredCurlInstance({
req: { ...request, cookieJar: options.cookieJar, cookies: [], suppressUserAgent: options.suppressUserAgent },
finalUrl: options.url,
settings,

View File

@@ -13,6 +13,7 @@ import {
CurlHttpVersion,
CurlInfoDebug,
CurlNetrc,
CurlProxy,
CurlSslOpt,
} from '@getinsomnia/node-libcurl';
import { isValid } from 'date-fns';
@@ -123,7 +124,7 @@ export const curlRequest = (options: CurlRequestOptions) =>
invariant(!finalUrl.startsWith('file://'), 'Local file URIs are not supported');
const caCert = caCertficatePath && (await insecureReadFile(caCertficatePath));
const { curl, debugTimeline } = createConfiguredCurlInstance({
const { curl, debugTimeline } = await createConfiguredCurlInstance({
req,
finalUrl,
settings,
@@ -286,7 +287,61 @@ export const curlRequest = (options: CurlRequestOptions) =>
}
});
export const createConfiguredCurlInstance = ({
/**
* Parse a PAC-format proxy string (from Electron's session.resolveProxy) into
* a curl proxy URL and type. Returns null when DIRECT.
* Format: "PROXY host:port", "HTTPS host:port", "SOCKS host:port", etc.
* https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Proxy_servers_and_tunneling/Proxy_Auto-Configuration_PAC_file#return_value_format
*/
export function parseResolvedProxy(pacString: string | undefined): { proxyUrl: string; proxyType: CurlProxy } | null {
if (!pacString) {
return null;
}
// only the first proxy specified will be used
const proxy = pacString
.trim()
.split(/\s*;\s*/g)
.find(Boolean);
if (!proxy) {
return null;
}
const parts = proxy.split(/\s+/);
const proxyType = parts[0];
if (proxyType === 'DIRECT') {
return null;
}
const proxyAddr = parts[1];
if (!proxyAddr) {
return null;
}
let curlProxyType: CurlProxy;
switch (proxyType) {
case 'PROXY':
case 'HTTP': {
curlProxyType = CurlProxy.Http;
break;
}
case 'HTTPS': {
curlProxyType = CurlProxy.Https;
break;
}
case 'SOCKS':
case 'SOCKS4': {
curlProxyType = CurlProxy.Socks4;
break;
}
case 'SOCKS5': {
curlProxyType = CurlProxy.Socks5;
break;
}
default: {
return null;
}
}
return { proxyUrl: proxyAddr, proxyType: curlProxyType };
}
export const createConfiguredCurlInstance = async ({
req,
finalUrl,
settings,
@@ -365,7 +420,21 @@ export const createConfiguredCurlInstance = ({
}
if (!settings.proxyEnabled) {
curl.setOpt(Curl.option.PROXY, '');
// When proxy is not explicitly configured, fall back to system proxy
let resolved: ReturnType<typeof parseResolvedProxy> = null;
try {
const systemProxy = await electron.session.defaultSession.resolveProxy(finalUrl);
resolved = parseResolvedProxy(systemProxy);
} catch {
// If resolveProxy fails (e.g. invalid URL, session issues), fall back to direct connection
}
if (resolved) {
curl.setOpt(Curl.option.PROXYTYPE, resolved.proxyType);
curl.setOpt(Curl.option.PROXY, resolved.proxyUrl);
debugTimeline.push({ value: `Using system proxy: ${resolved.proxyUrl}`, name: 'Text', timestamp: Date.now() });
} else {
curl.setOpt(Curl.option.PROXY, '');
}
} else {
const { protocol, hostname } = urlParse(req.url);
const { httpProxy, httpsProxy, noProxy } = settings;

View File

@@ -27,10 +27,10 @@ async function updateProxy() {
}
// Set proxy rules in the main session https://www.electronjs.org/docs/latest/api/structures/proxy-config
// no mode here — it overrides proxyRules ('system' ignores them)
await session.defaultSession.setProxy({
proxyRules: proxyRules.join(';'),
proxyBypassRules: noProxy ?? '',
mode: 'fixed_servers',
});
return;
} catch (err) {