From 999617422f409d4bffb1b99ab23855aa302fd1c3 Mon Sep 17 00:00:00 2001
From: Michael Heller <21163552+mdheller@users.noreply.github.com>
Date: Fri, 19 Jun 2026 22:05:33 -0400
Subject: [PATCH 1/2] =?UTF-8?q?fix(macos-shell):=20guard=20=5FserverTrust?=
=?UTF-8?q?=20KVC=20=E2=80=94=20crashed=20on=20every=20navigation?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
The native WKWebView shell aborted on EVERY finished navigation on macOS 26 /
WebKit 21623. webView:didFinishNavigation: read the private WKWebView ivar via
[wv valueForKey:@"_serverTrust"] with a comment claiming it was 'graceful if
absent' — but valueForKey: on an UNDEFINED key raises NSUndefinedKeyException,
it does not return nil. _serverTrust was removed/renamed on newer WebKit, so
the unhandled exception called abort() (SIGABRT) right after page load.
Wrap the private-KVC read in @try/@catch (trust=NULL on miss), matching how the
other private-API calls in this file are already guarded. Verified: compiles +
links cleanly; rebuilt binary installed over the crashing app.
Crash: -[BBDelegate webView:didFinishNavigation:] -> -[WKWebView
valueForUndefinedKey:] -> NSException -> abort().
---
native/macos/BearBrowserWebKitLauncher.m | 9 +++++++--
1 file changed, 7 insertions(+), 2 deletions(-)
diff --git a/native/macos/BearBrowserWebKitLauncher.m b/native/macos/BearBrowserWebKitLauncher.m
index d4e3606..ce1a220 100644
--- a/native/macos/BearBrowserWebKitLauncher.m
+++ b/native/macos/BearBrowserWebKitLauncher.m
@@ -4687,9 +4687,14 @@ - (void)webView:(WKWebView *)wv didFinishNavigation:(WKNavigation *)nav {
dispatch_async(dispatch_get_global_queue(QOS_CLASS_UTILITY,0),^{
[[BBHistoryStore shared] recordTitle:tab.title url:url];
});
- // Capture server trust for cert inspector — private KVC, graceful if absent
+ // Capture server trust for cert inspector — private KVC. `_serverTrust` was
+ // removed/renamed on newer WebKit (macOS 26 / WebKit 21623+), and valueForKey:
+ // on an UNDEFINED key raises NSUndefinedKeyException (it does NOT return nil),
+ // which would abort the app on every finished navigation. Guard it.
if (wv==self.webView) {
- SecTrustRef trust=(__bridge SecTrustRef)[wv valueForKey:@"_serverTrust"];
+ SecTrustRef trust=NULL;
+ @try { trust=(__bridge SecTrustRef)[wv valueForKey:@"_serverTrust"]; }
+ @catch (NSException *e) { trust=NULL; }
if (trust) { CFRetain(trust); if(self.currentTrust) CFRelease(self.currentTrust); self.currentTrust=trust; }
else { if(self.currentTrust){ CFRelease(self.currentTrust); self.currentTrust=nil; } }
}
From 14ae75d7bd2bbbfdfdb8c1396030e03ec577624d Mon Sep 17 00:00:00 2001
From: Michael Heller <21163552+mdheller@users.noreply.github.com>
Date: Fri, 19 Jun 2026 22:16:59 -0400
Subject: [PATCH 2/2] build(macos-shell): reproducible native-shell build
script
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Nothing in the repo compiled the native WKWebView shell into a .app — it was
built by hand, which is exactly how the _serverTrust KVC crash shipped without
anyone noticing. Add scripts/bearbrowser-build-native-shell.sh:
1. gates on verify-native-macos-shell.sh (contract greps + throwaway compile)
2. compiles BearBrowserWebKitLauncher.m (Cocoa/WebKit/AVFoundation/Security)
3. renders Info.plist from packaging/macos/Info.plist.template
4. installs bundle resources (BearBrowser-start.html, fonts/, icon)
5. ad-hoc signs + clears quarantine
6. optional --install to replace /Applications/BearBrowser.app
Verified: full run produces a valid signed BearBrowser.app. Now this whole
class of bug is caught by the gate before a build can ship.
---
scripts/bearbrowser-build-native-shell.sh | 102 ++++++++++++++++++++++
1 file changed, 102 insertions(+)
create mode 100755 scripts/bearbrowser-build-native-shell.sh
diff --git a/scripts/bearbrowser-build-native-shell.sh b/scripts/bearbrowser-build-native-shell.sh
new file mode 100755
index 0000000..e75e168
--- /dev/null
+++ b/scripts/bearbrowser-build-native-shell.sh
@@ -0,0 +1,102 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+# Builds the native macOS WKWebView shell (native/macos/BearBrowserWebKitLauncher.m)
+# into BearBrowser.app. Until now nothing in the repo compiled this into a bundle —
+# it was done by hand, which is how the _serverTrust KVC crash shipped unnoticed.
+# This makes it reproducible and gates on verify-native-macos-shell.sh first.
+
+script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+repo_root="${BEARBROWSER_HOME:-$(cd "$script_dir/.." && pwd)}"
+
+version="0.1.0-overlay"
+out_dir="$repo_root/build/macos-native"
+do_install=""
+
+usage() {
+ cat <<'USAGE'
+Usage: bearbrowser-build-native-shell [--version V] [--out-dir DIR] [--install]
+
+Compiles the native macOS WKWebView shell into BearBrowser.app.
+
+ --version CFBundle version string. Default: 0.1.0-overlay
+ --out-dir Output directory for the .app. Default: build/macos-native
+ --install Also install to /Applications/BearBrowser.app (replaces binary,
+ re-signs ad-hoc, clears quarantine).
+USAGE
+}
+
+while [ "$#" -gt 0 ]; do
+ case "$1" in
+ --version) version="${2:?missing version}"; shift 2 ;;
+ --out-dir) out_dir="${2:?missing out-dir}"; shift 2 ;;
+ --install) do_install="1"; shift ;;
+ -h|--help) usage; exit 0 ;;
+ *) echo "ERROR: unknown argument: $1" >&2; usage >&2; exit 1 ;;
+ esac
+done
+
+if [ "$(uname -s)" != "Darwin" ]; then
+ echo "ERROR: the native macOS shell only builds on macOS (Cocoa/WebKit)." >&2
+ exit 1
+fi
+
+src="$repo_root/native/macos/BearBrowserWebKitLauncher.m"
+landing="$repo_root/native/macos/BearBrowser-start.html"
+fonts_dir="$repo_root/native/macos/fonts"
+info_template="$repo_root/packaging/macos/Info.plist.template"
+app="$out_dir/BearBrowser.app"
+
+for f in "$src" "$landing" "$info_template"; do
+ [ -f "$f" ] || { echo "ERROR: missing required input: $f" >&2; exit 1; }
+done
+
+# Gate: source contract + a throwaway compile must pass before we bundle anything.
+echo "[1/6] Verifying native shell contract (verify-native-macos-shell.sh)..."
+bash "$repo_root/scripts/verify-native-macos-shell.sh"
+
+echo "[2/6] Compiling BearBrowserWebKitLauncher.m..."
+mkdir -p "$app/Contents/MacOS" "$app/Contents/Resources"
+clang -fobjc-arc -O2 \
+ -framework Cocoa -framework WebKit -framework AVFoundation -framework Security \
+ "$src" -o "$app/Contents/MacOS/BearBrowser"
+
+echo "[3/6] Rendering Info.plist (version=$version)..."
+python3 - "$info_template" "$app/Contents/Info.plist" "$version" <<'PY'
+from pathlib import Path
+import sys
+src, dst, version = Path(sys.argv[1]), Path(sys.argv[2]), sys.argv[3]
+text = src.read_text().replace('0.1.0-overlay', f'{version}')
+dst.write_text(text)
+PY
+
+echo "[4/6] Installing bundle resources (start page, fonts, icon)..."
+cp "$landing" "$app/Contents/Resources/BearBrowser-start.html"
+[ -d "$fonts_dir" ] && cp -R "$fonts_dir" "$app/Contents/Resources/fonts"
+# Icon: reuse the one already installed, or a packaged icns if present.
+icon=""
+for cand in "/Applications/BearBrowser.app/Contents/Resources/BearBrowser.icns" \
+ "$repo_root/packaging/macos/BearBrowser.icns"; do
+ [ -f "$cand" ] && { icon="$cand"; break; }
+done
+[ -n "$icon" ] && cp "$icon" "$app/Contents/Resources/BearBrowser.icns" || \
+ echo " note: no BearBrowser.icns found — bundle uses the default icon"
+
+echo "[5/6] Signing (ad-hoc) + clearing quarantine..."
+xattr -dr com.apple.quarantine "$app" 2>/dev/null || true
+codesign --force --sign - "$app" >/dev/null 2>&1 || \
+ echo " WARNING: ad-hoc signing returned non-zero (may still run)"
+
+echo "[6/6] Built: $app"
+
+if [ -n "$do_install" ]; then
+ dest="/Applications/BearBrowser.app"
+ echo "Installing to $dest ..."
+ mkdir -p "$dest/Contents/MacOS" "$dest/Contents/Resources"
+ cp "$app/Contents/MacOS/BearBrowser" "$dest/Contents/MacOS/BearBrowser"
+ cp "$app/Contents/Info.plist" "$dest/Contents/Info.plist"
+ cp -R "$app/Contents/Resources/." "$dest/Contents/Resources/"
+ xattr -dr com.apple.quarantine "$dest" 2>/dev/null || true
+ codesign --force --sign - "$dest" >/dev/null 2>&1 || true
+ echo "Installed: $dest (relaunch it)"
+fi