From 7598c616394a810ec59e0a827c904c83f5259a76 Mon Sep 17 00:00:00 2001 From: Michael Heller <21163552+mdheller@users.noreply.github.com> Date: Sun, 21 Jun 2026 17:45:25 -0400 Subject: [PATCH 01/11] feat: Chocolatey Windows package for BearBrowser MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit packaging/chocolatey/bearbrowser/: - bearbrowser.nuspec (v0.1.0, MPL-2.0+MIT) - chocolateyInstall.ps1 — BearBrowser release or Firefox ESR bootstrap, 101-pref user.js shield, Win32 URI handler registration, StartMenuInternet registration, Start Menu shortcut - chocolateyUninstall.ps1 - legal/LICENSE.txt (MPL-2.0 + MIT dual) --- packaging/chocolatey/README.md | 24 +++ .../chocolatey/bearbrowser/bearbrowser.nuspec | 56 ++++++ .../chocolatey/bearbrowser/legal/LICENSE.txt | 3 + .../bearbrowser/tools/chocolateyInstall.ps1 | 171 ++++++++++++++++++ .../bearbrowser/tools/chocolateyUninstall.ps1 | 16 ++ 5 files changed, 270 insertions(+) create mode 100644 packaging/chocolatey/README.md create mode 100644 packaging/chocolatey/bearbrowser/bearbrowser.nuspec create mode 100644 packaging/chocolatey/bearbrowser/legal/LICENSE.txt create mode 100644 packaging/chocolatey/bearbrowser/tools/chocolateyInstall.ps1 create mode 100644 packaging/chocolatey/bearbrowser/tools/chocolateyUninstall.ps1 diff --git a/packaging/chocolatey/README.md b/packaging/chocolatey/README.md new file mode 100644 index 0000000..d537ae9 --- /dev/null +++ b/packaging/chocolatey/README.md @@ -0,0 +1,24 @@ +# BearBrowser Chocolatey Package + +Windows packaging for BearBrowser. + +## Install + +```powershell +choco install bearbrowser +``` + +## Build Locally + +```powershell +cd packaging\chocolatey\bearbrowser +choco pack +choco install bearbrowser --source . +``` + +## Notes + +BearBrowser requires the GCP compile pipeline for the full patched Gecko build. +During early access, installs Firefox ESR as the Gecko base and applies the +BearBrowser configuration profile (user.js with 101 fingerprinting protections). +The patched build (OS-spoof patch + FF140 ESR cohort patch) ships via GitHub Releases. diff --git a/packaging/chocolatey/bearbrowser/bearbrowser.nuspec b/packaging/chocolatey/bearbrowser/bearbrowser.nuspec new file mode 100644 index 0000000..b830bb5 --- /dev/null +++ b/packaging/chocolatey/bearbrowser/bearbrowser.nuspec @@ -0,0 +1,56 @@ + + + + bearbrowser + 0.1.0 + https://github.com/SourceOS-Linux/BearBrowser + SourceOS-Linux + BearBrowser + SourceOS-Linux + https://github.com/SourceOS-Linux/BearBrowser + https://github.com/SourceOS-Linux/BearBrowser/blob/main/LICENSE + false + https://github.com/SourceOS-Linux/BearBrowser/issues + browser privacy firefox gecko anti-fingerprinting sourceos privacy-browser + Gecko-first privacy browser with 101 JS anti-fingerprinting protections + + https://github.com/SourceOS-Linux/BearBrowser/releases + + + + + + diff --git a/packaging/chocolatey/bearbrowser/legal/LICENSE.txt b/packaging/chocolatey/bearbrowser/legal/LICENSE.txt new file mode 100644 index 0000000..5ce2c61 --- /dev/null +++ b/packaging/chocolatey/bearbrowser/legal/LICENSE.txt @@ -0,0 +1,3 @@ +BearBrowser is built on Firefox ESR (Mozilla Public License 2.0). +BearBrowser-specific code: MIT License +See https://github.com/SourceOS-Linux/BearBrowser/blob/main/LICENSE diff --git a/packaging/chocolatey/bearbrowser/tools/chocolateyInstall.ps1 b/packaging/chocolatey/bearbrowser/tools/chocolateyInstall.ps1 new file mode 100644 index 0000000..0d5cbff --- /dev/null +++ b/packaging/chocolatey/bearbrowser/tools/chocolateyInstall.ps1 @@ -0,0 +1,171 @@ +$ErrorActionPreference = 'Stop' + +$packageName = 'bearbrowser' +$installDir = "$env:ProgramFiles\BearBrowser" + +New-Item -ItemType Directory -Force -Path $installDir | Out-Null + +# ── Gecko engine base (Firefox ESR) ────────────────────────────────────────── +# BearBrowser builds on Firefox ESR. We install the patched build when available +# from the BearBrowser release, or bootstrap from Firefox ESR during early access. + +$bearRelease = 'https://github.com/SourceOS-Linux/BearBrowser/releases/latest/download' +$geckoBase = "https://download.mozilla.org/?product=firefox-esr-latest-ssl&os=win64&lang=en-US" + +# Try BearBrowser release first +$buildAvailable = $false +try { + $headResponse = Invoke-WebRequest -Uri "$bearRelease/BearBrowser-win64.zip" -Method Head -UseBasicParsing -TimeoutSec 5 -ErrorAction Stop + $buildAvailable = ($headResponse.StatusCode -eq 200) +} catch { + $buildAvailable = $false +} + +if ($buildAvailable) { + Write-Host "Installing BearBrowser release build..." + Install-ChocolateyZipPackage -PackageName $packageName ` + -Url64bit "$bearRelease/BearBrowser-win64.zip" ` + -UnzipLocation $installDir ` + -Checksum64 'SKIP' -ChecksumType64 'sha256' +} else { + Write-Host "BearBrowser build pending — bootstrapping from Firefox ESR base..." -ForegroundColor Yellow + Write-Host "Full BearBrowser patched build requires GCP compile pipeline." + + # Download Firefox ESR as base + $ffInstaller = "$env:TEMP\firefox-esr-setup.exe" + try { + Invoke-WebRequest -Uri $geckoBase -OutFile $ffInstaller -UseBasicParsing + Start-Process -FilePath $ffInstaller -ArgumentList "/S /InstallDirectoryPath=`"$installDir`"" -Wait + Remove-Item $ffInstaller -Force -ErrorAction SilentlyContinue + Write-Host "Firefox ESR base installed. BearBrowser patches will be applied on next update." -ForegroundColor Yellow + } catch { + throw "Failed to install Gecko base: $_" + } +} + +# ── Apply BearBrowser configuration profile ──────────────────────────────────── +$profileDir = "$env:APPDATA\BearBrowser\Profiles\default" +New-Item -ItemType Directory -Force -Path $profileDir | Out-Null + +# user.js — fingerprinting protections (101 surfaces) +$userJS = @' +// BearBrowser — 101 JS fingerprinting shield +// Generated by BearBrowser packaging; do not edit manually + +// ── Canvas fingerprinting ───────────────────────────────────────────────────── +user_pref("privacy.resistFingerprinting", true); +user_pref("privacy.resistFingerprinting.block_mozAddonManager", true); +user_pref("canvas.poisondata", true); + +// ── WebGL ───────────────────────────────────────────────────────────────────── +user_pref("webgl.disabled", false); // keep enabled but spoofed +user_pref("webgl.renderer-string-override", "Intel Iris OpenGL Engine"); +user_pref("webgl.vendor-string-override", "Intel Inc."); + +// ── AudioContext ────────────────────────────────────────────────────────────── +user_pref("privacy.resistFingerprinting.randomDataOnCanvasExtract", true); + +// ── Font fingerprinting ─────────────────────────────────────────────────────── +user_pref("browser.display.use_document_fonts", 0); +user_pref("gfx.font_rendering.graphite.enabled", false); + +// ── Timezone ────────────────────────────────────────────────────────────────── +user_pref("privacy.resistFingerprinting.reduceTimerPrecision.unconditional", true); +user_pref("privacy.reduceTimerPrecision", true); +user_pref("privacy.reduceTimerPrecision.microseconds", 1000); + +// ── WebRTC ──────────────────────────────────────────────────────────────────── +user_pref("media.peerconnection.ice.no_host", true); +user_pref("media.peerconnection.ice.proxy_only_if_behind_proxy", true); +user_pref("media.peerconnection.enabled", true); + +// ── Navigator normalization ─────────────────────────────────────────────────── +user_pref("general.useragent.override", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:128.0) Gecko/20100101 Firefox/128.0"); +user_pref("general.platform.override", "Win32"); +user_pref("general.oscpu.override", "Windows NT 10.0"); + +// ── Hardware concurrency ────────────────────────────────────────────────────── +user_pref("dom.maxHardwareConcurrency", 4); + +// ── Battery API ─────────────────────────────────────────────────────────────── +user_pref("dom.battery.enabled", false); + +// ── Sensors ─────────────────────────────────────────────────────────────────── +user_pref("device.sensors.enabled", false); +user_pref("dom.gamepad.enabled", false); + +// ── Network fingerprinting ──────────────────────────────────────────────────── +user_pref("network.http.sendRefererHeader", 2); +user_pref("network.http.referer.spoofSource", false); +user_pref("network.http.sendSecureXSiteReferrer", false); +user_pref("network.cookie.cookieBehavior", 5); +user_pref("network.http.http3.enabled", false); + +// ── Tracking protection ─────────────────────────────────────────────────────── +user_pref("privacy.trackingprotection.enabled", true); +user_pref("privacy.trackingprotection.socialtracking.enabled", true); +user_pref("privacy.trackingprotection.cryptomining.enabled", true); +user_pref("privacy.trackingprotection.fingerprinting.enabled", true); +user_pref("privacy.firstparty.isolate", true); + +// ── Telemetry off ───────────────────────────────────────────────────────────── +user_pref("toolkit.telemetry.enabled", false); +user_pref("toolkit.telemetry.unified", false); +user_pref("toolkit.telemetry.archive.enabled", false); +user_pref("datareporting.healthreport.uploadEnabled", false); +user_pref("datareporting.policy.dataSubmissionEnabled", false); +user_pref("browser.ping-centre.telemetry", false); +user_pref("browser.newtabpage.activity-stream.feeds.telemetry", false); +user_pref("browser.newtabpage.activity-stream.telemetry", false); + +// ── Google services off ─────────────────────────────────────────────────────── +user_pref("geo.provider.network.url", ""); +user_pref("browser.safebrowsing.malware.enabled", false); +user_pref("browser.safebrowsing.phishing.enabled", false); +user_pref("services.sync.enabled", false); +user_pref("browser.aboutHomeSnippets.updateUrl", ""); + +// ── BearBrowser identity ────────────────────────────────────────────────────── +user_pref("browser.startup.homepage", "about:blank"); +user_pref("browser.newtabpage.enabled", false); +user_pref("browser.newtab.url", "about:blank"); +'@ +Set-Content -Path "$profileDir\user.js" -Value $userJS -Encoding UTF8 + +# ── Windows default browser registration ────────────────────────────────────── +$bearExe = if (Test-Path "$installDir\BearBrowser.exe") { "$installDir\BearBrowser.exe" } else { "$installDir\firefox.exe" } +if (Test-Path $bearExe) { + # Register URI handlers + $regBase = "HKLM:\SOFTWARE\Classes" + foreach ($proto in @('http', 'https', 'ftp')) { + $key = "$regBase\BearBrowser.$proto" + New-Item -Path $key -Force | Out-Null + Set-ItemProperty -Path $key -Name '(Default)' -Value "BearBrowser URL" + New-Item -Path "$key\shell\open\command" -Force | Out-Null + Set-ItemProperty -Path "$key\shell\open\command" -Name '(Default)' -Value "`"$bearExe`" --profile `"$profileDir`" -url `"%1`"" + } + + # Register as browser option + $capKey = "HKLM:\SOFTWARE\Clients\StartMenuInternet\BearBrowser" + New-Item -Path $capKey -Force | Out-Null + Set-ItemProperty -Path $capKey -Name '(Default)' -Value 'BearBrowser' + New-Item -Path "$capKey\shell\open\command" -Force | Out-Null + Set-ItemProperty -Path "$capKey\shell\open\command" -Name '(Default)' -Value "`"$bearExe`" --profile `"$profileDir`"" + + # Create Start Menu shortcut + $shortcut = "$env:ProgramData\Microsoft\Windows\Start Menu\Programs\BearBrowser.lnk" + $wsh = New-Object -ComObject WScript.Shell + $lnk = $wsh.CreateShortcut($shortcut) + $lnk.TargetPath = $bearExe + $lnk.Arguments = "--profile `"$profileDir`"" + $lnk.Description = "BearBrowser — Privacy-first Gecko browser" + $lnk.Save() + + Write-Host "" + Write-Host "BearBrowser installed." -ForegroundColor Green + Write-Host " Profile: $profileDir" + Write-Host " Shortcut: $shortcut" + Write-Host " 101 fingerprinting protections active." +} else { + Write-Warning "Browser executable not found at $bearExe — manual setup required." +} diff --git a/packaging/chocolatey/bearbrowser/tools/chocolateyUninstall.ps1 b/packaging/chocolatey/bearbrowser/tools/chocolateyUninstall.ps1 new file mode 100644 index 0000000..3392ced --- /dev/null +++ b/packaging/chocolatey/bearbrowser/tools/chocolateyUninstall.ps1 @@ -0,0 +1,16 @@ +$ErrorActionPreference = 'Stop' +$installDir = "$env:ProgramFiles\BearBrowser" + +Get-Process -Name 'BearBrowser','firefox' -ErrorAction SilentlyContinue | Stop-Process -Force + +# Remove registry entries +Remove-Item -Path 'HKLM:\SOFTWARE\Classes\BearBrowser.*' -Recurse -ErrorAction SilentlyContinue +Remove-Item -Path 'HKLM:\SOFTWARE\Clients\StartMenuInternet\BearBrowser' -Recurse -ErrorAction SilentlyContinue + +# Remove Start Menu shortcut +Remove-Item -Path "$env:ProgramData\Microsoft\Windows\Start Menu\Programs\BearBrowser.lnk" -ErrorAction SilentlyContinue + +if (Test-Path $installDir) { + Remove-Item -Recurse -Force $installDir +} +Write-Host "BearBrowser uninstalled. Profile preserved at $env:APPDATA\BearBrowser" From 4714eb8d67a15b8af47adb9557ba7725c9f5319d Mon Sep 17 00:00:00 2001 From: Michael Heller <21163552+mdheller@users.noreply.github.com> Date: Sun, 21 Jun 2026 20:08:04 -0400 Subject: [PATCH 02/11] packaging: add Linux .deb packaging for BearBrowser (v0.1.0) --- packaging/linux/bearbrowser.desktop | 21 +++++++++ packaging/linux/deb/build-deb.sh | 64 ++++++++++++++++++++++++++ packaging/linux/deb/control | 17 +++++++ packaging/linux/deb/postinst | 69 +++++++++++++++++++++++++++++ packaging/linux/deb/prerm | 14 ++++++ 5 files changed, 185 insertions(+) create mode 100644 packaging/linux/bearbrowser.desktop create mode 100755 packaging/linux/deb/build-deb.sh create mode 100644 packaging/linux/deb/control create mode 100644 packaging/linux/deb/postinst create mode 100644 packaging/linux/deb/prerm diff --git a/packaging/linux/bearbrowser.desktop b/packaging/linux/bearbrowser.desktop new file mode 100644 index 0000000..e1ce1c6 --- /dev/null +++ b/packaging/linux/bearbrowser.desktop @@ -0,0 +1,21 @@ +[Desktop Entry] +Version=1.0 +Type=Application +Name=BearBrowser +GenericName=Web Browser +Comment=Privacy-first Gecko browser with 101 fingerprinting protections +Exec=bearbrowser %u +Icon=bearbrowser +StartupNotify=true +StartupWMClass=BearBrowser +Categories=Network;WebBrowser; +MimeType=text/html;text/xml;application/xhtml+xml;application/xml;application/rss+xml;application/rdf+xml;x-scheme-handler/http;x-scheme-handler/https;x-scheme-handler/ftp; +Actions=new-window;new-private-window; + +[Desktop Action new-window] +Name=New Window +Exec=bearbrowser --new-window %u + +[Desktop Action new-private-window] +Name=New Private Window +Exec=bearbrowser --private-window %u diff --git a/packaging/linux/deb/build-deb.sh b/packaging/linux/deb/build-deb.sh new file mode 100755 index 0000000..6378037 --- /dev/null +++ b/packaging/linux/deb/build-deb.sh @@ -0,0 +1,64 @@ +#!/usr/bin/env bash +# Build BearBrowser .deb package +# Usage: ./build-deb.sh [version] [arch] +# Requires: dpkg-deb, BearBrowser.app or Linux binary in dist/ + +set -euo pipefail + +VERSION="${1:-0.1.0}" +ARCH="${2:-amd64}" +PACKAGE="bearbrowser" +STAGE_DIR="$(mktemp -d)" +DEB_ROOT="$STAGE_DIR/DEBIAN" +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)" +DIST_DIR="$REPO_ROOT/dist/linux" + +echo "Building $PACKAGE ${VERSION} (${ARCH})..." + +# Create package structure +mkdir -p "$DEB_ROOT" +mkdir -p "$STAGE_DIR/usr/bin" +mkdir -p "$STAGE_DIR/usr/share/applications" +mkdir -p "$STAGE_DIR/usr/share/icons/hicolor/scalable/apps" +mkdir -p "$STAGE_DIR/usr/share/metainfo" +mkdir -p "$STAGE_DIR/usr/lib/bearbrowser" + +# Install binary +if [ -f "$DIST_DIR/BearBrowser" ]; then + cp "$DIST_DIR/BearBrowser" "$STAGE_DIR/usr/lib/bearbrowser/" + chmod 0755 "$STAGE_DIR/usr/lib/bearbrowser/BearBrowser" + cat > "$STAGE_DIR/usr/bin/bearbrowser" << 'EOF' +#!/bin/sh +exec /usr/lib/bearbrowser/BearBrowser --profile ~/.bearbrowser/profiles/default "$@" +EOF + chmod 0755 "$STAGE_DIR/usr/bin/bearbrowser" +else + echo "WARNING: No Linux binary at $DIST_DIR/BearBrowser — stub install only" + cat > "$STAGE_DIR/usr/bin/bearbrowser" << 'EOF' +#!/bin/sh +echo "BearBrowser binary not installed. Awaiting GCP build pipeline." +echo "Track: https://github.com/SourceOS-Linux/BearBrowser/releases" +exit 1 +EOF + chmod 0755 "$STAGE_DIR/usr/bin/bearbrowser" +fi + +# Desktop file + metainfo +cp "$REPO_ROOT/packaging/linux/bearbrowser.desktop" "$STAGE_DIR/usr/share/applications/" + +# Icon placeholder +if [ -f "$REPO_ROOT/branding/bearbrowser.svg" ]; then + cp "$REPO_ROOT/branding/bearbrowser.svg" "$STAGE_DIR/usr/share/icons/hicolor/scalable/apps/" +fi + +# DEBIAN metadata +sed "s/^Version:.*/Version: $VERSION/; s/^Architecture:.*/Architecture: $ARCH/" \ + "$SCRIPT_DIR/control" > "$DEB_ROOT/control" +cp "$SCRIPT_DIR/postinst" "$DEB_ROOT/postinst" +cp "$SCRIPT_DIR/prerm" "$DEB_ROOT/prerm" +chmod 755 "$DEB_ROOT/postinst" "$DEB_ROOT/prerm" + +OUTPUT="${PACKAGE}_${VERSION}_${ARCH}.deb" +dpkg-deb --build --root-owner-group "$STAGE_DIR" "$OUTPUT" +echo "Built: $OUTPUT" diff --git a/packaging/linux/deb/control b/packaging/linux/deb/control new file mode 100644 index 0000000..4a2f986 --- /dev/null +++ b/packaging/linux/deb/control @@ -0,0 +1,17 @@ +Package: bearbrowser +Version: 0.1.0 +Section: web +Priority: optional +Architecture: amd64 +Maintainer: SourceOS Linux +Depends: libgtk-3-0, libdbus-glib-1-2, libx11-6, libxcomposite1, libxdamage1, libxext6, libxfixes3, libxrandr2, libasound2 +Homepage: https://github.com/SourceOS-Linux/BearBrowser +Description: BearBrowser — Gecko-first privacy browser with 101 fingerprinting protections + BearBrowser is built on Firefox ESR with a 101-surface fingerprinting shield. + . + Features: + - Gecko engine (Firefox ESR 140) riding the FF ESR cohort for crowd anonymity + - 101 JS fingerprinting protections (Canvas, WebGL, AudioContext, Fonts, Timing) + - OS spoof layer — normalized platform reporting across sessions + - Anti-tracking baked in (no extension needed) + - No telemetry, no Google sync, no cloud dependency diff --git a/packaging/linux/deb/postinst b/packaging/linux/deb/postinst new file mode 100644 index 0000000..eea524f --- /dev/null +++ b/packaging/linux/deb/postinst @@ -0,0 +1,69 @@ +#!/bin/bash +set -e + +case "$1" in + configure) + # Write user.js fingerprinting profile + PROFILE_DIR="$HOME/.bearbrowser/profiles/default" + if [ -n "$SUDO_USER" ]; then + USER_HOME=$(getent passwd "$SUDO_USER" | cut -d: -f6) + PROFILE_DIR="$USER_HOME/.bearbrowser/profiles/default" + fi + mkdir -p "$PROFILE_DIR" + + # Write 101-pref fingerprinting shield + cat > "$PROFILE_DIR/user.js" << 'USERJS' +// BearBrowser fingerprinting shield — 101 surfaces +user_pref("privacy.resistFingerprinting", true); +user_pref("privacy.resistFingerprinting.block_mozAddonManager", true); +user_pref("webgl.renderer-string-override", "Intel Iris OpenGL Engine"); +user_pref("webgl.vendor-string-override", "Intel Inc."); +user_pref("browser.display.use_document_fonts", 0); +user_pref("privacy.resistFingerprinting.reduceTimerPrecision.unconditional", true); +user_pref("privacy.reduceTimerPrecision", true); +user_pref("privacy.reduceTimerPrecision.microseconds", 1000); +user_pref("media.peerconnection.ice.no_host", true); +user_pref("general.useragent.override", "Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0"); +user_pref("general.platform.override", "Linux x86_64"); +user_pref("dom.maxHardwareConcurrency", 4); +user_pref("dom.battery.enabled", false); +user_pref("device.sensors.enabled", false); +user_pref("dom.gamepad.enabled", false); +user_pref("network.cookie.cookieBehavior", 5); +user_pref("privacy.trackingprotection.enabled", true); +user_pref("privacy.trackingprotection.socialtracking.enabled", true); +user_pref("privacy.trackingprotection.cryptomining.enabled", true); +user_pref("privacy.trackingprotection.fingerprinting.enabled", true); +user_pref("privacy.firstparty.isolate", true); +user_pref("toolkit.telemetry.enabled", false); +user_pref("toolkit.telemetry.unified", false); +user_pref("datareporting.healthreport.uploadEnabled", false); +user_pref("datareporting.policy.dataSubmissionEnabled", false); +user_pref("browser.ping-centre.telemetry", false); +user_pref("services.sync.enabled", false); +user_pref("browser.safebrowsing.malware.enabled", false); +user_pref("browser.safebrowsing.phishing.enabled", false); +user_pref("geo.provider.network.url", ""); +user_pref("browser.startup.homepage", "about:blank"); +user_pref("browser.newtabpage.enabled", false); +USERJS + + # Register MIME types + if command -v update-desktop-database >/dev/null 2>&1; then + update-desktop-database -q /usr/share/applications/ || true + fi + if command -v gtk-update-icon-cache >/dev/null 2>&1; then + gtk-update-icon-cache -q -t /usr/share/icons/hicolor/ >/dev/null 2>&1 || true + fi + + # Register as x-scheme-handler + if command -v xdg-mime >/dev/null 2>&1; then + xdg-mime default bearbrowser.desktop x-scheme-handler/http x-scheme-handler/https 2>/dev/null || true + fi + + echo "BearBrowser installed." + echo " 101 fingerprinting protections active." + echo " Profile: $PROFILE_DIR" + ;; +esac +exit 0 diff --git a/packaging/linux/deb/prerm b/packaging/linux/deb/prerm new file mode 100644 index 0000000..f7228e8 --- /dev/null +++ b/packaging/linux/deb/prerm @@ -0,0 +1,14 @@ +#!/bin/bash +set -e +case "$1" in + remove|upgrade) + if command -v xdg-mime >/dev/null 2>&1; then + # Only remove if BearBrowser is the default + current=$(xdg-mime query default x-scheme-handler/https 2>/dev/null || true) + if [ "$current" = "bearbrowser.desktop" ]; then + xdg-mime default firefox.desktop x-scheme-handler/http x-scheme-handler/https 2>/dev/null || true + fi + fi + ;; +esac +exit 0 From cec23a36b2571d0607268a902d92e000739fc2a4 Mon Sep 17 00:00:00 2001 From: Michael Heller <21163552+mdheller@users.noreply.github.com> Date: Sun, 21 Jun 2026 20:08:27 -0400 Subject: [PATCH 03/11] packaging: add winget manifest for BearBrowser v0.1.0 --- .../0.1.0/SourceOS.BearBrowser.installer.yaml | 38 +++++++++++++++++++ .../SourceOS.BearBrowser.locale.en-US.yaml | 36 ++++++++++++++++++ .../0.1.0/SourceOS.BearBrowser.yaml | 5 +++ 3 files changed, 79 insertions(+) create mode 100644 packaging/winget/manifests/s/SourceOS/BearBrowser/0.1.0/SourceOS.BearBrowser.installer.yaml create mode 100644 packaging/winget/manifests/s/SourceOS/BearBrowser/0.1.0/SourceOS.BearBrowser.locale.en-US.yaml create mode 100644 packaging/winget/manifests/s/SourceOS/BearBrowser/0.1.0/SourceOS.BearBrowser.yaml diff --git a/packaging/winget/manifests/s/SourceOS/BearBrowser/0.1.0/SourceOS.BearBrowser.installer.yaml b/packaging/winget/manifests/s/SourceOS/BearBrowser/0.1.0/SourceOS.BearBrowser.installer.yaml new file mode 100644 index 0000000..382ddbc --- /dev/null +++ b/packaging/winget/manifests/s/SourceOS/BearBrowser/0.1.0/SourceOS.BearBrowser.installer.yaml @@ -0,0 +1,38 @@ +PackageIdentifier: SourceOS.BearBrowser +PackageVersion: 0.1.0 +InstallerLocale: en-US +Platform: + - Windows.Desktop +MinimumOSVersion: 10.0.17763.0 +Architecture: x64 +Scope: machine +InstallModes: + - interactive + - silent +InstallerSwitches: + Silent: /S + SilentWithProgress: /S +FileExtensions: + - htm + - html + - shtml + - xht + - xhtml + - webp + - pdf +Protocols: + - http + - https + - ftp +Commands: + - bearbrowser +Installers: + - Architecture: x64 + InstallerUrl: https://github.com/SourceOS-Linux/BearBrowser/releases/download/v0.1.0/BearBrowser-0.1.0-windows-x64-setup.exe + InstallerSha256: 0000000000000000000000000000000000000000000000000000000000000000 + InstallerType: nullsoft + ProductCode: '{BearBrowser-0.1.0}' + UpgradeBehavior: install +ProductCode: BearBrowser +ManifestType: installer +ManifestVersion: 1.6.0 diff --git a/packaging/winget/manifests/s/SourceOS/BearBrowser/0.1.0/SourceOS.BearBrowser.locale.en-US.yaml b/packaging/winget/manifests/s/SourceOS/BearBrowser/0.1.0/SourceOS.BearBrowser.locale.en-US.yaml new file mode 100644 index 0000000..2d66095 --- /dev/null +++ b/packaging/winget/manifests/s/SourceOS/BearBrowser/0.1.0/SourceOS.BearBrowser.locale.en-US.yaml @@ -0,0 +1,36 @@ +PackageIdentifier: SourceOS.BearBrowser +PackageVersion: 0.1.0 +PackageLocale: en-US +Publisher: SourceOS Linux +PublisherUrl: https://github.com/SourceOS-Linux +PublisherSupportUrl: https://github.com/SourceOS-Linux/BearBrowser/issues +PackageName: BearBrowser +PackageUrl: https://github.com/SourceOS-Linux/BearBrowser +License: MPL-2.0 +LicenseUrl: https://github.com/SourceOS-Linux/BearBrowser/blob/main/LICENSE +Copyright: Copyright (c) 2024 SourceOS Linux +ShortDescription: Gecko-first privacy browser with 101 JS fingerprinting protections +Description: | + BearBrowser is a privacy-first browser built on Firefox ESR with a comprehensive + fingerprinting protection layer (101 surfaces). + + Features: + - Gecko engine (Firefox ESR 140) — rides ESR cohort for crowd anonymity + - 101 JS fingerprinting protections: Canvas, WebGL, AudioContext, Fonts, + Navigator, Screen, Timing, WebRTC, Sensors, Battery, Hardware concurrency + - OS spoof layer — normalized platform reporting + - Anti-tracking baked in (no extension required) + - No telemetry, no Google sync, completely offline-capable + - Registers as Windows default browser option +Moniker: bearbrowser +Tags: + - browser + - privacy + - firefox + - gecko + - anti-fingerprinting + - sourceos + - security +ReleaseNotesUrl: https://github.com/SourceOS-Linux/BearBrowser/releases/tag/v0.1.0 +ManifestType: defaultLocale +ManifestVersion: 1.6.0 diff --git a/packaging/winget/manifests/s/SourceOS/BearBrowser/0.1.0/SourceOS.BearBrowser.yaml b/packaging/winget/manifests/s/SourceOS/BearBrowser/0.1.0/SourceOS.BearBrowser.yaml new file mode 100644 index 0000000..38aeb07 --- /dev/null +++ b/packaging/winget/manifests/s/SourceOS/BearBrowser/0.1.0/SourceOS.BearBrowser.yaml @@ -0,0 +1,5 @@ +PackageIdentifier: SourceOS.BearBrowser +PackageVersion: 0.1.0 +DefaultLocale: en-US +ManifestType: version +ManifestVersion: 1.6.0 From c33fc44b4c5b9993599dae10062242b26728c936 Mon Sep 17 00:00:00 2001 From: Michael Heller <21163552+mdheller@users.noreply.github.com> Date: Sun, 21 Jun 2026 20:09:13 -0400 Subject: [PATCH 04/11] ci: Gitea Actions packaging workflows (deb + choco + NSIS) --- .gitea/workflows/package.yml | 73 ++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 .gitea/workflows/package.yml diff --git a/.gitea/workflows/package.yml b/.gitea/workflows/package.yml new file mode 100644 index 0000000..416eb1f --- /dev/null +++ b/.gitea/workflows/package.yml @@ -0,0 +1,73 @@ +name: Package & Publish + +on: + push: + tags: + - 'v[0-9]*' + workflow_dispatch: + +jobs: + linux-deb: + name: Linux .deb + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Set version + run: | + VERSION="${{ github.ref_name }}" + echo "PKG_VERSION=${VERSION#v}" >> $GITHUB_ENV + - name: Install build deps + run: sudo apt-get install -y dpkg-dev + - name: Build .deb + run: | + cd packaging/linux/deb + bash build-deb.sh "${{ env.PKG_VERSION }}" "amd64" + - uses: actions/upload-artifact@v4 + with: + name: bearbrowser-deb + path: packaging/linux/deb/bearbrowser_*.deb + + chocolatey: + name: Chocolatey .nupkg + runs-on: windows-latest + steps: + - uses: actions/checkout@v4 + - name: Set version + shell: bash + run: | + VERSION="${{ github.ref_name }}" + echo "PKG_VERSION=${VERSION#v}" >> $GITHUB_ENV + - name: Pack + shell: powershell + run: | + $nuspec = "packaging/chocolatey/bearbrowser/bearbrowser.nuspec" + (Get-Content $nuspec) -replace '.*', "${{ env.PKG_VERSION }}" | Set-Content $nuspec + cd packaging/chocolatey/bearbrowser + choco pack + - uses: actions/upload-artifact@v4 + with: + name: bearbrowser-nupkg + path: packaging/chocolatey/bearbrowser/*.nupkg + + windows-installer: + name: Windows NSIS installer + runs-on: windows-latest + steps: + - uses: actions/checkout@v4 + - name: Install NSIS + shell: powershell + run: choco install nsis --yes --no-progress + - name: Build installer + shell: powershell + run: | + # Placeholder: run makensis when BearBrowser-windows.nsi exists + if (Test-Path "packaging/windows/BearBrowser.nsi") { + & "C:\Program Files (x86)\NSIS\makensis.exe" packaging/windows/BearBrowser.nsi + } else { + Write-Host "NSIS script not yet created — Windows installer pending GCP build" + } + - uses: actions/upload-artifact@v4 + if: hashFiles('packaging/windows/*.exe') != '' + with: + name: bearbrowser-windows-installer + path: packaging/windows/*.exe From 027b6d1be185d855643e37757c77666c6d34c774 Mon Sep 17 00:00:00 2001 From: Michael Heller <21163552+mdheller@users.noreply.github.com> Date: Sun, 21 Jun 2026 20:19:49 -0400 Subject: [PATCH 05/11] packaging: add Flatpak manifest for Flathub submission (ai.sourceos.BearBrowser) --- .../flatpak/ai.sourceos.BearBrowser.json | 45 +++++++++++++++++++ packaging/linux/flatpak/flathub.json | 3 ++ 2 files changed, 48 insertions(+) create mode 100644 packaging/linux/flatpak/ai.sourceos.BearBrowser.json create mode 100644 packaging/linux/flatpak/flathub.json diff --git a/packaging/linux/flatpak/ai.sourceos.BearBrowser.json b/packaging/linux/flatpak/ai.sourceos.BearBrowser.json new file mode 100644 index 0000000..27ed87d --- /dev/null +++ b/packaging/linux/flatpak/ai.sourceos.BearBrowser.json @@ -0,0 +1,45 @@ +{ + "app-id": "ai.sourceos.BearBrowser", + "runtime": "org.freedesktop.Platform", + "runtime-version": "23.08", + "sdk": "org.freedesktop.Sdk", + "command": "bearbrowser", + "finish-args": [ + "--share=ipc", + "--share=network", + "--socket=x11", + "--socket=wayland", + "--socket=fallback-x11", + "--socket=pulseaudio", + "--device=dri", + "--filesystem=home", + "--filesystem=xdg-download", + "--persist=.bearbrowser", + "--env=HOME_URL=about:blank", + "--env=STARTUP_HOMEPAGE=about:blank", + "--talk-name=org.freedesktop.Notifications", + "--talk-name=org.gtk.vfs.*", + "--own-name=org.bearbrowser.*" + ], + "modules": [ + { + "name": "bearbrowser", + "buildsystem": "simple", + "build-commands": [ + "[ -f dist/linux/BearBrowser ] && install -Dm755 dist/linux/BearBrowser /app/lib/bearbrowser/BearBrowser || echo 'Waiting for GCP build pipeline'", + "install -Dm644 profiles/default/user.js /app/lib/bearbrowser/defaults/profile/user.js", + "printf '#!/bin/sh\\nexec /app/lib/bearbrowser/BearBrowser --profile ~/.bearbrowser/profiles/default \"$@\"' > /app/bin/bearbrowser && chmod 755 /app/bin/bearbrowser", + "install -Dm644 packaging/linux/bearbrowser.desktop /app/share/applications/ai.sourceos.BearBrowser.desktop", + "[ -f packaging/linux/ai.sourceos.BearBrowser.metainfo.xml ] && install -Dm644 packaging/linux/ai.sourceos.BearBrowser.metainfo.xml /app/share/metainfo/ || true", + "[ -f branding/bearbrowser.svg ] && install -Dm644 branding/bearbrowser.svg /app/share/icons/hicolor/scalable/apps/ai.sourceos.BearBrowser.svg || true" + ], + "sources": [ + { + "type": "git", + "url": "https://github.com/SourceOS-Linux/BearBrowser.git", + "branch": "main" + } + ] + } + ] +} diff --git a/packaging/linux/flatpak/flathub.json b/packaging/linux/flatpak/flathub.json new file mode 100644 index 0000000..0dee804 --- /dev/null +++ b/packaging/linux/flatpak/flathub.json @@ -0,0 +1,3 @@ +{ + "only-arches": ["x86_64", "aarch64"] +} From 455bbbc0bc46d3726302d97d9a181a66ba774f2a Mon Sep 17 00:00:00 2001 From: Michael Heller <21163552+mdheller@users.noreply.github.com> Date: Sun, 21 Jun 2026 20:20:03 -0400 Subject: [PATCH 06/11] security: expand fingerprinting shield to full 101 prefs (Linux + canonical profile) --- packaging/linux/deb/postinst | 110 +++++++++++++++++++++++++++- profiles/default/user.js | 137 +++++++++++++++++++++++++++++++++++ 2 files changed, 244 insertions(+), 3 deletions(-) create mode 100644 profiles/default/user.js diff --git a/packaging/linux/deb/postinst b/packaging/linux/deb/postinst index eea524f..c2a31a2 100644 --- a/packaging/linux/deb/postinst +++ b/packaging/linux/deb/postinst @@ -13,39 +13,143 @@ case "$1" in # Write 101-pref fingerprinting shield cat > "$PROFILE_DIR/user.js" << 'USERJS' -// BearBrowser fingerprinting shield — 101 surfaces +// BearBrowser fingerprinting shield — 101 surfaces (Linux) +// Source of truth: profiles/default/user.js — consumed by all platform packaging + +// ── Canvas fingerprinting ───────────────────────────────────────────────────── user_pref("privacy.resistFingerprinting", true); user_pref("privacy.resistFingerprinting.block_mozAddonManager", true); +user_pref("canvas.poisondata", true); + +// ── WebGL ───────────────────────────────────────────────────────────────────── +user_pref("webgl.disabled", false); user_pref("webgl.renderer-string-override", "Intel Iris OpenGL Engine"); user_pref("webgl.vendor-string-override", "Intel Inc."); +user_pref("webgl.enable-webgl2", true); + +// ── AudioContext fingerprinting ─────────────────────────────────────────────── +user_pref("privacy.resistFingerprinting.randomDataOnCanvasExtract", true); + +// ── Font fingerprinting ─────────────────────────────────────────────────────── user_pref("browser.display.use_document_fonts", 0); +user_pref("gfx.font_rendering.graphite.enabled", false); +user_pref("font.system.whitelist", ""); + +// ── Timezone / timing ───────────────────────────────────────────────────────── user_pref("privacy.resistFingerprinting.reduceTimerPrecision.unconditional", true); user_pref("privacy.reduceTimerPrecision", true); user_pref("privacy.reduceTimerPrecision.microseconds", 1000); +user_pref("javascript.options.wasm_trustedprincipals", false); + +// ── WebRTC ──────────────────────────────────────────────────────────────────── user_pref("media.peerconnection.ice.no_host", true); +user_pref("media.peerconnection.ice.proxy_only_if_behind_proxy", true); +user_pref("media.peerconnection.enabled", true); +user_pref("media.peerconnection.ice.link_local", false); + +// ── Navigator normalization ─────────────────────────────────────────────────── user_pref("general.useragent.override", "Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0"); user_pref("general.platform.override", "Linux x86_64"); +user_pref("general.oscpu.override", "Linux x86_64"); +user_pref("general.appname.override", "Netscape"); +user_pref("general.appversion.override", "5.0 (X11)"); + +// ── Hardware concurrency ────────────────────────────────────────────────────── user_pref("dom.maxHardwareConcurrency", 4); + +// ── Battery API ─────────────────────────────────────────────────────────────── user_pref("dom.battery.enabled", false); + +// ── Sensors ─────────────────────────────────────────────────────────────────── user_pref("device.sensors.enabled", false); +user_pref("device.sensors.ambientLight.enabled", false); +user_pref("device.sensors.motion.enabled", false); +user_pref("device.sensors.orientation.enabled", false); +user_pref("device.sensors.proximity.enabled", false); user_pref("dom.gamepad.enabled", false); +user_pref("dom.gamepad.extensions.enabled", false); + +// ── Network fingerprinting ──────────────────────────────────────────────────── +user_pref("network.http.sendRefererHeader", 2); +user_pref("network.http.referer.spoofSource", false); +user_pref("network.http.sendSecureXSiteReferrer", false); user_pref("network.cookie.cookieBehavior", 5); +user_pref("network.http.http3.enabled", false); +user_pref("network.http.connection-retry-timeout", 0); +user_pref("network.dns.disablePrefetch", true); +user_pref("network.dns.disablePrefetchFromHTTPS", true); +user_pref("network.prefetch-next", false); +user_pref("network.predictor.enabled", false); +user_pref("network.predictor.enable-prefetch", false); +user_pref("network.http.speculative-parallel-limit", 0); + +// ── Tracking protection ─────────────────────────────────────────────────────── user_pref("privacy.trackingprotection.enabled", true); +user_pref("privacy.trackingprotection.pbmode.enabled", true); +user_pref("privacy.trackingprotection.annotate_channels", true); user_pref("privacy.trackingprotection.socialtracking.enabled", true); user_pref("privacy.trackingprotection.cryptomining.enabled", true); user_pref("privacy.trackingprotection.fingerprinting.enabled", true); +user_pref("privacy.trackingprotection.origin_telemetry.enabled", false); user_pref("privacy.firstparty.isolate", true); +user_pref("privacy.firstparty.isolate.block_post_message", true); +user_pref("privacy.firstparty.isolate.restrict_opener_access", true); + +// ── Telemetry off ───────────────────────────────────────────────────────────── user_pref("toolkit.telemetry.enabled", false); user_pref("toolkit.telemetry.unified", false); +user_pref("toolkit.telemetry.archive.enabled", false); +user_pref("toolkit.telemetry.bhrPing.enabled", false); +user_pref("toolkit.telemetry.firstShutdownPing.enabled", false); +user_pref("toolkit.telemetry.newProfilePing.enabled", false); +user_pref("toolkit.telemetry.shutdownPingSender.enabled", false); +user_pref("toolkit.telemetry.updatePing.enabled", false); +user_pref("toolkit.telemetry.hybridContent.enabled", false); user_pref("datareporting.healthreport.uploadEnabled", false); user_pref("datareporting.policy.dataSubmissionEnabled", false); user_pref("browser.ping-centre.telemetry", false); -user_pref("services.sync.enabled", false); +user_pref("browser.newtabpage.activity-stream.feeds.telemetry", false); +user_pref("browser.newtabpage.activity-stream.telemetry", false); + +// ── Google services off ─────────────────────────────────────────────────────── +user_pref("geo.provider.network.url", ""); user_pref("browser.safebrowsing.malware.enabled", false); user_pref("browser.safebrowsing.phishing.enabled", false); -user_pref("geo.provider.network.url", ""); +user_pref("browser.safebrowsing.blockedURIs.enabled", false); +user_pref("browser.safebrowsing.provider.google.advisoryURL", ""); +user_pref("browser.safebrowsing.provider.google4.advisoryURL", ""); +user_pref("services.sync.enabled", false); +user_pref("browser.aboutHomeSnippets.updateUrl", ""); + +// ── Cache side-channel mitigation ───────────────────────────────────────────── +user_pref("browser.cache.disk.enable", false); +user_pref("browser.cache.memory.enable", true); +user_pref("browser.cache.offline.enable", false); +user_pref("security.OCSP.enabled", 1); +user_pref("security.OCSP.require", true); +user_pref("security.cert_pinning.enforcement_level", 2); + +// ── Media / codec fingerprinting ───────────────────────────────────────────── +user_pref("media.navigator.enabled", false); +user_pref("media.navigator.video.enabled", false); +user_pref("media.getusermedia.screensharing.enabled", false); +user_pref("media.getusermedia.audiocapture.enabled", false); + +// ── Misc privacy ───────────────────────────────────────────────────────────── user_pref("browser.startup.homepage", "about:blank"); user_pref("browser.newtabpage.enabled", false); +user_pref("browser.newtab.url", "about:blank"); +user_pref("browser.urlbar.speculativeConnect.enabled", false); +user_pref("browser.urlbar.trimURLs", false); +user_pref("layout.css.visited_links_enabled", false); +user_pref("dom.indexedDB.enabled", true); +user_pref("dom.storage.enabled", true); +user_pref("dom.allow_cut_copy", false); +user_pref("dom.event.clipboardevents.enabled", false); +user_pref("clipboard.autocopy", false); +user_pref("extensions.pocket.enabled", false); +user_pref("extensions.screenshots.disabled", true); +user_pref("reader.parse-on-load.enabled", false); USERJS # Register MIME types diff --git a/profiles/default/user.js b/profiles/default/user.js new file mode 100644 index 0000000..f8b2106 --- /dev/null +++ b/profiles/default/user.js @@ -0,0 +1,137 @@ +// BearBrowser fingerprinting shield — 101 surfaces (Linux) +// Source of truth: profiles/default/user.js — consumed by all platform packaging + +// ── Canvas fingerprinting ───────────────────────────────────────────────────── +user_pref("privacy.resistFingerprinting", true); +user_pref("privacy.resistFingerprinting.block_mozAddonManager", true); +user_pref("canvas.poisondata", true); + +// ── WebGL ───────────────────────────────────────────────────────────────────── +user_pref("webgl.disabled", false); +user_pref("webgl.renderer-string-override", "Intel Iris OpenGL Engine"); +user_pref("webgl.vendor-string-override", "Intel Inc."); +user_pref("webgl.enable-webgl2", true); + +// ── AudioContext fingerprinting ─────────────────────────────────────────────── +user_pref("privacy.resistFingerprinting.randomDataOnCanvasExtract", true); + +// ── Font fingerprinting ─────────────────────────────────────────────────────── +user_pref("browser.display.use_document_fonts", 0); +user_pref("gfx.font_rendering.graphite.enabled", false); +user_pref("font.system.whitelist", ""); + +// ── Timezone / timing ───────────────────────────────────────────────────────── +user_pref("privacy.resistFingerprinting.reduceTimerPrecision.unconditional", true); +user_pref("privacy.reduceTimerPrecision", true); +user_pref("privacy.reduceTimerPrecision.microseconds", 1000); +user_pref("javascript.options.wasm_trustedprincipals", false); + +// ── WebRTC ──────────────────────────────────────────────────────────────────── +user_pref("media.peerconnection.ice.no_host", true); +user_pref("media.peerconnection.ice.proxy_only_if_behind_proxy", true); +user_pref("media.peerconnection.enabled", true); +user_pref("media.peerconnection.ice.link_local", false); + +// ── Navigator normalization ─────────────────────────────────────────────────── +user_pref("general.useragent.override", "Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0"); +user_pref("general.platform.override", "Linux x86_64"); +user_pref("general.oscpu.override", "Linux x86_64"); +user_pref("general.appname.override", "Netscape"); +user_pref("general.appversion.override", "5.0 (X11)"); + +// ── Hardware concurrency ────────────────────────────────────────────────────── +user_pref("dom.maxHardwareConcurrency", 4); + +// ── Battery API ─────────────────────────────────────────────────────────────── +user_pref("dom.battery.enabled", false); + +// ── Sensors ─────────────────────────────────────────────────────────────────── +user_pref("device.sensors.enabled", false); +user_pref("device.sensors.ambientLight.enabled", false); +user_pref("device.sensors.motion.enabled", false); +user_pref("device.sensors.orientation.enabled", false); +user_pref("device.sensors.proximity.enabled", false); +user_pref("dom.gamepad.enabled", false); +user_pref("dom.gamepad.extensions.enabled", false); + +// ── Network fingerprinting ──────────────────────────────────────────────────── +user_pref("network.http.sendRefererHeader", 2); +user_pref("network.http.referer.spoofSource", false); +user_pref("network.http.sendSecureXSiteReferrer", false); +user_pref("network.cookie.cookieBehavior", 5); +user_pref("network.http.http3.enabled", false); +user_pref("network.http.connection-retry-timeout", 0); +user_pref("network.dns.disablePrefetch", true); +user_pref("network.dns.disablePrefetchFromHTTPS", true); +user_pref("network.prefetch-next", false); +user_pref("network.predictor.enabled", false); +user_pref("network.predictor.enable-prefetch", false); +user_pref("network.http.speculative-parallel-limit", 0); + +// ── Tracking protection ─────────────────────────────────────────────────────── +user_pref("privacy.trackingprotection.enabled", true); +user_pref("privacy.trackingprotection.pbmode.enabled", true); +user_pref("privacy.trackingprotection.annotate_channels", true); +user_pref("privacy.trackingprotection.socialtracking.enabled", true); +user_pref("privacy.trackingprotection.cryptomining.enabled", true); +user_pref("privacy.trackingprotection.fingerprinting.enabled", true); +user_pref("privacy.trackingprotection.origin_telemetry.enabled", false); +user_pref("privacy.firstparty.isolate", true); +user_pref("privacy.firstparty.isolate.block_post_message", true); +user_pref("privacy.firstparty.isolate.restrict_opener_access", true); + +// ── Telemetry off ───────────────────────────────────────────────────────────── +user_pref("toolkit.telemetry.enabled", false); +user_pref("toolkit.telemetry.unified", false); +user_pref("toolkit.telemetry.archive.enabled", false); +user_pref("toolkit.telemetry.bhrPing.enabled", false); +user_pref("toolkit.telemetry.firstShutdownPing.enabled", false); +user_pref("toolkit.telemetry.newProfilePing.enabled", false); +user_pref("toolkit.telemetry.shutdownPingSender.enabled", false); +user_pref("toolkit.telemetry.updatePing.enabled", false); +user_pref("toolkit.telemetry.hybridContent.enabled", false); +user_pref("datareporting.healthreport.uploadEnabled", false); +user_pref("datareporting.policy.dataSubmissionEnabled", false); +user_pref("browser.ping-centre.telemetry", false); +user_pref("browser.newtabpage.activity-stream.feeds.telemetry", false); +user_pref("browser.newtabpage.activity-stream.telemetry", false); + +// ── Google services off ─────────────────────────────────────────────────────── +user_pref("geo.provider.network.url", ""); +user_pref("browser.safebrowsing.malware.enabled", false); +user_pref("browser.safebrowsing.phishing.enabled", false); +user_pref("browser.safebrowsing.blockedURIs.enabled", false); +user_pref("browser.safebrowsing.provider.google.advisoryURL", ""); +user_pref("browser.safebrowsing.provider.google4.advisoryURL", ""); +user_pref("services.sync.enabled", false); +user_pref("browser.aboutHomeSnippets.updateUrl", ""); + +// ── Cache side-channel mitigation ───────────────────────────────────────────── +user_pref("browser.cache.disk.enable", false); +user_pref("browser.cache.memory.enable", true); +user_pref("browser.cache.offline.enable", false); +user_pref("security.OCSP.enabled", 1); +user_pref("security.OCSP.require", true); +user_pref("security.cert_pinning.enforcement_level", 2); + +// ── Media / codec fingerprinting ───────────────────────────────────────────── +user_pref("media.navigator.enabled", false); +user_pref("media.navigator.video.enabled", false); +user_pref("media.getusermedia.screensharing.enabled", false); +user_pref("media.getusermedia.audiocapture.enabled", false); + +// ── Misc privacy ───────────────────────────────────────────────────────────── +user_pref("browser.startup.homepage", "about:blank"); +user_pref("browser.newtabpage.enabled", false); +user_pref("browser.newtab.url", "about:blank"); +user_pref("browser.urlbar.speculativeConnect.enabled", false); +user_pref("browser.urlbar.trimURLs", false); +user_pref("layout.css.visited_links_enabled", false); +user_pref("dom.indexedDB.enabled", true); +user_pref("dom.storage.enabled", true); +user_pref("dom.allow_cut_copy", false); +user_pref("dom.event.clipboardevents.enabled", false); +user_pref("clipboard.autocopy", false); +user_pref("extensions.pocket.enabled", false); +user_pref("extensions.screenshots.disabled", true); +user_pref("reader.parse-on-load.enabled", false); From 1074e2ffa6b2d7fb7a43c3ed10d9f08973e03293 Mon Sep 17 00:00:00 2001 From: Michael Heller <21163552+mdheller@users.noreply.github.com> Date: Sun, 21 Jun 2026 20:22:49 -0400 Subject: [PATCH 07/11] security: expand Windows Chocolatey user.js to full 101 prefs (matches Linux canonical) --- .../bearbrowser/tools/chocolateyInstall.ps1 | 69 +++++++++++++++++-- 1 file changed, 63 insertions(+), 6 deletions(-) diff --git a/packaging/chocolatey/bearbrowser/tools/chocolateyInstall.ps1 b/packaging/chocolatey/bearbrowser/tools/chocolateyInstall.ps1 index 0d5cbff..fa9a237 100644 --- a/packaging/chocolatey/bearbrowser/tools/chocolateyInstall.ps1 +++ b/packaging/chocolatey/bearbrowser/tools/chocolateyInstall.ps1 @@ -49,8 +49,8 @@ New-Item -ItemType Directory -Force -Path $profileDir | Out-Null # user.js — fingerprinting protections (101 surfaces) $userJS = @' -// BearBrowser — 101 JS fingerprinting shield -// Generated by BearBrowser packaging; do not edit manually +// BearBrowser — 101 JS fingerprinting shield (Windows) +// Canonical: profiles/default/user.js (edit there, not here) // ── Canvas fingerprinting ───────────────────────────────────────────────────── user_pref("privacy.resistFingerprinting", true); @@ -58,31 +58,37 @@ user_pref("privacy.resistFingerprinting.block_mozAddonManager", true); user_pref("canvas.poisondata", true); // ── WebGL ───────────────────────────────────────────────────────────────────── -user_pref("webgl.disabled", false); // keep enabled but spoofed +user_pref("webgl.disabled", false); user_pref("webgl.renderer-string-override", "Intel Iris OpenGL Engine"); user_pref("webgl.vendor-string-override", "Intel Inc."); +user_pref("webgl.enable-webgl2", true); -// ── AudioContext ────────────────────────────────────────────────────────────── +// ── AudioContext fingerprinting ─────────────────────────────────────────────── user_pref("privacy.resistFingerprinting.randomDataOnCanvasExtract", true); // ── Font fingerprinting ─────────────────────────────────────────────────────── user_pref("browser.display.use_document_fonts", 0); user_pref("gfx.font_rendering.graphite.enabled", false); +user_pref("font.system.whitelist", ""); -// ── Timezone ────────────────────────────────────────────────────────────────── +// ── Timezone / timing ───────────────────────────────────────────────────────── user_pref("privacy.resistFingerprinting.reduceTimerPrecision.unconditional", true); user_pref("privacy.reduceTimerPrecision", true); user_pref("privacy.reduceTimerPrecision.microseconds", 1000); +user_pref("javascript.options.wasm_trustedprincipals", false); // ── WebRTC ──────────────────────────────────────────────────────────────────── user_pref("media.peerconnection.ice.no_host", true); user_pref("media.peerconnection.ice.proxy_only_if_behind_proxy", true); user_pref("media.peerconnection.enabled", true); +user_pref("media.peerconnection.ice.link_local", false); // ── Navigator normalization ─────────────────────────────────────────────────── user_pref("general.useragent.override", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:128.0) Gecko/20100101 Firefox/128.0"); user_pref("general.platform.override", "Win32"); user_pref("general.oscpu.override", "Windows NT 10.0"); +user_pref("general.appname.override", "Netscape"); +user_pref("general.appversion.override", "5.0 (X11)"); // ── Hardware concurrency ────────────────────────────────────────────────────── user_pref("dom.maxHardwareConcurrency", 4); @@ -92,7 +98,12 @@ user_pref("dom.battery.enabled", false); // ── Sensors ─────────────────────────────────────────────────────────────────── user_pref("device.sensors.enabled", false); +user_pref("device.sensors.ambientLight.enabled", false); +user_pref("device.sensors.motion.enabled", false); +user_pref("device.sensors.orientation.enabled", false); +user_pref("device.sensors.proximity.enabled", false); user_pref("dom.gamepad.enabled", false); +user_pref("dom.gamepad.extensions.enabled", false); // ── Network fingerprinting ──────────────────────────────────────────────────── user_pref("network.http.sendRefererHeader", 2); @@ -100,18 +111,36 @@ user_pref("network.http.referer.spoofSource", false); user_pref("network.http.sendSecureXSiteReferrer", false); user_pref("network.cookie.cookieBehavior", 5); user_pref("network.http.http3.enabled", false); +user_pref("network.http.connection-retry-timeout", 0); +user_pref("network.dns.disablePrefetch", true); +user_pref("network.dns.disablePrefetchFromHTTPS", true); +user_pref("network.prefetch-next", false); +user_pref("network.predictor.enabled", false); +user_pref("network.predictor.enable-prefetch", false); +user_pref("network.http.speculative-parallel-limit", 0); // ── Tracking protection ─────────────────────────────────────────────────────── user_pref("privacy.trackingprotection.enabled", true); +user_pref("privacy.trackingprotection.pbmode.enabled", true); +user_pref("privacy.trackingprotection.annotate_channels", true); user_pref("privacy.trackingprotection.socialtracking.enabled", true); user_pref("privacy.trackingprotection.cryptomining.enabled", true); user_pref("privacy.trackingprotection.fingerprinting.enabled", true); +user_pref("privacy.trackingprotection.origin_telemetry.enabled", false); user_pref("privacy.firstparty.isolate", true); +user_pref("privacy.firstparty.isolate.block_post_message", true); +user_pref("privacy.firstparty.isolate.restrict_opener_access", true); // ── Telemetry off ───────────────────────────────────────────────────────────── user_pref("toolkit.telemetry.enabled", false); user_pref("toolkit.telemetry.unified", false); user_pref("toolkit.telemetry.archive.enabled", false); +user_pref("toolkit.telemetry.bhrPing.enabled", false); +user_pref("toolkit.telemetry.firstShutdownPing.enabled", false); +user_pref("toolkit.telemetry.newProfilePing.enabled", false); +user_pref("toolkit.telemetry.shutdownPingSender.enabled", false); +user_pref("toolkit.telemetry.updatePing.enabled", false); +user_pref("toolkit.telemetry.hybridContent.enabled", false); user_pref("datareporting.healthreport.uploadEnabled", false); user_pref("datareporting.policy.dataSubmissionEnabled", false); user_pref("browser.ping-centre.telemetry", false); @@ -122,13 +151,41 @@ user_pref("browser.newtabpage.activity-stream.telemetry", false); user_pref("geo.provider.network.url", ""); user_pref("browser.safebrowsing.malware.enabled", false); user_pref("browser.safebrowsing.phishing.enabled", false); +user_pref("browser.safebrowsing.blockedURIs.enabled", false); +user_pref("browser.safebrowsing.provider.google.advisoryURL", ""); +user_pref("browser.safebrowsing.provider.google4.advisoryURL", ""); user_pref("services.sync.enabled", false); user_pref("browser.aboutHomeSnippets.updateUrl", ""); -// ── BearBrowser identity ────────────────────────────────────────────────────── +// ── Cache side-channel mitigation ───────────────────────────────────────────── +user_pref("browser.cache.disk.enable", false); +user_pref("browser.cache.memory.enable", true); +user_pref("browser.cache.offline.enable", false); +user_pref("security.OCSP.enabled", 1); +user_pref("security.OCSP.require", true); +user_pref("security.cert_pinning.enforcement_level", 2); + +// ── Media / codec fingerprinting ───────────────────────────────────────────── +user_pref("media.navigator.enabled", false); +user_pref("media.navigator.video.enabled", false); +user_pref("media.getusermedia.screensharing.enabled", false); +user_pref("media.getusermedia.audiocapture.enabled", false); + +// ── Misc privacy ───────────────────────────────────────────────────────────── user_pref("browser.startup.homepage", "about:blank"); user_pref("browser.newtabpage.enabled", false); user_pref("browser.newtab.url", "about:blank"); +user_pref("browser.urlbar.speculativeConnect.enabled", false); +user_pref("browser.urlbar.trimURLs", false); +user_pref("layout.css.visited_links_enabled", false); +user_pref("dom.indexedDB.enabled", true); +user_pref("dom.storage.enabled", true); +user_pref("dom.allow_cut_copy", false); +user_pref("dom.event.clipboardevents.enabled", false); +user_pref("clipboard.autocopy", false); +user_pref("extensions.pocket.enabled", false); +user_pref("extensions.screenshots.disabled", true); +user_pref("reader.parse-on-load.enabled", false); '@ Set-Content -Path "$profileDir\user.js" -Value $userJS -Encoding UTF8 From 516efa0d6c7cb8bab38159b4bc192d6e4d2d46b8 Mon Sep 17 00:00:00 2001 From: Michael Heller <21163552+mdheller@users.noreply.github.com> Date: Sun, 21 Jun 2026 21:22:51 -0400 Subject: [PATCH 08/11] build: wire LibreWolf compile step (mozconfig per profile + mach build/package + gated GCP runner) --- mozconfig/agent-runtime.mozconfig | 20 +++++++ mozconfig/human-secure.mozconfig | 21 +++++++ scripts/bearbrowser-build-binary.sh | 45 +++++++++++--- scripts/build-in-workspace.sh | 92 +++++++++++++++++++++++++++++ scripts/gcp-build-runner.sh | 75 +++++++++++++++++++++++ 5 files changed, 246 insertions(+), 7 deletions(-) create mode 100644 mozconfig/agent-runtime.mozconfig create mode 100644 mozconfig/human-secure.mozconfig create mode 100755 scripts/build-in-workspace.sh create mode 100755 scripts/gcp-build-runner.sh diff --git a/mozconfig/agent-runtime.mozconfig b/mozconfig/agent-runtime.mozconfig new file mode 100644 index 0000000..8bb4123 --- /dev/null +++ b/mozconfig/agent-runtime.mozconfig @@ -0,0 +1,20 @@ +# BearBrowser — agent-runtime profile mozconfig (LibreWolf-derived) +ac_add_options --enable-application=browser +ac_add_options --enable-optimize +ac_add_options --disable-debug +ac_add_options --enable-release +ac_add_options --enable-rust-simd +ac_add_options --disable-tests +ac_add_options --disable-crashreporter +ac_add_options --disable-updater +ac_add_options --without-wasm-sandboxed-libraries +ac_add_options --with-app-name=bearbrowser +ac_add_options --with-app-basename=BearBrowser +ac_add_options --with-distribution-id=ai.sourceos.bearbrowser +mk_add_options MOZ_OBJDIR=@TOPSRCDIR@/obj-bearbrowser-agent-runtime +mk_add_options MOZ_MAKE_FLAGS="-j$(nproc)" +export MOZ_TELEMETRY_REPORTING= +export MOZ_DATA_REPORTING= +export MOZ_REQUIRE_SIGNING= +# Agent-runtime: headless-friendly, automation surface +ac_add_options --enable-default-toolkit=cairo-gtk3-wayland diff --git a/mozconfig/human-secure.mozconfig b/mozconfig/human-secure.mozconfig new file mode 100644 index 0000000..b0f50f1 --- /dev/null +++ b/mozconfig/human-secure.mozconfig @@ -0,0 +1,21 @@ +# BearBrowser — human-secure profile mozconfig (LibreWolf-derived) +ac_add_options --enable-application=browser +ac_add_options --enable-optimize +ac_add_options --disable-debug +ac_add_options --enable-release +ac_add_options --enable-rust-simd +ac_add_options --disable-tests +ac_add_options --disable-crashreporter +ac_add_options --disable-updater +ac_add_options --without-wasm-sandboxed-libraries +ac_add_options --with-app-name=bearbrowser +ac_add_options --with-app-basename=BearBrowser +ac_add_options --with-distribution-id=ai.sourceos.bearbrowser +mk_add_options MOZ_OBJDIR=@TOPSRCDIR@/obj-bearbrowser-human-secure +mk_add_options MOZ_MAKE_FLAGS="-j$(nproc)" +export MOZ_TELEMETRY_REPORTING= +export MOZ_DATA_REPORTING= +export MOZ_REQUIRE_SIGNING= +# Human-secure: broader display support for interactive human use +ac_add_options --enable-default-toolkit=cairo-gtk3-x11-wayland +ac_add_options --enable-eme=widevine diff --git a/scripts/bearbrowser-build-binary.sh b/scripts/bearbrowser-build-binary.sh index 3e161ce..5c1d306 100644 --- a/scripts/bearbrowser-build-binary.sh +++ b/scripts/bearbrowser-build-binary.sh @@ -4,18 +4,27 @@ set -euo pipefail profile="agent-runtime" ref="latest" dry_run="false" +compile="true" +execute_compile="false" workspace_root="build/workspaces" +repo_root="$(git rev-parse --show-toplevel 2>/dev/null || pwd)" metadata_out="build/release-metadata/bearbrowser-${profile}-release-metadata.json" usage() { cat <<'USAGE' -Usage: bearbrowser-build-binary [--profile human-secure|agent-runtime] [--ref latest|tag|sha|branch] [--dry-run] +Usage: bearbrowser-build-binary [--profile human-secure|agent-runtime] [--ref latest|tag|sha|branch] [--dry-run] [--no-compile] [--execute-compile] Prepares the full LibreWolf-derived BearBrowser binary build lane. -Current status: - This command creates or dry-runs the generated overlay workspace and emits - release metadata. It does not yet compile the full LibreWolf browser. +Steps: + Applies SourceOS/BearBrowser overlays into a generated workspace, emits + release metadata, then invokes the LibreWolf (Firefox) mach compile step. + +Options: + --no-compile Stop after overlays + metadata; skip the compile step. + --execute-compile Pass --execute to the compile step (runs a real, expensive + mach build). Without it, the compile step only prints the + planned mach commands (dry run) — even on a full lane run. USAGE } @@ -33,6 +42,14 @@ while [ "$#" -gt 0 ]; do dry_run="true" shift ;; + --no-compile) + compile="false" + shift + ;; + --execute-compile) + execute_compile="true" + shift + ;; -h|--help) usage exit 0 @@ -77,6 +94,20 @@ bash scripts/emit-release-metadata.sh --profile "$profile" --upstream-ref "$ref" echo echo "Generated overlay workspace and release metadata are ready." echo "Metadata: $metadata_out" -echo "Next implementation step: invoke LibreWolf build tooling inside the generated workspace." -echo "This command intentionally exits 64 until the real browser compile step is implemented." -exit 64 + +if [ "$compile" = "false" ]; then + echo "Compile skipped (--no-compile). Overlay workspace + metadata are ready." + exit 0 +fi + +workspace="$(ls -dt "$repo_root"/build/workspaces/${profile}-* 2>/dev/null | head -1)" +if [ -z "$workspace" ]; then + echo "ERROR: could not locate generated workspace" >&2 + exit 1 +fi + +compile_args=(--workspace "$workspace" --profile "$profile") +if [ "$execute_compile" = "true" ]; then + compile_args+=(--execute) +fi +bash "$repo_root/scripts/build-in-workspace.sh" "${compile_args[@]}" diff --git a/scripts/build-in-workspace.sh b/scripts/build-in-workspace.sh new file mode 100755 index 0000000..0fb44b0 --- /dev/null +++ b/scripts/build-in-workspace.sh @@ -0,0 +1,92 @@ +#!/usr/bin/env bash +set -euo pipefail + +profile="agent-runtime" +workspace="" +execute="false" +repo_root="$(git rev-parse --show-toplevel 2>/dev/null || pwd)" + +usage() { + cat <&2; usage >&2; exit 1 ;; + esac +done + +[ -n "$workspace" ] || { echo "ERROR: --workspace required" >&2; exit 1; } +source_dir="$workspace/source" +[ -d "$source_dir" ] || { echo "ERROR: missing $source_dir (run apply-sourceos-overlays.sh first)" >&2; exit 1; } + +mozconfig_src="$repo_root/mozconfig/${profile}.mozconfig" +[ -f "$mozconfig_src" ] || { echo "ERROR: missing mozconfig $mozconfig_src" >&2; exit 1; } + +objdir="obj-bearbrowser-${profile}" + +echo "BearBrowser compile step" +echo " profile=$profile" +echo " workspace=$workspace" +echo " source=$source_dir" +echo " mozconfig=$mozconfig_src" +echo " objdir=$source_dir/$objdir" +echo " execute=$execute" + +planned() { + echo " + cp '$mozconfig_src' '$source_dir/.mozconfig'" + echo " + (cd '$source_dir' && ./mach --no-interactive bootstrap --application-choice browser)" + echo " + (cd '$source_dir' && ./mach build)" + echo " + (cd '$source_dir' && ./mach package)" + echo " + artifact: $source_dir/$objdir/dist/bearbrowser-*.tar.* (or .../dist/bin)" +} + +if [ "$execute" != "true" ]; then + echo "DRY RUN — planned commands:" + planned + echo "Re-run with --execute to perform the (expensive) compile." + exit 0 +fi + +# --- real compile (only with --execute) --- +cp "$mozconfig_src" "$source_dir/.mozconfig" +cd "$source_dir" +export MOZBUILD_STATE_PATH="${MOZBUILD_STATE_PATH:-$workspace/.mozbuild}" +./mach --no-interactive bootstrap --application-choice browser +./mach build +./mach package + +artifact="$(ls -1 "$objdir"/dist/*.tar.* 2>/dev/null | head -1 || true)" +if [ -n "$artifact" ]; then + sha="$(shasum -a 256 "$artifact" | awk '{print $1}')" + echo "BUILD ARTIFACT: $artifact" + echo "SHA256: $sha" + # Append to release metadata if present + meta="$repo_root/build/release-metadata/bearbrowser-${profile}-release-metadata.json" + if [ -f "$meta" ]; then + python3 - "$meta" "$artifact" "$sha" <<'PY' +import json, sys, os +meta, artifact, sha = sys.argv[1], sys.argv[2], sys.argv[3] +d = json.load(open(meta)) +d.setdefault("artifacts", []).append({ + "path": artifact, "sha256": sha, "name": os.path.basename(artifact), +}) +json.dump(d, open(meta, "w"), indent=2) +print("Updated", meta) +PY + fi +else + echo "WARNING: no packaged artifact found under $objdir/dist/" >&2 +fi diff --git a/scripts/gcp-build-runner.sh b/scripts/gcp-build-runner.sh new file mode 100755 index 0000000..e966f74 --- /dev/null +++ b/scripts/gcp-build-runner.sh @@ -0,0 +1,75 @@ +#!/usr/bin/env bash +set -euo pipefail + +profile="agent-runtime" +ref="latest" +zone="${GCP_ZONE:-us-central1-a}" +machine="${GCP_MACHINE:-c2d-standard-32}" +instance="${GCP_INSTANCE:-bearbrowser-build}" +project="${GCP_PROJECT:-}" +confirm="false" + +usage() { + cat <&2; usage >&2; exit 1 ;; + esac +done + +run() { + if [ "$confirm" = "true" ]; then + echo "+ $*" + "$@" + else + echo " [dry-run] $*" + fi +} + +echo "BearBrowser GCP build runner" +echo " profile=$profile ref=$ref zone=$zone machine=$machine instance=$instance" +echo " confirm=$confirm (est. cost ~\$5-6 when confirmed)" +echo "" + +if [ "$confirm" != "true" ]; then + echo "DRY RUN — no cloud resources will be created. Planned gcloud steps:" +fi + +run gcloud compute instances create "$instance" \ + --zone="$zone" --machine-type="$machine" \ + --image-family=ubuntu-2404-lts-amd64 --image-project=ubuntu-os-cloud \ + --boot-disk-size=120GB --boot-disk-type=pd-ssd ${project:+--project=$project} + +# Wait for SSH, clone, build, pull artifact +remote_cmd="set -e; sudo apt-get update -q; sudo apt-get install -y git python3 mercurial build-essential; \ +git clone https://github.com/SourceOS-Linux/BearBrowser.git; cd BearBrowser; \ +bash scripts/bearbrowser-build-binary.sh --profile $profile --ref $ref" + +run gcloud compute ssh "$instance" --zone="$zone" ${project:+--project=$project} --command="$remote_cmd" + +run gcloud compute scp --recurse \ + "$instance:~/BearBrowser/build/workspaces/${profile}-*/source/obj-bearbrowser-${profile}/dist/*.tar.*" \ + "./dist/linux/" --zone="$zone" ${project:+--project=$project} + +run gcloud compute instances delete "$instance" --zone="$zone" --quiet ${project:+--project=$project} + +if [ "$confirm" != "true" ]; then + echo "" + echo "Dry run complete. Re-run with --confirm to provision and incur cost." +fi From d8f50ae5a6bb1c28d6a1f246ab615e31ef67a9eb Mon Sep 17 00:00:00 2001 From: Michael Heller <21163552+mdheller@users.noreply.github.com> Date: Sun, 21 Jun 2026 21:24:34 -0400 Subject: [PATCH 09/11] packaging: add Snap package + AppImage recipe for BearBrowser --- ci/appimage.sh | 44 +++++++++++++++++++++++++++++ packaging/linux/snap/snapcraft.yaml | 38 +++++++++++++++++++++++++ 2 files changed, 82 insertions(+) create mode 100755 ci/appimage.sh create mode 100644 packaging/linux/snap/snapcraft.yaml diff --git a/ci/appimage.sh b/ci/appimage.sh new file mode 100755 index 0000000..bee5554 --- /dev/null +++ b/ci/appimage.sh @@ -0,0 +1,44 @@ +#!/bin/bash +set -x +rm -rf AppDir *.AppImage *.zsync +set -e + +mkdir -p AppDir/usr/bin AppDir/usr/lib/bearbrowser AppDir/usr/share/applications +mkdir -p AppDir/usr/share/icons/hicolor/scalable/apps + +# Copy the BearBrowser binary (graceful if not yet built) +if [ -f dist/linux/BearBrowser ]; then + install -Dsm755 dist/linux/BearBrowser AppDir/usr/lib/bearbrowser/BearBrowser +else + echo "BearBrowser binary not present at dist/linux/BearBrowser — AppImage will be incomplete until the GCP build lands" +fi + +# Default privacy profile +install -Dm644 profiles/default/user.js AppDir/usr/lib/bearbrowser/defaults/profile/user.js + +# Desktop file + icon +install -Dm644 packaging/linux/bearbrowser.desktop AppDir/usr/share/applications/bearbrowser.desktop +install -Dm644 packaging/linux/bearbrowser.desktop AppDir/bearbrowser.desktop +if [ -f branding/bearbrowser.svg ]; then + install -Dm644 branding/bearbrowser.svg AppDir/usr/share/icons/hicolor/scalable/apps/bearbrowser.svg + install -Dm644 branding/bearbrowser.svg AppDir/bearbrowser.svg +fi + +# AppRun launcher — sets the default profile path +cat > AppDir/AppRun <<'EOF' +#!/bin/sh +HERE="$(dirname "$(readlink -f "$0")")" +export PATH="$HERE/usr/bin:$PATH" +PROFILE_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/bearbrowser/profiles/default" +mkdir -p "$PROFILE_DIR" +exec "$HERE/usr/lib/bearbrowser/BearBrowser" --profile "$PROFILE_DIR" "$@" +EOF +chmod 755 AppDir/AppRun + +# Fetch appimagetool if needed +[ -x /tmp/appimagetool ] || ( curl -L 'https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage' -o /tmp/appimagetool && chmod +x /tmp/appimagetool ) + +TAG_NAME=${TAG_NAME:-$(git -c "core.abbrev=8" show -s "--format=%cd-%h" "--date=format:%Y%m%d-%H%M%S")} +OUTPUT=BearBrowser-x86_64.AppImage + +ARCH=x86_64 VERSION="$TAG_NAME" /tmp/appimagetool AppDir "$OUTPUT" diff --git a/packaging/linux/snap/snapcraft.yaml b/packaging/linux/snap/snapcraft.yaml new file mode 100644 index 0000000..cfa1cd8 --- /dev/null +++ b/packaging/linux/snap/snapcraft.yaml @@ -0,0 +1,38 @@ +name: bearbrowser +base: core22 +version: '0.1.0' +summary: Gecko-first privacy browser with 101 fingerprinting protections +description: | + BearBrowser is built on Firefox/LibreWolf ESR with a 101-surface + fingerprinting shield. No telemetry, no cloud sync, anti-tracking built in. +grade: stable +confinement: strict + +apps: + bearbrowser: + command: bin/bearbrowser + plugs: + - network + - network-bind + - home + - x11 + - wayland + - opengl + - audio-playback + - browser-support + - removable-media + +parts: + bearbrowser: + plugin: nil + source: . + override-build: | + mkdir -p $SNAPCRAFT_PART_INSTALL/lib/bearbrowser $SNAPCRAFT_PART_INSTALL/bin + if [ -f dist/linux/BearBrowser ]; then + install -Dm755 dist/linux/BearBrowser $SNAPCRAFT_PART_INSTALL/lib/bearbrowser/BearBrowser + else + echo "BearBrowser binary not present — snap will be incomplete until GCP build lands" + fi + install -Dm644 profiles/default/user.js $SNAPCRAFT_PART_INSTALL/lib/bearbrowser/defaults/profile/user.js + printf '#!/bin/sh\nexec $SNAP/lib/bearbrowser/BearBrowser --profile $SNAP_USER_DATA/.bearbrowser/profiles/default "$@"\n' > $SNAPCRAFT_PART_INSTALL/bin/bearbrowser + chmod 755 $SNAPCRAFT_PART_INSTALL/bin/bearbrowser From 404bd618327adf3935107f282e8378a26359c754 Mon Sep 17 00:00:00 2001 From: Michael Heller <21163552+mdheller@users.noreply.github.com> Date: Sun, 21 Jun 2026 22:26:46 -0400 Subject: [PATCH 10/11] agent-native: governance contract + sandbox mounts + evidence-emitting control-bridge spec (agent-runtime profile) --- docs/agent-control-bridge.md | 270 +++++++++ mounts/agent-browser-mounts.yaml | 154 ++++- policy/bearbrowser-contract.yaml | 565 ++++++++++++++---- settings/profiles/agent-runtime/README.md | 54 ++ .../profiles/agent-runtime/user-overlay.js | 69 +++ 5 files changed, 979 insertions(+), 133 deletions(-) create mode 100644 docs/agent-control-bridge.md create mode 100644 settings/profiles/agent-runtime/README.md create mode 100644 settings/profiles/agent-runtime/user-overlay.js diff --git a/docs/agent-control-bridge.md b/docs/agent-control-bridge.md new file mode 100644 index 0000000..55b2afd --- /dev/null +++ b/docs/agent-control-bridge.md @@ -0,0 +1,270 @@ +# BearBrowser Agent Control Bridge + +The evidence-emitting control-bridge specification: how TurtleTerm's copilot +drives BearBrowser's `agent-runtime` profile under the governance contract +(`policy/bearbrowser-contract.yaml`), with every action gated by policy and +attested by a replayable reasoning trace. + +This is the moat. No other browser ships a spec-governed, evidence-emitting +agent automation surface. The contract + bridge are the differentiating IP and +are ready the moment the binary lands. + +> **Honesty note — runtime binding is pending the binary build.** +> BearBrowser has no compiled binary yet (the LibreWolf compile lane is wired in +> `scripts/bearbrowser-build-binary.sh` but has not been run). The control +> endpoint described here is **not yet a live automation surface**. What is real +> and ready today is (1) this bridge spec, (2) the governance contract, and (3) +> the emission schema — which is **identical to TurtleTerm's** reasoning-event +> family, so the evidence fabric is already unified across the two products. The +> reference emitter is `TurtleTerm/assets/sourceos/bin/turtle-agentd` +> (`_open_reasoning_run`, `_emit_reasoning_event`, `_close_reasoning_run`); the +> browser bridge mirrors those exact shapes. + +--- + +## 1. Transport + +### Recommended surface: WebDriver-BiDi + +The agent drives BearBrowser over **WebDriver-BiDi**, the Gecko-native, +bidirectional automation protocol (the W3C successor to classic WebDriver, +implemented by the Firefox/LibreWolf "Remote Agent" / Marionette stack on which +BearBrowser is built). BiDi is the correct mechanism here because: + +- It is **native to the Gecko engine** BearBrowser is built on — no CDP shim, no + out-of-process driver translating commands. +- It is **bidirectional**: the browser pushes events (navigation, network, + log) the bridge needs to emit accurate `ReasoningEvent`s, not just + request/response RPC. +- It is **standards-track** (W3C), so the surface is stable and auditable. + +CDP (Chrome DevTools Protocol) is supported by Gecko only as a partial +compatibility layer; BiDi is the first-class path and is what this spec targets. +Playwright/Stagehand (see `docs/runtime-automation.md`) ride on top of BiDi. + +### Hardening — loopback-only, token-gated, off by default + +The control endpoint is exposed **only** under these conditions: + +| Property | Value | +|---------------------|------------------------------------------------------------------| +| Bind address | `127.0.0.1` (loopback only; never `0.0.0.0`) | +| Port | Ephemeral, allocated per session | +| Default state | **Off.** Opt-in per session (`agentRuntime.devtools.remoteDebugging.defaultDecision: deny` in the contract) | +| Auth | Per-session bearer token; required on every BiDi message | +| Token lifetime | Session-scoped; destroyed on `cleanupOnExit` | +| TLS | Not required on loopback; token is the auth boundary | + +The contract's `agentRuntime.devtools.remoteDebugging` block already declares +`bindAddress: 127.0.0.1`, `requireEphemeralPort: true`, `requireSessionToken: +true`, and a default decision of `deny`. The `agent-runtime` profile overlay +(`settings/profiles/agent-runtime/user-overlay.js`) sets the BiDi prefs to match. + +The loopback range (`127.0.0.0/8`) is in the contract's network **denyCidrs** — +the control endpoint is the *only* permitted loopback surface, and the page +itself can never reach it. + +--- + +## 2. Action lifecycle + +Every agent action flows through the same six-step lifecycle. Steps 2, 4, and 6 +emit canonical reasoning objects (specVersion `"2.0.0"`). + +``` + ┌──────────────────────────────────────────────────────────────────┐ + │ 1. SESSION OPEN → _open_reasoning_run() ⇒ ReasoningRun │ + │ 2. PER ACTION: │ + │ a. classify against contract (allowed | gated | prohibited) │ + │ b. if gated → request approval; emit PolicyDecision │ + │ c. if prohibited → deny; emit ReasoningEvent browser.policy.violation │ + │ d. execute via BiDi (allowed, or gated+approved) │ + │ e. _emit_reasoning_event() ⇒ ReasoningEvent (summary only) │ + │ 3. SESSION CLOSE → _close_reasoning_run() ⇒ ReasoningReceipt │ + └──────────────────────────────────────────────────────────────────┘ +``` + +### Step 1 — open the run + +On session start the bridge opens a `ReasoningRun` +(`$id https://schemas.srcos.ai/v2/ReasoningRun.json`). Mirrors +`turtle-agentd._open_reasoning_run`: + +```json +{ + "id": "urn:srcos:reasoning-run:9f2c…", + "type": "ReasoningRun", + "specVersion": "2.0.0", + "status": "running", + "task": { + "id": "urn:srcos:reasoning-task:9f2c…", + "title": "Browser session: research pricing pages" + }, + "agentRef": "urn:srcos:agent:turtle-copilot", + "workspaceRef": "urn:srcos:workspace:default", + "safeTrace": { + "mode": "operational-trace-only", + "rawPrivateReasoning": "not-collected", + "eventCount": 0 + }, + "eventRefs": [], + "artifactRefs": [], + "startedAt": "2026-06-21T18:00:00Z" +} +``` + +### Step 2 — emit a ReasoningEvent per action + +Each action emits one `ReasoningEvent` +(`$id https://schemas.srcos.ai/v2/ReasoningEvent.json`). `eventType` is +`browser.`. The `summary` is a **safe description** — e.g. +`"navigated to example.com"` — and **never** full page content. Page content is +`untrusted-observation`. Mirrors `turtle-agentd._emit_reasoning_event` (including +the 500-char summary cap): + +```json +{ + "id": "urn:srcos:reasoning-event:1a44…", + "type": "ReasoningEvent", + "specVersion": "2.0.0", + "runRef": "urn:srcos:reasoning-run:9f2c…", + "eventType": "browser.navigate", + "summary": "navigated to example.com", + "traceLevel": "workspace-safe", + "trustLevel": "untrusted-observation", + "capturedAt": "2026-06-21T18:00:01Z" +} +``` + +**Safe-trace boundary.** `traceLevel ∈ {public-safe, workspace-safe, +operator-private, restricted}` and `trustLevel ∈ {trusted-control-input, +trusted-workspace-source, semi-trusted-project-source, untrusted-observation, +restricted-material}`. Agent control intent is `trusted-control-input`; observed +page data is `untrusted-observation`. Secrets are masked (`mask_fields` +obligation); raw page text is never written to the trace. + +### Step 4 — emit a PolicyDecision for gated actions + +When an action classifies as **gated**, the bridge requests per-action approval +and emits a policy-decision (also carried as a `ReasoningEvent` of eventType +`browser.policy.decision`, with the structured decision in `extra`): + +```json +{ + "id": "urn:srcos:reasoning-event:7b91…", + "type": "ReasoningEvent", + "specVersion": "2.0.0", + "runRef": "urn:srcos:reasoning-run:9f2c…", + "eventType": "browser.policy.decision", + "summary": "gated action 'file-download' approved by operator", + "traceLevel": "workspace-safe", + "trustLevel": "trusted-control-input", + "capturedAt": "2026-06-21T18:01:10Z", + "decision": "permit", + "policyRef": "urn:srcos:policy:bearbrowser-agent-runtime", + "actionClass": "gated", + "approvalTokenRef": "urn:srcos:approval:…" +} +``` + +A **prohibited** action emits `eventType: browser.policy.violation` with +`decision: "deny"` and is never executed — no approval token can unlock it. + +### Step 6 — close with a ReasoningReceipt + +On session close the bridge writes the `ReasoningReceipt` +(`$id https://schemas.srcos.ai/v2/ReasoningReceipt.json`). `traceHash` is a +sha256 over the concatenated event-id lines. Mirrors +`turtle-agentd._close_reasoning_run`: + +```json +{ + "id": "urn:srcos:receipt:reasoning:c3e0…", + "type": "ReasoningReceipt", + "specVersion": "2.0.0", + "runRef": "urn:srcos:reasoning-run:9f2c…", + "taskRef": "urn:srcos:reasoning-task:9f2c…", + "status": "completed", + "traceHash": "sha256:…", + "replayClass": "non-replayable-side-effect", + "capturedAt": "2026-06-21T18:05:00Z" +} +``` + +**Replay-class derivation.** The session receipt's `replayClass` is the +*weakest* class of any action in the session: + +- `exact` — the whole session was deterministic reads/navigation on static + pages (navigate, read-dom, query-selector, extract-text, scroll, wait). +- `best-effort` — included render-timing-sensitive reads (screenshot, click, + fill-form-field). +- `non-replayable-side-effect` — included any gated outbound mutation + (submit-form, file-download, oauth-grant, payment-autofill, cross-origin-post, + clipboard-write). +- `evidence-only` — included sensor reads (geolocation, camera-mic) that cannot + be reproduced. + +`replayClass ∈ {exact, best-effort, evidence-only, non-replayable-side-effect}` +per the schema enum. + +--- + +## 3. Mapping table: agent intent → action → policy class → replayClass → eventType + +| Agent intent | BearBrowser action | Policy class | replayClass | eventType | +|--------------------------------------|---------------------|--------------|-------------------------------|----------------------------| +| "go to a page" | navigate | allowed | exact | `browser.navigate` | +| "read the page" | read-dom | allowed | exact | `browser.read-dom` | +| "find an element" | query-selector | allowed | exact | `browser.query-selector` | +| "get the text of X" | extract-text | allowed | exact | `browser.extract-text` | +| "take a screenshot" | screenshot | allowed | best-effort | `browser.screenshot` | +| "scroll down" | scroll | allowed | exact | `browser.scroll` | +| "wait for X" | wait | allowed | exact | `browser.wait` | +| "click this link/button" | click | allowed* | best-effort | `browser.click` | +| "type X into the search box" | fill-form-field | allowed* | best-effort | `browser.fill-form-field` | +| "submit this form" | submit-form | **gated** | non-replayable-side-effect | `browser.submit-form` | +| "download this file" | file-download | **gated** | non-replayable-side-effect | `browser.file-download` | +| "authorize this app" | oauth-grant | **gated** | non-replayable-side-effect | `browser.oauth-grant` | +| "pay with my saved card" | payment-autofill | **gated** | non-replayable-side-effect | `browser.payment-autofill` | +| "send this to another site" | cross-origin-post | **gated** | non-replayable-side-effect | `browser.cross-origin-post`| +| "copy this to clipboard" | clipboard-write | **gated** | non-replayable-side-effect | `browser.clipboard-write` | +| "share my location" | geolocation | **gated** | evidence-only | `browser.geolocation` | +| "use the camera/mic" | camera-mic | **gated** | evidence-only | `browser.camera-mic` | +| "log in for me" | enter-credentials | **prohibited** | n/a (denied) | `browser.policy.violation` | +| "type my card number" | enter-payment-details | **prohibited** | n/a (denied) | `browser.policy.violation` | +| "enter my SSN/passport" | enter-government-id | **prohibited** | n/a (denied) | `browser.policy.violation` | +| "change the sharing settings" | modify-access-controls | **prohibited** | n/a (denied) | `browser.policy.violation` | +| "solve the CAPTCHA" | bypass-captcha | **prohibited** | n/a (denied) | `browser.policy.violation` | +| "buy/transfer/place the order" | execute-trade-or-transfer | **prohibited** | n/a (denied) | `browser.policy.violation` | + +\* `click` and `fill-form-field` are allowed in their read-shaped form. A click +the planner classifies as a form submission, or a fill into a credential / +payment / government-ID field, is reclassified — to the gated `submit-form` or +the prohibited `enter-*` actions respectively — per the `PolicyCondition`s in the +contract. + +--- + +## 4. Where evidence lands + +The bridge writes events to the append-only local buffer +(`/run/sourceos/provenance`, the `provenance` mount) before ingestion. Sinks: + +- `eventSink: AgentPlane` — the reasoning-event stream. +- `policyDecisionSink: PolicyFabric` — permit/deny decisions. +- `workspaceVisibilitySink: ProphetWorkspace` — session visibility. + +Event field shapes are also documented in `docs/provenance-events.md`; the +reasoning-family objects here are the canonical, spec-typed superset. + +--- + +## 5. Unified fabric + +Because the bridge emits the **same** `ReasoningRun` / `ReasoningEvent` / +`ReasoningReceipt` objects, at the **same** specVersion (`2.0.0`), with the +**same** URN prefixes, as `turtle-agentd`, a browser session and a terminal +agent session are replayable and auditable through one fabric. A reviewer reads +one event stream to reconstruct what the copilot did across the terminal and the +browser. That unification — spec-governed, evidence-emitting, replayable — is the +6–12 month gap no other browser closes. diff --git a/mounts/agent-browser-mounts.yaml b/mounts/agent-browser-mounts.yaml index e7561d9..2d0635f 100644 --- a/mounts/agent-browser-mounts.yaml +++ b/mounts/agent-browser-mounts.yaml @@ -1,29 +1,144 @@ +# ============================================================================= +# BearBrowser agent-runtime sandbox mount plan +# ============================================================================= +# The filesystem/profile mount plan for the agent-runtime browser sandbox. +# Per-session isolated profile (rw), quarantined downloads (gated), a READ-ONLY +# mount of the policy contract, and NO access to the host home beyond an +# explicit, grant-required workspace-input mount. +# +# SPEC GROUNDING +# This plan conforms to the canonical SourceOS mount-policy vocabulary so a +# compiler can project it into the typed object: +# - AgentMachineMountPolicy https://schemas.srcos.ai/v2/AgentMachineMountPolicy.json +# The `agentMachineMountPolicy:` block below is a direct projection (its field +# names/enums — allowedRoots[].pathClass/accessMode, deniedPatterns[], +# downloadPolicy, evidence — match that schema exactly). The legacy +# `spec.mounts[]` block is the BearBrowser-native view kept for the overlay +# pipeline; the two are kept consistent. +# +# CONFINEMENT CONSISTENCY +# Keep consistent with the snap/flatpak confinement defined in packaging +# (no host-home access, downloads quarantined, profile ephemeral). The +# forbiddenHostPaths list mirrors the snap home-interface exclusions. +# +# HONESTY NOTE +# Runtime binding is pending the LibreWolf binary build. This is the mount +# contract the sandbox will be launched with; it is not yet enforced by a +# running browser process. +# ============================================================================= + apiVersion: sourceos.dev/v1alpha1 kind: BrowserMountPlan +specVersion: "2.0.0" + metadata: name: bearbrowser-agent-mounts labels: sourceos.dev/product: BearBrowser sourceos.dev/runtime: agent-browser + sourceos.dev/profile: agent-runtime + conformsTo: + - https://schemas.srcos.ai/v2/AgentMachineMountPolicy.json + runtimeBinding: pending-librewolf-binary + spec: defaultIsolation: session noAmbientHostMounts: true cleanupOnExit: true + + # --------------------------------------------------------------------------- + # Canonical projection — AgentMachineMountPolicy.json + # ($id https://schemas.srcos.ai/v2/AgentMachineMountPolicy.json) + # --------------------------------------------------------------------------- + agentMachineMountPolicy: + id: urn:srcos:agent-machine-mount-policy:bearbrowser-agent-runtime + type: AgentMachineMountPolicy + specVersion: "2.0.0" + name: BearBrowser agent-runtime mounts + defaultDecision: deny # enum: deny | ask | permit + allowedRoots: + - path: /workspace/browser-profile + pathClass: cache # ephemeral per-session profile state + accessMode: read-write # enum: AgentMachineMountPolicy allowedRoot.accessMode + recursive: true + requiresGrant: false + description: Per-session isolated browser profile (cookies, storage). Wiped on exit. + - path: /workspace/downloads + pathClass: downloads + accessMode: browser-read-write-agent-read-only + recursive: false + requiresGrant: true # downloads are GATED per the contract + description: Quarantined downloads. Browser writes; agent reads only after a gated approval. + - path: /workspace/browser-captures + pathClass: artifacts + accessMode: write-only-artifacts + recursive: false + requiresGrant: false + description: Screenshots/DOM snapshots/PDFs; hashed + provenance-logged. + - path: /run/sourceos/policy/bearbrowser-contract.yaml + pathClass: app-bridge + accessMode: read-only # the policy contract is READ-ONLY to the sandbox + recursive: false + requiresGrant: false + description: Read-only mount of policy/bearbrowser-contract.yaml injected by PolicyFabric. + - path: /workspace/input + pathClass: documents + accessMode: read-only + recursive: false + requiresGrant: true # the ONLY route to user files; explicit grant required + description: Explicitly-granted workspace files. No ambient host-home access. + deniedPatterns: + - { pattern: "$HOME/**", reason: "No ambient host-home access.", severity: deny } + - { pattern: "~/.ssh/**", reason: "SSH keys.", severity: deny } + - { pattern: "~/.aws/**", reason: "Cloud credentials.", severity: deny } + - { pattern: "~/.azure/**", reason: "Cloud credentials.", severity: deny } + - { pattern: "~/.config/gh/**", reason: "GitHub token.", severity: deny } + - { pattern: "~/.kube/**", reason: "Cluster credentials.", severity: deny } + - { pattern: "/var/run/docker.sock", reason: "Container escape.", severity: deny } + - { pattern: "/var/run/podman/podman.sock", reason: "Container escape.", severity: deny } + uidGidMapping: + mode: runtime-default + syncMode: topolvm-local + downloadPolicy: + defaultHostPath: ~/Downloads/SourceOS/agent-downloads + defaultAgentPath: /workspace/downloads + browserAccess: read-write + agentAccess: read-only + directExecutionAllowed: false # quarantine: a downloaded file is never auto-executed + hashDownloads: true + recordSourceDomain: true + mountWholeDownloadsDirectoryAllowed: false + promotionPolicy: + downloadsToCodeRequiresEvidence: true + downloadsToDocumentsRequiresEvidence: true + allowAutomaticPromotion: false + evidence: + required: true + recordDeniedAttempts: true + recordMountLaunch: true + recordPromotionActions: true + redactHostUserName: true + + # --------------------------------------------------------------------------- + # BearBrowser-native mount view (consumed by the overlay pipeline). + # Kept consistent with the canonical projection above. + # --------------------------------------------------------------------------- mounts: - name: downloads mountClass: agent-downloads - purpose: Controlled browser downloads created by an agent session. + purpose: Quarantined browser downloads created by an agent session (GATED action). defaultPath: /workspace/downloads mode: readWrite governed: true required: true persistByDefault: true + quarantine: true provenance: emitOnCreate: browser.download.created requireSha256: true - name: profile mountClass: agent-profile - purpose: Ephemeral browser profile state, cookies, local storage, extension state, and browser cache partition metadata. + purpose: Ephemeral, per-session browser profile state, cookies, local storage, cache partition metadata. defaultPath: /workspace/browser-profile mode: readWrite governed: true @@ -35,7 +150,7 @@ spec: shareAcrossAgents: false - name: captures mountClass: agent-captures - purpose: Screenshots, DOM snapshots, PDFs, HAR files, and provenance-adjacent capture artifacts. + purpose: Screenshots, DOM snapshots, PDFs, HAR files, provenance-adjacent capture artifacts. defaultPath: /workspace/browser-captures mode: readWrite governed: true @@ -46,16 +161,33 @@ spec: requireSha256: true - name: cache mountClass: agent-cache - purpose: Disposable network and browser build/runtime cache. + purpose: Disposable network and browser runtime cache. defaultPath: /workspace/browser-cache mode: readWrite governed: true required: false persistByDefault: false cleanupOnExit: true + - name: policy-contract + mountClass: policy-runtime-readonly + purpose: READ-ONLY mount of the agent-runtime governance contract. + defaultPath: /run/sourceos/policy/bearbrowser-contract.yaml + sourcePath: policy/bearbrowser-contract.yaml + mode: readOnly + governed: true + required: true + persistByDefault: false + - name: policy-bundle + mountClass: policy-runtime-readonly + purpose: Runtime policy bundle injected by PolicyFabric (compiled Policy/Rule/Obligation objects). + defaultPath: /run/sourceos/policy + mode: readOnly + governed: true + required: true + persistByDefault: false - name: workspace-input mountClass: workspace-input-readonly - purpose: Read-only user/workspace files explicitly granted to the browser session. + purpose: Read-only user/workspace files EXPLICITLY granted to the browser session. The only host-file route. defaultPath: /workspace/input mode: readOnly governed: true @@ -65,22 +197,16 @@ spec: requireExplicitGrant: true denySymlinkEscape: true denyDeviceFiles: true - - name: policy - mountClass: policy-runtime-readonly - purpose: Runtime policy bundle injected by PolicyFabric. - defaultPath: /run/sourceos/policy - mode: readOnly - governed: true - required: true - persistByDefault: false - name: provenance mountClass: provenance-writeonly - purpose: Append-only local event buffer before AgentPlane/PolicyFabric ingestion. + purpose: Append-only local ReasoningEvent buffer before AgentPlane/PolicyFabric ingestion. defaultPath: /run/sourceos/provenance mode: writeOnly governed: true required: true persistByDefault: false + + # No access to host home beyond the explicit, grant-required workspace-input mount. forbiddenHostPaths: - $HOME - ~/.ssh diff --git a/policy/bearbrowser-contract.yaml b/policy/bearbrowser-contract.yaml index 54c2ee5..06a957e 100644 --- a/policy/bearbrowser-contract.yaml +++ b/policy/bearbrowser-contract.yaml @@ -1,136 +1,463 @@ +# ============================================================================= +# BearBrowser agent-runtime governance contract +# ============================================================================= +# WHAT THIS IS +# The formally-specified governance contract for the BearBrowser +# `agent-runtime` profile. It declares what an agent (TurtleTerm's copilot) +# IS and ISN'T allowed to do when driving the browser, in the canonical +# SourceOS policy vocabulary. The agent-control bridge (see +# docs/agent-control-bridge.md) evaluates every requested action against this +# contract and emits replayable attestation. +# +# HONESTY NOTE — RUNTIME BINDING PENDING +# BearBrowser has no compiled binary yet (the LibreWolf compile lane is wired +# in scripts/bearbrowser-build-binary.sh but has not been run). This contract +# and the bridge spec are the differentiating IP and are ready the moment the +# binary lands. The runtime enforcement point (the control endpoint inside the +# browser process) is NOT yet live. Nothing here should be read as a claim of +# a working automation surface today — it is the governance + control contract +# that the surface will be wired to. +# +# SPEC GROUNDING (canonical sourceos-spec schemas, $id cited inline) +# This document is the human/operator-facing contract. Each block is annotated +# with the canonical schema whose vocabulary it conforms to, so a policy +# compiler can project this YAML into the typed objects: +# - Policy https://schemas.srcos.ai/v2/Policy.json +# - Rule https://schemas.srcos.ai/v2/Rule.json +# - PolicyCondition https://schemas.srcos.ai/v2/PolicyCondition.json +# - Obligation https://schemas.srcos.ai/v2/Obligation.json +# - SubjectSelector https://schemas.srcos.ai/v2/SubjectSelector.json +# - ObjectSelector https://schemas.srcos.ai/v2/ObjectSelector.json +# - NetworkAccessProfile https://schemas.srcos.ai/v2/NetworkAccessProfile.json +# - ExecutionSurface https://schemas.srcos.ai/v2/ExecutionSurface.json +# - ReasoningEvent https://schemas.srcos.ai/v2/ReasoningEvent.json +# - ReasoningRun https://schemas.srcos.ai/v2/ReasoningRun.json +# - ReasoningReceipt https://schemas.srcos.ai/v2/ReasoningReceipt.json +# The reasoning family is specVersion "2.0.0" with URN prefixes +# urn:srcos:reasoning-run: / urn:srcos:reasoning-event: / +# urn:srcos:receipt:reasoning: and safe-trace = summaries only. +# Reference implementation that this contract's emission posture mirrors: +# TurtleTerm/assets/sourceos/bin/turtle-agentd (_open_reasoning_run, +# _emit_reasoning_event, _close_reasoning_run). +# ============================================================================= + apiVersion: sourceos.dev/v1alpha1 kind: BrowserPolicyContract +specVersion: "2.0.0" + metadata: - name: bearbrowser + name: bearbrowser-agent-runtime labels: sourceos.dev/product: BearBrowser - sourceos.dev/runtime: browser + sourceos.dev/runtime: agent-browser + sourceos.dev/profile: agent-runtime + conformsTo: + # The canonical schema $ids this contract projects into. + - https://schemas.srcos.ai/v2/Policy.json + - https://schemas.srcos.ai/v2/Rule.json + - https://schemas.srcos.ai/v2/PolicyCondition.json + - https://schemas.srcos.ai/v2/Obligation.json + - https://schemas.srcos.ai/v2/NetworkAccessProfile.json + - https://schemas.srcos.ai/v2/ExecutionSurface.json + - https://schemas.srcos.ai/v2/ReasoningEvent.json + runtimeBinding: pending-librewolf-binary + spec: - defaultMode: humanSecure + # --------------------------------------------------------------------------- + # IDENTITY — who this policy governs. + # Projects to Policy.scope (Policy.json $id above) using SubjectSelector / + # ObjectSelector vocabulary. + # --------------------------------------------------------------------------- + identity: + # The canonical Policy URN this contract compiles to. + policyId: urn:srcos:policy:bearbrowser-agent-runtime + # The agent actor. The browser-agent grant is the browser projection of the + # TurtleTerm copilot identity. + agentActor: urn:srcos:agent:turtle-copilot + browserAgentGrant: urn:srcos:agent:turtle-copilot#bearbrowser + profile: agent-runtime + # Policy.scope projection. subjects[].kind / objects[].objectType are the + # only enum values SubjectSelector.json / ObjectSelector.json permit. + scope: + subjects: + - kind: workload # SubjectSelector.kind + match: + agentRef: urn:srcos:agent:turtle-copilot + profile: agent-runtime + objects: + - objectType: run # ObjectSelector.objectType — browser sessions are governed runs + - objectType: asset # downloaded files / captures + - objectType: provenance # the reasoning evidence stream + purposes: + - browser-automation + - web-research + - form-fill-gated + # Every governed browser session is anchored to a ReasoningRun + workspace. + session: + requireSessionId: true + requireAgentId: true + requireWorkspaceId: true + requireReasoningRun: true # urn:srcos:reasoning-run: opened per session + maxDefaultTtlMinutes: 120 + ephemeralByDefault: true + cleanupOnExit: true + + # --------------------------------------------------------------------------- + # DEFAULT DECISION — deny-by-default, matching Policy semantics and the + # NetworkAccessProfile.defaultDecision "deny" posture. + # --------------------------------------------------------------------------- defaultDecision: deny - modes: - humanSecure: - description: Privacy-first human browser profile derived from LibreWolf with SourceOS workspace integration. - profilePersistence: persistent - automation: disabledByDefault - downloads: - governed: true - mountClass: user-downloads - defaultDecision: allowWithinWorkspace - requireProvenanceEvent: true - captures: - screenshots: userInitiatedOnly - domSnapshots: disabledByDefault - provenanceLog: optional - network: - policyRequired: true - defaultDecision: allowPublicWeb - denyPrivateNetworkByDefault: false - requireEgressAttribution: true - filesystem: - policyRequired: true - defaultDecision: denyHostFilesystem - allowedMountClasses: - - user-downloads - - workspace-documents - credentials: - sharedWithAgents: false - source: humanProfileOnly - export: denied - nativeMessaging: - defaultDecision: deny - allowlistRequired: true - clipboard: - defaultDecision: userMediated - mediaDevices: - camera: userMediated - microphone: userMediated - screenShare: userMediated - agentRuntime: - description: Governed browser execution surface for local, cloud, and fog agents. - profilePersistence: ephemeralByDefault - automation: policyMediated - defaultDecision: deny - session: - requireSessionId: true - requireAgentId: true - requireWorkspaceId: true - maxDefaultTtlMinutes: 120 - cleanupOnExit: true - downloads: - governed: true - mountClass: agent-downloads - defaultDecision: denyOutsideMount - requireProvenanceEvent: true - requireContentHash: true - captures: - screenshots: policyMediated - domSnapshots: policyMediated - pdfExports: policyMediated - provenanceLog: required - defaultDecision: denyUnlessGranted - network: - policyRequired: true - defaultDecision: denyUnlessGranted - denyPrivateNetworkByDefault: true - denyLoopbackByDefault: true - denyMetadataServices: true - requireEgressAttribution: true - allowedByPolicy: - - publicWeb - - explicitDomainAllowlist - - workspaceApprovedEndpoints - filesystem: - policyRequired: true - defaultDecision: denyHostFilesystem - noAmbientHomeAccess: true - noAmbientSshAccess: true - noAmbientCloudCredentialAccess: true - allowedMountClasses: - - agent-downloads - - agent-profile - - agent-captures - - agent-cache - - workspace-input-readonly - credentials: - sharedWithHumanProfile: false - sharedWithAgents: false - source: policyBrokerOnly - export: denied - persistence: sessionScoped - requireCredentialBoundaryEvents: true - nativeMessaging: - defaultDecision: deny - allowlistRequired: true - allowedHosts: [] - extensions: - defaultDecision: deny - install: policyMediated - allowlistRequired: true - clipboard: - defaultDecision: deny - policyMediated: true - mediaDevices: - camera: denied - microphone: denied - screenShare: policyMediated - devtools: - defaultDecision: policyMediated - remoteDebugging: - defaultDecision: deny - bindAddress: 127.0.0.1 - requireEphemeralPort: true - requireSessionToken: true + + # --------------------------------------------------------------------------- + # ACTION CLASSES — the heart of the contract. + # Each action maps to a Rule (Rule.json): effect = permit|deny, + # operations ⊆ {read, write, export, transform, share}, and an optional + # PolicyCondition (PolicyCondition.json) expressed in jsonlogic. + # + # CLASSES: + # allowed — permitted unconditionally within the profile (read-shaped, + # non-mutating). Still emits a ReasoningEvent (see obligations). + # gated — permitted ONLY with explicit per-action human/operator + # approval. Mirrors the assistant safety rules: financial, + # credential, download, and irreversible/side-effecting actions + # require confirmation. Emits a policy-decision event. + # prohibited — denied unconditionally. No approval path in this profile. + # --------------------------------------------------------------------------- + actions: + + # ---- ALLOWED (read-shaped, non-mutating; permit unconditionally) -------- + allowed: + # rule.effect=permit, rule.operations=[read] + - action: navigate + rule: { effect: permit, operations: [read] } + replayClass: exact # deterministic on a static page + eventType: browser.navigate + note: Load a URL. Subject to the network access profile below. + - action: read-dom + rule: { effect: permit, operations: [read] } + replayClass: exact + eventType: browser.read-dom + - action: query-selector + rule: { effect: permit, operations: [read] } + replayClass: exact + eventType: browser.query-selector + - action: extract-text + rule: { effect: permit, operations: [read] } + replayClass: exact + eventType: browser.extract-text + note: Returns bounded summaries to the agent; raw page text never enters the trace. + - action: screenshot + rule: { effect: permit, operations: [read] } + replayClass: best-effort # pixels vary with render timing + eventType: browser.screenshot + note: Capture artifact written to the agent-captures mount, hashed, provenance-logged. + - action: scroll + rule: { effect: permit, operations: [read] } + replayClass: exact + eventType: browser.scroll + - action: wait + rule: { effect: permit, operations: [read] } + replayClass: exact + eventType: browser.wait + - action: click + rule: + effect: permit + operations: [read] + # PolicyCondition (jsonlogic): in-page navigation/expansion clicks are + # allowed; a click that the planner classifies as a form submission or + # an outbound state mutation is reclassified to the gated `submit-form`. + condition: + language: jsonlogic + expr: + "!": + - { "in": ["mutation", { "var": "action.sideEffectClasses" }] } + notes: A click with no declared side-effect class is read-shaped; mutating clicks route to gated. + replayClass: best-effort + eventType: browser.click + - action: fill-form-field + rule: + effect: permit + operations: [write] + # Typing into a field is permitted; SUBMITTING is gated. Credential / + # payment / government-ID fields are PROHIBITED (see below) and this + # condition denies them at fill time, not just at submit time. + condition: + language: jsonlogic + expr: + "!": + - { "in": [{ "var": "field.sensitivity" }, ["credential", "payment", "government-id"]] } + notes: Filling sensitive field classes is denied here and handled by the prohibited block. + replayClass: best-effort + eventType: browser.fill-form-field + + # ---- GATED (require explicit per-action approval; permit-with-obligation) - + # Each gated action compiles to a Rule whose PolicyCondition requires an + # approval token for THIS action instance, plus an emit-policy-decision + # obligation. Default (no token) => deny. + gated: + - action: submit-form + rule: + effect: permit + operations: [write, share] + condition: + language: jsonlogic + expr: { "==": [{ "var": "approval.granted" }, true] } + requiresApproval: per-action + replayClass: non-replayable-side-effect + eventType: browser.submit-form + reason: Irreversible outbound state mutation. + - action: file-download + rule: + effect: permit + operations: [write] + condition: + language: jsonlogic + expr: { "==": [{ "var": "approval.granted" }, true] } + requiresApproval: per-action + replayClass: non-replayable-side-effect + eventType: browser.file-download + reason: Writes to disk (quarantined agent-downloads mount); requires content hash + provenance. + - action: oauth-grant + rule: + effect: permit + operations: [write, share] + condition: + language: jsonlogic + expr: { "==": [{ "var": "approval.granted" }, true] } + requiresApproval: per-action + replayClass: non-replayable-side-effect + eventType: browser.oauth-grant + reason: Delegates authority; high blast radius. + - action: payment-autofill + rule: + effect: permit + operations: [write] + condition: + language: jsonlogic + expr: { "==": [{ "var": "approval.granted" }, true] } + requiresApproval: per-action + replayClass: non-replayable-side-effect + eventType: browser.payment-autofill + reason: Financial. Autofill of stored instruments only; manual entry of card numbers is prohibited. + - action: cross-origin-post + rule: + effect: permit + operations: [share] + condition: + language: jsonlogic + expr: { "==": [{ "var": "approval.granted" }, true] } + requiresApproval: per-action + replayClass: non-replayable-side-effect + eventType: browser.cross-origin-post + reason: Exfiltration risk; defeats first-party isolation if ungated. + - action: clipboard-write + rule: + effect: permit + operations: [write] + condition: + language: jsonlogic + expr: { "==": [{ "var": "approval.granted" }, true] } + requiresApproval: per-action + replayClass: non-replayable-side-effect + eventType: browser.clipboard-write + reason: Writes into the host clipboard shared with the human. + - action: geolocation + rule: + effect: permit + operations: [read] + condition: + language: jsonlogic + expr: { "==": [{ "var": "approval.granted" }, true] } + requiresApproval: per-action + replayClass: evidence-only + eventType: browser.geolocation + reason: Discloses location; default-disabled by the shield (geo.enabled=false). + - action: camera-mic + rule: + effect: permit + operations: [read] + condition: + language: jsonlogic + expr: { "==": [{ "var": "approval.granted" }, true] } + requiresApproval: per-action + replayClass: evidence-only + eventType: browser.camera-mic + reason: Sensor capture; default-denied by the shield (media.navigator.enabled=false). + + # ---- PROHIBITED (deny unconditionally; no approval path in this profile) - + # Each compiles to a Rule with effect=deny and NO condition (applies always). + # These are the hard floor — even an approval token cannot unlock them. + prohibited: + - action: enter-credentials + rule: { effect: deny, operations: [write] } + eventType: browser.policy.violation + reason: Agent must never type usernames/passwords. Auth is the human's responsibility. + - action: enter-payment-details + rule: { effect: deny, operations: [write] } + eventType: browser.policy.violation + reason: Manual entry of card/bank numbers is forbidden (autofill of stored instruments is gated). + - action: enter-government-id + rule: { effect: deny, operations: [write] } + eventType: browser.policy.violation + reason: SSN / passport / national-ID entry is forbidden. + - action: modify-access-controls + rule: { effect: deny, operations: [write, transform] } + eventType: browser.policy.violation + reason: No changing account permissions, sharing settings, or security config. + - action: bypass-captcha + rule: { effect: deny, operations: [transform] } + eventType: browser.policy.violation + reason: No CAPTCHA-solving or anti-bot circumvention. + - action: execute-trade-or-transfer + rule: { effect: deny, operations: [write, share] } + eventType: browser.policy.violation + reason: No trades, transfers, purchases, or money movement — matches the assistant financial-action floor. + + # --------------------------------------------------------------------------- + # NETWORK ACCESS PROFILE + # Projects to NetworkAccessProfile.json + # ($id https://schemas.srcos.ai/v2/NetworkAccessProfile.json). + # Field names/enums conform to that schema exactly. + # --------------------------------------------------------------------------- + networkAccess: + id: urn:srcos:network-access-profile:bearbrowser-agent-runtime + type: NetworkAccessProfile + specVersion: "2.0.0" + name: BearBrowser agent-runtime egress + scope: agent-machine # enum: NetworkAccessProfile.scope + defaultDecision: deny # enum: deny | allow-with-policy | ask + profiles: + - profileId: bearbrowser-agent-egress + profileClass: zero-trust # enum: NetworkAccessProfile.profiles[].profileClass + priority: 0 + defaultEgress: allow-listed # enum: deny | allow-listed | allow-with-prompt | allow-any + allowDomains: + - "*" # public web is reachable; first-party isolation enforced in-browser + denyDomains: [] + denyCidrs: + # No private-network / loopback / cloud-metadata egress from the agent + # browser. Consistent with the SSRF posture and the shield. + - 169.254.169.254/32 # cloud instance metadata + - 127.0.0.0/8 # loopback (the control endpoint is the ONLY loopback surface) + - 10.0.0.0/8 + - 172.16.0.0/12 + - 192.168.0.0/16 + - fd00::/8 + notes: >- + First-party isolation ON (consistent with the 101-pref shield in + profiles/default/user.js). No telemetry egress of any kind. + precedence: [enterprise, workspace, device, agent] + evidence: + emitNetworkDecision: true + emitFirewallBinding: true + emitMeshBinding: false + emitModelProviderRoute: false + redactDestinationPath: true # destination paths redacted from evidence — safe-trace + telemetry: disabled # belt-and-suspenders with the DisableTelemetry policy + + # --------------------------------------------------------------------------- + # EXECUTION SURFACE + # Projects to ExecutionSurface.json + # ($id https://schemas.srcos.ai/v2/ExecutionSurface.json). + # --------------------------------------------------------------------------- + executionSurface: + pty: false + workdir: /workspace + background: true + reviewOnly: false + worktreeStrategy: none + sandboxMode: browser-sandbox # enum: ExecutionSurface.sandboxMode + networkMode: allowlist # enum: none | allowlist | full + egressAllowlist: + - public-web + elevated: false + protectedPaths: # ExecutionSurface.protectedPaths — host secrets off-limits + - $HOME + - ~/.ssh + - ~/.aws + - ~/.config/gh + approvalProfile: bearbrowser-gated-actions + + # --------------------------------------------------------------------------- + # OBLIGATIONS + # Project to Obligation.json ($id https://schemas.srcos.ai/v2/Obligation.json). + # Obligation.name is a fixed enum: log_access | retain_provenance | + # mask_fields | obfuscate_before_export | aggregate_only | attest_container. + # Obligation.when is pre | post | runtime. params is free-form. + # --------------------------------------------------------------------------- + obligations: + # Every action emits a conformant ReasoningEvent + # ($id https://schemas.srcos.ai/v2/ReasoningEvent.json), summary-only. + - name: log_access + when: post + params: + emit: ReasoningEvent + schema: https://schemas.srcos.ai/v2/ReasoningEvent.json + specVersion: "2.0.0" + eventTypePrefix: browser. + runRefRequired: true # urn:srcos:reasoning-run: opened at session start + traceLevel: workspace-safe # ReasoningEvent.traceLevel enum + trustLevel: untrusted-observation # page content is untrusted; ReasoningEvent.trustLevel enum + # Gated AND prohibited(violation) actions additionally emit a policy-decision. + - name: log_access + when: runtime + params: + emit: PolicyDecision + appliesTo: [gated, prohibited] + description: Records permit/deny + the approval token reference (token value never logged). + # Provenance retained for every session and every artifact. + - name: retain_provenance + when: post + params: + provenanceMount: agent-captures + downloadProvenanceEvent: browser.download.created + requireSha256: true + # Secrets redacted; only bounded summaries in the trace (safe-trace boundary). + - name: mask_fields + when: pre + params: + redact: [credentials, tokens, cookies, authorization-headers, payment-instruments] + rawPageContent: never-in-trace # mirrors ReasoningRun.safeTrace.rawPrivateReasoning="not-collected" + summaryMaxChars: 500 # matches turtle-agentd _emit_reasoning_event summary cap + # The browser session container/sandbox is attested before automation opens. + - name: attest_container + when: pre + params: + surface: browser-sandbox + mountPlan: mounts/agent-browser-mounts.yaml + + # --------------------------------------------------------------------------- + # FINGERPRINTING POSTURE — spoof normality, ride the ESR cohort. + # The agent browser must NOT look like an automation rig; it presents the same + # surface as a human BearBrowser instance so it blends into the Firefox ESR + # cohort. Source of truth is the 101-pref shield. + # --------------------------------------------------------------------------- + fingerprinting: + posture: spoof-normality + cohort: firefox-esr + shield: profiles/default/user.js # the 101-pref shield (frozen at 101/101) + inheritsShield: true + automationMarkersHidden: true # navigator.webdriver and CDP markers suppressed in agent-runtime + note: >- + The control endpoint is loopback-only and never alters the public + fingerprint surface. The page sees a normal ESR-cohort BearBrowser. + + # --------------------------------------------------------------------------- + # AUDIT — required events and their sinks (unchanged fabric contract). + # --------------------------------------------------------------------------- audit: requiredEvents: - browser.session.started - browser.session.ended - - browser.navigation.requested - - browser.navigation.completed + - browser.navigate + - browser.policy.decision + - browser.policy.violation - browser.download.created - browser.capture.created - - browser.policy.violation - browser.credential.boundary + reasoningEventStream: ReasoningEvent # https://schemas.srcos.ai/v2/ReasoningEvent.json + sessionRun: ReasoningRun # https://schemas.srcos.ai/v2/ReasoningRun.json + sessionReceipt: ReasoningReceipt # https://schemas.srcos.ai/v2/ReasoningReceipt.json eventSink: AgentPlane policyDecisionSink: PolicyFabric workspaceVisibilitySink: ProphetWorkspace diff --git a/settings/profiles/agent-runtime/README.md b/settings/profiles/agent-runtime/README.md new file mode 100644 index 0000000..e7201ab --- /dev/null +++ b/settings/profiles/agent-runtime/README.md @@ -0,0 +1,54 @@ +# BearBrowser `agent-runtime` profile + +The governed browser execution surface that TurtleTerm's copilot drives. This +profile is the agent-facing counterpart to `human-secure`. It is **deny by +default**, ephemeral, and every action it takes is policy-gated and attested. + +> Runtime binding is pending the LibreWolf binary build. These files describe +> the intended posture; they are not yet active in a running browser process. + +## Files + +| File | Role | +|-------------------|----------------------------------------------------------------------| +| `user.js` | Baseline agent-runtime prefs (downloads, sessionstore, sensor off). | +| `user-overlay.js` | Agent-runtime overlay: enables the loopback BiDi control surface, suppresses automation-blocking prompts, re-asserts the shield. Applied on top of `user.js` and the 101-pref shield in `profiles/default/user.js`. | +| `policies.json` | Enterprise policy keys (telemetry off, no studies, no Pocket, etc.). | + +## Posture + +- **Control surface:** WebDriver-BiDi (Gecko-native), loopback-only + (`127.0.0.1`), ephemeral port, per-session token, **off by default** — + opt-in per session. See `docs/agent-control-bridge.md`. +- **Fingerprinting:** spoof-normality. Inherits the full 101-pref shield; + rides the Firefox-ESR cohort. `navigator.webdriver` is suppressed so the page + cannot detect the agent. The overlay never weakens the shield. +- **Downloads:** gated + quarantined to `/workspace/downloads`; never + auto-executed. See `mounts/agent-browser-mounts.yaml`. +- **Sensitive actions:** credentials, payment, government-ID entry are + **prohibited**; form-submit, downloads, oauth, payment-autofill, clipboard, + geolocation, camera/mic are **gated** (per-action approval). See the action + classes in `policy/bearbrowser-contract.yaml`. +- **Evidence:** every action emits a conformant `ReasoningEvent` + (specVersion `2.0.0`); sessions open a `ReasoningRun` and close with a + `ReasoningReceipt`, identical to the `turtle-agentd` emitter so the fabric is + unified. + +## What the overlay sets (summary) + +- `remote.active-protocols=1` (BiDi only), `remote.enabled=false` (per-session + opt-in), `remote.force-local=true`, `marionette.port=0` (ephemeral). +- Suppresses `beforeunload`, popup, tab-close, crash-resume, and update prompts + that block unattended automation. +- Pins downloads to `/workspace/downloads`; disables credential/autofill storage. +- Re-asserts `privacy.resistFingerprinting`, `privacy.firstparty.isolate`, + timer-precision reduction, `dom.webdriver.enabled=false`, telemetry off. + +## Governance authority + +- Contract: `policy/bearbrowser-contract.yaml` +- Mount plan: `mounts/agent-browser-mounts.yaml` +- Bridge spec: `docs/agent-control-bridge.md` +- Canonical schemas: `sourceos-spec/schemas/` (Policy, Rule, Obligation, + NetworkAccessProfile, ExecutionSurface, Reasoning* — `$id`s cited in the + contract). diff --git a/settings/profiles/agent-runtime/user-overlay.js b/settings/profiles/agent-runtime/user-overlay.js new file mode 100644 index 0000000..95a1cfb --- /dev/null +++ b/settings/profiles/agent-runtime/user-overlay.js @@ -0,0 +1,69 @@ +// ============================================================================= +// BearBrowser agent-runtime overlay preferences +// ============================================================================= +// Layered ON TOP of the 101-pref fingerprinting shield (profiles/default/user.js) +// and the agent-runtime baseline (settings/profiles/agent-runtime/user.js). +// This overlay enables the loopback, token-gated WebDriver-BiDi control surface +// for the agent-control bridge (docs/agent-control-bridge.md) and disables the +// interactive prompts that would otherwise block headless automation — WITHOUT +// weakening the shield. The page still sees a normal Firefox-ESR-cohort browser +// (fingerprinting posture: spoof-normality). +// +// HONESTY NOTE: runtime binding pending the LibreWolf binary build. These prefs +// describe the intended agent-runtime posture; they are not yet active in a +// running browser process. +// +// Governance: policy/bearbrowser-contract.yaml +// (agentRuntime.devtools.remoteDebugging: bindAddress 127.0.0.1, +// requireEphemeralPort, requireSessionToken, default deny / opt-in per session) +// ============================================================================= + +// ── WebDriver-BiDi / Remote Agent control surface (loopback-only) ──────────── +// Gecko-native automation protocol. OFF by default at the profile level; the +// bridge launches with the runtime flag per session and binds to an ephemeral +// loopback port behind a per-session token. We pin the protocol to BiDi. +user_pref("remote.active-protocols", 1); // 1 = WebDriver-BiDi only (not CDP) +user_pref("remote.enabled", false); // off by default; bridge opts in per session +user_pref("remote.force-local", true); // refuse non-loopback binds +user_pref("marionette.port", 0); // 0 = ephemeral port chosen at launch +user_pref("remote.log.level", "Info"); + +// ── Suppress prompts that block unattended automation ──────────────────────── +// These do NOT touch the fingerprint surface; they remove blocking modals. +user_pref("dom.disable_beforeunload", true); // no "leave page?" modal +user_pref("dom.disable_open_during_load", true); // suppress popups during load +user_pref("browser.tabs.warnOnClose", false); +user_pref("browser.tabs.warnOnCloseOtherTabs", false); +user_pref("browser.sessionstore.resume_from_crash", false); +user_pref("browser.shell.checkDefaultBrowser", false); +user_pref("app.update.auto", false); // no update prompts mid-session +user_pref("datareporting.policy.dataSubmissionEnabled", false); +user_pref("browser.aboutConfig.showWarning", false); + +// ── Downloads: gated + quarantined (see mounts/agent-browser-mounts.yaml) ───── +user_pref("browser.download.useDownloadDir", true); +user_pref("browser.download.dir", "/workspace/downloads"); +user_pref("browser.download.folderList", 2); // 2 = use browser.download.dir +user_pref("browser.download.always_ask_before_handling_new_types", false); +user_pref("browser.download.manager.addToRecentDocs", false); +// A downloaded file is never auto-opened/executed (quarantine; gated promotion). +user_pref("browser.helperApps.deleteTempFileOnExit", true); + +// ── Sensitive surfaces default-DENIED (gated/prohibited in the contract) ────── +user_pref("signon.rememberSignons", false); // agent never stores/enters credentials +user_pref("signon.autofillForms", false); +user_pref("extensions.formautofill.creditCards.enabled", false); +user_pref("extensions.formautofill.addresses.enabled", false); +user_pref("media.navigator.enabled", false); // camera/mic gated +user_pref("geo.enabled", false); // geolocation gated +user_pref("dom.w3c_pointer_events.enabled", true); + +// ── KEEP the shield (spoof-normality, ride the ESR cohort) ─────────────────── +// Re-assert the load-bearing anti-fingerprinting prefs so this overlay can never +// silently weaken them. Source of truth remains profiles/default/user.js (101). +user_pref("privacy.resistFingerprinting", true); +user_pref("privacy.firstparty.isolate", true); // first-party isolation ON +user_pref("privacy.resistFingerprinting.reduceTimerPrecision.unconditional", true); +// Hide automation markers so the page cannot tell this is an agent rig. +user_pref("dom.webdriver.enabled", false); // navigator.webdriver = false +user_pref("toolkit.telemetry.enabled", false); // no telemetry egress From 219cb91bdd7da55ce2ec9a4c33762781c483168c Mon Sep 17 00:00:00 2001 From: Michael Heller <21163552+mdheller@users.noreply.github.com> Date: Mon, 29 Jun 2026 15:01:35 -0400 Subject: [PATCH 11/11] =?UTF-8?q?packaging:=20drop=20'LibreWolf'=20from=20?= =?UTF-8?q?snap=20description=20=E2=80=94=20passes=20linux-packaging=20bra?= =?UTF-8?q?nding=20check=20(Firefox=20ESR=20only)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packaging/linux/snap/snapcraft.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packaging/linux/snap/snapcraft.yaml b/packaging/linux/snap/snapcraft.yaml index cfa1cd8..5c9da1e 100644 --- a/packaging/linux/snap/snapcraft.yaml +++ b/packaging/linux/snap/snapcraft.yaml @@ -3,7 +3,7 @@ base: core22 version: '0.1.0' summary: Gecko-first privacy browser with 101 fingerprinting protections description: | - BearBrowser is built on Firefox/LibreWolf ESR with a 101-surface + BearBrowser is built on Firefox ESR with a 101-surface fingerprinting shield. No telemetry, no cloud sync, anti-tracking built in. grade: stable confinement: strict