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 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/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 41a0f85..4defa9e 100644 --- a/mounts/agent-browser-mounts.yaml +++ b/mounts/agent-browser-mounts.yaml @@ -1,23 +1,138 @@ +# ============================================================================= +# 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 @@ -31,7 +146,7 @@ spec: - activate - 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 @@ -43,7 +158,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 @@ -84,16 +199,33 @@ spec: - activate - 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 @@ -103,22 +235,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/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/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..fa9a237 --- /dev/null +++ b/packaging/chocolatey/bearbrowser/tools/chocolateyInstall.ps1 @@ -0,0 +1,228 @@ +$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 (Windows) +// Canonical: profiles/default/user.js (edit there, not here) + +// ── 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 (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); + +// ── 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); +'@ +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" 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 index a61a164..29ed492 100644 --- a/packaging/linux/deb/control +++ b/packaging/linux/deb/control @@ -4,8 +4,11 @@ Section: web Priority: optional Architecture: amd64 Maintainer: SourceOS +Depends: libgtk-3-0, libdbus-glib-1-2, libx11-6, libxcomposite1, libxdamage1, libxext6, libxfixes3, libxrandr2, libasound2 Homepage: https://github.com/SourceOS-Linux/BearBrowser Description: SourceOS governed browser for humans and agents BearBrowser is a SourceOS governed browser with separate human-secure and agent-runtime profiles, policy-mediated automation surfaces, and workspace - integration contracts. + integration contracts. Built on Firefox ESR with a 101-surface fingerprinting + shield (Canvas, WebGL, AudioContext, Fonts, Timing), OS-spoof normalization, + and no telemetry / no cloud dependency. diff --git a/packaging/linux/deb/postinst b/packaging/linux/deb/postinst new file mode 100644 index 0000000..c2a31a2 --- /dev/null +++ b/packaging/linux/deb/postinst @@ -0,0 +1,173 @@ +#!/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 (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); +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 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"] +} diff --git a/packaging/linux/snap/snapcraft.yaml b/packaging/linux/snap/snapcraft.yaml new file mode 100644 index 0000000..5c9da1e --- /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 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 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 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); 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 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