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.
This commit is contained in:
Timothy Jaeryang Baek 2026-04-24 23:59:25 +09:00
parent eb3c569078
commit 37f0891840
2 changed files with 32 additions and 3 deletions

View file

@ -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)
})

View file

@ -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<boolean> => {
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<boolean> => {
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