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