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/10] 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/10] 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/10] 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/10] 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/10] 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/10] 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/10] 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/10] 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/10] 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/10] 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