fix(electron): load self-signed loopback pages

This commit is contained in:
Bohdan Triapitsyn
2026-08-18 23:10:14 +03:00
parent b754ccbf63
commit 0c5e183c62
4 changed files with 66 additions and 1 deletions
+3 -1
View File
@@ -159,7 +159,9 @@ Use an explicit override when testing a different OpenCode CLI build or when a u
grants permission requests by default when no handler is set, and the panel
loads whatever address the user types. Tab favicons are fetched in this
session too, so icons behind the page's own login resolve and the app's origin
never requests anything from a third-party host.
never requests anything from a third-party host. Self-signed loopback HTTPS
pages may use an untrusted certificate authority; certificate failures for
external hosts and all other certificate errors remain blocked.
## IPC Pattern
@@ -0,0 +1,12 @@
const LOOPBACK_HOSTNAMES = new Set(['localhost', '127.0.0.1', '[::1]']);
export const shouldAllowBrowserPanelCertificateError = ({ url, error }) => {
if (error !== 'net::ERR_CERT_AUTHORITY_INVALID') return false;
try {
const parsed = new URL(url);
return parsed.protocol === 'https:' && LOOPBACK_HOSTNAMES.has(parsed.hostname.toLowerCase());
} catch {
return false;
}
};
@@ -0,0 +1,41 @@
import assert from 'node:assert/strict';
import test from 'node:test';
import { shouldAllowBrowserPanelCertificateError } from './browser-panel-security.mjs';
test('allows untrusted certificate authorities for loopback HTTPS pages', () => {
for (const url of [
'https://localhost:58580/',
'https://127.0.0.1:58580/',
'https://[::1]:58580/',
]) {
assert.equal(shouldAllowBrowserPanelCertificateError({
url,
error: 'net::ERR_CERT_AUTHORITY_INVALID',
}), true);
}
});
test('keeps certificate validation for non-loopback pages', () => {
for (const url of [
'https://example.com/',
'https://localhost.example.com/',
'https://0.0.0.0:58580/',
]) {
assert.equal(shouldAllowBrowserPanelCertificateError({
url,
error: 'net::ERR_CERT_AUTHORITY_INVALID',
}), false);
}
});
test('does not bypass other certificate failures or malformed URLs', () => {
assert.equal(shouldAllowBrowserPanelCertificateError({
url: 'https://localhost:58580/',
error: 'net::ERR_CERT_DATE_INVALID',
}), false);
assert.equal(shouldAllowBrowserPanelCertificateError({
url: 'not a url',
error: 'net::ERR_CERT_AUTHORITY_INVALID',
}), false);
});
+10
View File
@@ -31,6 +31,7 @@ import {
setLinuxAutostartEnabled,
} from './linux-autostart.mjs';
import { unsupportedAppSpecificOpenError, validateLocalPath } from './path-open-utils.mjs';
import { shouldAllowBrowserPanelCertificateError } from './browser-panel-security.mjs';
import { mintOutsideFileGrant } from '@openchamber/web/server/lib/fs/routes.js';
const execFileAsync = promisify(execFile);
@@ -1186,6 +1187,15 @@ const resolveBrowserPanelContents = (rawId) => {
const hardenBrowserPanelSession = () => {
const panelSession = session.fromPartition(BROWSER_PANEL_PARTITION);
app.on('certificate-error', (event, contents, url, error, _certificate, callback) => {
if (contents.session === panelSession && shouldAllowBrowserPanelCertificateError({ url, error })) {
event.preventDefault();
callback(true);
return;
}
callback(false);
});
panelSession.setPermissionRequestHandler((_contents, permission, callback, details) => {
log.info('[electron] browser panel denied a permission request', {
permission,