From 37f0891840ae555ffdbfcbadbcfd422491c70e27 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Fri, 24 Apr 2026 23:59:25 +0900 Subject: [PATCH] fix(#108): trust all SSL certificates for HTTPS connections Bypass certificate verification globally so the desktop app can connect to Open WebUI instances using self-signed, private-CA, ZeroSSL, or any other non-publicly-trusted certificates. Three layers: - session.defaultSession.setCertificateVerifyProc: covers net.fetch() used by validateRemoteUrl / checkUrlAndOpen - app.on('session-created'): covers partitioned webview sessions (persist:connection-*) including in-page API calls - app.on('certificate-error'): fallback for BrowserWindow navigations Also switches validateRemoteUrl and checkUrlAndOpen from Node fetch() to Electron net.fetch() so they route through Chromium's network stack and respect the session certificate overrides. --- src/main/index.ts | 29 +++++++++++++++++++++++++++++ src/main/utils/index.ts | 6 +++--- 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/src/main/index.ts b/src/main/index.ts index c5fcba6..faa1604 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -1065,6 +1065,35 @@ if (!gotTheLock) { log.info('Running with GPU sandbox disabled (marker file present)') } + // ─── Self-Signed / Untrusted Certificate Support ─ + // Allow connections to Open WebUI instances that use self-signed or + // otherwise untrusted SSL certificates (issue #108). The user + // explicitly configures the server URL, so trusting all certs is + // acceptable — this matches the behaviour of VS Code, Postman, and + // other Electron apps used in enterprise/self-hosted environments. + app.on('certificate-error', (event, _webContents, url, error, certificate, callback) => { + log.warn( + `Certificate error: ${error} for ${url} ` + + `(subject: ${certificate.subjectName}, issuer: ${certificate.issuerName})` + ) + event.preventDefault() + callback(true) + }) + + // Trust all certs on the default session (used by net.fetch() in + // validateRemoteUrl / checkUrlAndOpen). + session.defaultSession.setCertificateVerifyProc((_request, callback) => { + callback(0) // 0 = verified/trusted + }) + + // Webviews use partitioned sessions (persist:connection-*). Each + // new partition's session also needs to trust all certs. + app.on('session-created', (newSession) => { + newSession.setCertificateVerifyProc((_request, callback) => { + callback(0) + }) + }) + app.on('browser-window-created', (_, window) => { optimizer.watchWindowShortcuts(window) }) diff --git a/src/main/utils/index.ts b/src/main/utils/index.ts index 86fd436..4d1153b 100644 --- a/src/main/utils/index.ts +++ b/src/main/utils/index.ts @@ -8,7 +8,7 @@ import crypto from 'crypto' import * as tar from 'tar' -import { app, shell, Notification } from 'electron' +import { app, shell, Notification, net as electronNet } from 'electron' import { execFileSync, exec, spawn, execSync, execFile } from 'child_process' import log from 'electron-log' @@ -755,7 +755,7 @@ export const checkUrlAndOpen = async (url: string, callback: Function = async () const checkUrl = async (): Promise => { try { - const response = await fetch(url, { method: 'HEAD' }) + const response = await electronNet.fetch(url, { method: 'HEAD' }) return response.ok } catch { return false @@ -783,7 +783,7 @@ export const checkUrlAndOpen = async (url: string, callback: Function = async () export const validateRemoteUrl = async (url: string): Promise => { try { - const response = await fetch(url, { method: 'HEAD', signal: AbortSignal.timeout(5000) }) + const response = await electronNet.fetch(url, { method: 'HEAD', signal: AbortSignal.timeout(5000) }) return response.ok } catch { return false