Security hardening and release readiness
ober
7118a9c32951e677e8136841a5eafa9b846c9b39
--- a/.jerboa/security.json +++ b/.jerboa/security.json @@ -15,6 +15,7 @@ "ffi": { "allowed": true, "requireLazyLoading": true, "requireBoundaryDocs": true }, "browser": { "chromiumSandbox": "required", + "targetSandboxProof": "marker-validated-fail-closed", "defaultProfile": "off-record", "persistentProfileRequiresEncryptedVault": true, "envPassphrase": "deny" --- a/Makefile +++ b/Makefile @@ -3,8 +3,11 @@ # release toolchain; developer machines can still override JERBUILD explicitly. JERBOA_VERSION ?= v0.2.3 JERBOA_TOOL_DIR ?= $(CURDIR)/.jerboa/bin +JERBOA_SIBLING_TOOL_DIR ?= $(CURDIR)/../jerboa/dist JERBUILD ?= $(shell if [ -x "$(JERBOA_TOOL_DIR)/jerbuild" ] && [ -x "$(JERBOA_TOOL_DIR)/jerboa" ]; then \ printf '%s\n' "$(JERBOA_TOOL_DIR)/jerbuild"; \ +elif [ -x "$(JERBOA_SIBLING_TOOL_DIR)/jerbuild" ] && [ -x "$(JERBOA_SIBLING_TOOL_DIR)/jerboa" ]; then \ + printf '%s\n' "$(JERBOA_SIBLING_TOOL_DIR)/jerbuild"; \ elif command -v jerbuild >/dev/null 2>&1 && command -v jerboa >/dev/null 2>&1; then \ command -v jerbuild; \ else \ @@ -17,7 +20,12 @@ JERBOA_YUBIKEY_LIB := $(JERBOA_YUBIKEY_DIR)/lib JEXEC := $(JERBUILD) exec --libdirs $(CURDIR)/$(JERBOA_YUBIKEY_LIB):$(CURDIR)/scheme:$(CURDIR)/vendor/jerboa-fuse/lib:$(JH)/lib JERBUILD_ERROR := "ERROR: jerbuild unavailable (or '$(JERBUILD) --jerboa-home' failed). Run make ensure-jerboa-tools, install jerbuild, or set JERBUILD=/path/to/jerbuild." BIN_DIR ?= $(HOME)/.local/bin +BIN ?= jerboa-browser DIST_DIR ?= dist/release-evidence +SBOM_DIR ?= dist/sbom +REPRO_DIR ?= dist/reproducibility +BINARY_SMOKE_DIR ?= dist/binary-smoke +SANDBOX_DIR ?= dist/webengine-sandbox CC ?= cc CARGO ?= cargo @@ -71,7 +79,7 @@ lib/libyubikey_native.$(SOEXT): vendor-yubikey yubikey-native: lib/libyubikey_native.$(SOEXT) -.PHONY: all build binary install run test test-all test-keymap test-minibuffer test-commands test-keys test-nav test-hint test-pass test-polish test-securestore test-adblock test-gui test-buffers test-native repl security audit verify release-evidence clean help check-jerbuild check-docker ensure-jerboa-tools static-qt docker-static-qt vendor-yubikey yubikey-native +.PHONY: all build binary install run test test-all test-keymap test-minibuffer test-commands test-keys test-nav test-hint test-pass test-polish test-securestore test-adblock test-gui test-buffers test-native repl security audit sbom reproducibility-report binary-smoke webengine-sandbox-evidence verify release-evidence clean help check-jerbuild check-docker ensure-jerboa-tools static-qt docker-static-qt vendor-yubikey yubikey-native .DEFAULT_GOAL := help all: binary @@ -82,6 +90,8 @@ check-jerbuild: ensure-jerboa-tools: @if [ -x "$(JERBOA_TOOL_DIR)/jerbuild" ] && [ -x "$(JERBOA_TOOL_DIR)/jerboa" ]; then \ echo "=== Using downloaded Jerboa toolchain: $(JERBOA_TOOL_DIR) ==="; \ + elif [ -x "$(JERBOA_SIBLING_TOOL_DIR)/jerbuild" ] && [ -x "$(JERBOA_SIBLING_TOOL_DIR)/jerboa" ]; then \ + echo "=== Using sibling Jerboa release toolchain: $(JERBOA_SIBLING_TOOL_DIR) ==="; \ elif command -v jerbuild >/dev/null 2>&1 && command -v jerboa >/dev/null 2>&1; then \ echo "=== Using Jerboa toolchain from PATH: $$(command -v jerbuild) ==="; \ else \ @@ -214,9 +224,42 @@ audit: echo "cargo-audit not installed or YubiKey native Cargo.toml missing; release owner must review before publication." > "$(DIST_DIR)/rustsec-yubikey-native.txt"; \ fi +sbom: + JBROWSER_SBOM_DIR="$(SBOM_DIR)" \ + JERBUILD="$(JERBUILD)" \ + BIN="$(BIN)" \ + CARGO="$(CARGO)" \ + STATIC_QT_TARBALL="$(STATIC_QT_TARBALL)" \ + FUSE_SHIM="$(FUSE_SHIM)" \ + sh scripts/sbom.sh + +reproducibility-report: + JBROWSER_REPRO_DIR="$(REPRO_DIR)" \ + JERBUILD="$(JERBUILD)" \ + BIN="$(BIN)" \ + sh scripts/reproducibility-report.sh + +binary-smoke: + @if [ ! -x "./$(BIN)" ]; then $(MAKE) --no-print-directory binary; fi + JBROWSER_BINARY_SMOKE_DIR="$(BINARY_SMOKE_DIR)" \ + BIN="$(BIN)" \ + sh scripts/binary-smoke.sh + +webengine-sandbox-evidence: + JBROWSER_SANDBOX_DIR="$(SANDBOX_DIR)" \ + JBROWSER_RUN_WEBENGINE_PROCESS_SMOKE="$(JBROWSER_RUN_WEBENGINE_PROCESS_SMOKE)" \ + JBROWSER_WEBENGINE_PROCESS_SMOKE_MS="$(JBROWSER_WEBENGINE_PROCESS_SMOKE_MS)" \ + JBROWSER_WEBENGINE_PROCESS_SMOKE_URL="$(JBROWSER_WEBENGINE_PROCESS_SMOKE_URL)" \ + JBROWSER_WEBENGINE_PROCESS_SMOKE_SAMPLES="$(JBROWSER_WEBENGINE_PROCESS_SMOKE_SAMPLES)" \ + JBROWSER_REQUIRE_WEBENGINE_PROCESS_SMOKE="$(JBROWSER_REQUIRE_WEBENGINE_PROCESS_SMOKE)" \ + JBROWSER_TARGET_SANDBOX_PROOF_FILE="$(JBROWSER_TARGET_SANDBOX_PROOF_FILE)" \ + JBROWSER_REQUIRE_TARGET_SANDBOX_PROOF="$(JBROWSER_REQUIRE_TARGET_SANDBOX_PROOF)" \ + BIN="$(BIN)" \ + sh scripts/webengine-sandbox-evidence.sh + verify: test-all security audit -release-evidence: verify binary +release-evidence: verify reproducibility-report sbom webengine-sandbox-evidence binary-smoke @rm -rf "$(DIST_DIR)" @mkdir -p "$(DIST_DIR)" @git status --short > "$(DIST_DIR)/git-status.txt" @@ -233,12 +276,12 @@ release-evidence: verify binary @QT_QPA_PLATFORM=offscreen QTWEBENGINE_CHROMIUM_FLAGS=--disable-gpu ./jerboa-browser version > "$(DIST_DIR)/browser-version.txt" 2>&1 || true @find scheme qt-webengine/src qt-webengine/tests include support packaging -type f \( -name '*.ss' -o -name '*.sls' -o -name '*.c' -o -name '*.cc' -o -name '*.cpp' -o -name '*.h' -o -name '*.sh' -o -name '*.md' -o -name 'CMakeLists.txt' \) -print | sort | xargs shasum -a 256 > "$(DIST_DIR)/source-sha256.txt" @find qt-webengine/vendor/adblock-rust-ffi "$(JERBOA_YUBIKEY_DIR)/yubikey-native" -maxdepth 2 -type f \( -name Cargo.toml -o -name Cargo.lock \) -print 2>/dev/null | sort | xargs shasum -a 256 > "$(DIST_DIR)/native-cargo-sha256.txt" - @shasum -a 256 Makefile .jerbuild .jerboa/security.json SECURITY.md docs/threat-model.md docs/credential-handling.md docs/ffi-boundary.md docs/webengine-sandbox.md docs/release-evidence.md scripts/security-check.sh > "$(DIST_DIR)/release-inputs-sha256.txt" - @shasum -a 256 jerboa-browser > "$(DIST_DIR)/binary-sha256.txt" + @shasum -a 256 Makefile .jerbuild .jerboa/security.json SECURITY.md docs/threat-model.md docs/credential-handling.md docs/ffi-boundary.md docs/webengine-sandbox.md docs/release-evidence.md scripts/security-check.sh scripts/sbom.sh scripts/reproducibility-report.sh scripts/binary-smoke.sh scripts/webengine-sandbox-evidence.sh > "$(DIST_DIR)/release-inputs-sha256.txt" + @shasum -a 256 "$(BIN)" > "$(DIST_DIR)/binary-sha256.txt" @if command -v otool >/dev/null 2>&1; then \ - { otool -L jerboa-browser 2>/dev/null || true; otool -L qt-webengine/build/libjerboa_browser.* 2>/dev/null || true; otool -L lib/libyubikey_native.* 2>/dev/null || true; } > "$(DIST_DIR)/native-linkage.txt"; \ + { otool -L "$(BIN)" 2>/dev/null || true; otool -L qt-webengine/build/libjerboa_browser.* 2>/dev/null || true; otool -L lib/libyubikey_native.* 2>/dev/null || true; } > "$(DIST_DIR)/native-linkage.txt"; \ elif command -v ldd >/dev/null 2>&1; then \ - { ldd jerboa-browser 2>/dev/null || true; ldd qt-webengine/build/libjerboa_browser.* 2>/dev/null || true; ldd lib/libyubikey_native.* 2>/dev/null || true; } > "$(DIST_DIR)/native-linkage.txt"; \ + { ldd "$(BIN)" 2>/dev/null || true; ldd qt-webengine/build/libjerboa_browser.* 2>/dev/null || true; ldd lib/libyubikey_native.* 2>/dev/null || true; } > "$(DIST_DIR)/native-linkage.txt"; \ else \ echo "no otool/ldd available" > "$(DIST_DIR)/native-linkage.txt"; \ fi @@ -258,6 +301,17 @@ release-evidence: verify binary else \ echo "cargo-audit not installed or YubiKey native Cargo.toml missing; release owner must review before publication." > "$(DIST_DIR)/rustsec-yubikey-native.txt"; \ fi + @rm -rf "$(DIST_DIR)/sbom" "$(DIST_DIR)/reproducibility" "$(DIST_DIR)/binary-smoke" "$(DIST_DIR)/webengine-sandbox" + @cp -R "$(SBOM_DIR)" "$(DIST_DIR)/sbom" + @cp -R "$(REPRO_DIR)" "$(DIST_DIR)/reproducibility" + @cp -R "$(BINARY_SMOKE_DIR)" "$(DIST_DIR)/binary-smoke" + @cp -R "$(SANDBOX_DIR)" "$(DIST_DIR)/webengine-sandbox" + @grep -q '^status=match$$' "$(DIST_DIR)/reproducibility/result.txt" + @grep -q '^status=pass$$' "$(DIST_DIR)/binary-smoke/status.txt" + @if grep -q '^status=failed-current$$' "$(DIST_DIR)/webengine-sandbox/status.txt"; then \ + echo "current WebEngine sandbox evidence failed; see $(DIST_DIR)/webengine-sandbox/status.txt"; \ + exit 1; \ + fi @echo "release evidence written to $(DIST_DIR)" # REPL with (browser) preloaded, in the interpreter. @@ -323,6 +377,10 @@ help: @echo " make install build + install to $(BIN_DIR)/jerboa-browser" @echo " make static-qt build the bundled Linux Qt/WebEngine tarball via Docker" @echo " make verify run full tests, security checks, and dependency audit" + @echo " make sbom write SBOM/provenance evidence under dist/sbom" + @echo " make reproducibility-report rebuild the browser binary twice" + @echo " make binary-smoke start the binary through offscreen version" + @echo " make webengine-sandbox-evidence record WebEngine sandbox status" @echo " make release-evidence write release evidence under dist/release-evidence" @echo " make vendor-yubikey clone jerboa-yubikey into vendor/" @echo " make yubikey-native build lib/libyubikey_native for direct USB" --- a/SECURITY.md +++ b/SECURITY.md @@ -13,11 +13,21 @@ requires: - `make release-evidence` produces a complete `dist/release-evidence/` bundle. - `jerboa_security_scan` reports no medium-or-higher findings in non-vendored production source. +- SBOM/provenance, repeated binary reproducibility, no-secret binary-smoke, and + WebEngine sandbox-status evidence are present in the release bundle. - The Qt WebEngine backend builds and its native tests pass on every supported target OS. - RustSec/native dependency audits are clean or documented as accepted release risks. -- Target-OS evidence confirms the Chromium/Qt WebEngine sandbox is enabled. +- Target-OS evidence confirms the Chromium/Qt WebEngine sandbox is enabled. Run + WebEngine evidence with `JBROWSER_RUN_WEBENGINE_PROCESS_SMOKE=1`, + `JBROWSER_REQUIRE_WEBENGINE_PROCESS_SMOKE=1`, + `JBROWSER_TARGET_SANDBOX_PROOF_FILE=/path/to/target-sandbox-proof.txt`, and + `JBROWSER_REQUIRE_TARGET_SANDBOX_PROOF=1` in the release environment. Required + target proof is marker-validated for observed helper runtime, absence of + sandbox-disable args, target OS sandbox inspection, and production sandbox + status; missing or incomplete proof fails closed with + `status=blocked-target-proof`. ## Security Expectations --- a/docs/release-evidence.md +++ b/docs/release-evidence.md @@ -6,8 +6,49 @@ Run: make release-evidence ``` -The target runs `make verify`, builds `./jerboa-browser`, and writes evidence to -`dist/release-evidence/`. +The target runs `make verify`, refreshes reproducibility/SBOM/sandbox/binary +smoke evidence, and writes evidence to `dist/release-evidence/`. + +`make release-evidence` also runs: + +- `make reproducibility-report`, which rebuilds `./jerboa-browser` twice using a + fixed Jerbuild object directory without invoking the destructive `make clean` + target after an unmeasured warmup and stabilization build. It compares the + shipped binary, generated Jerbuild inputs, native runtime inputs, and source + manifest. +- `make sbom`, which records source/release input hashes, Qt WebEngine version + status, native runtime hashes, Cargo metadata, static Qt tarball status, and + helper-process presence when visible on the local platform. +- `make binary-smoke`, which starts the shipped binary through the no-secret + offscreen `version` path and records macOS code-signature status when + available. +- `make webengine-sandbox-evidence`, which records source-policy status, Qt + version status, helper-process presence, optional helper-process smoke, and + explicit target-host sandbox blockers. + +For release-host WebEngine evidence, run: + +```sh +JBROWSER_RUN_WEBENGINE_PROCESS_SMOKE=1 \ +JBROWSER_REQUIRE_WEBENGINE_PROCESS_SMOKE=1 \ +JBROWSER_TARGET_SANDBOX_PROOF_FILE=/path/to/target-sandbox-proof.txt \ +JBROWSER_REQUIRE_TARGET_SANDBOX_PROOF=1 \ +make webengine-sandbox-evidence +``` + +The process smoke uses `jerboa-browser browse about:blank` with +`JWB_BROWSE_MS=6000`, records `QtWebEngineProcess` argv, rejects `--no-sandbox`, +and writes `process_smoke_status`, `qtwebengineprocess_argv_status`, +`platform_sandbox_indicator_status`, `target_sandbox_proof_status`, +`target_os_sandbox_status`, and `production_sandbox_status` into +`webengine-sandbox/status.txt`. Target proof files must include +`target_os_sandbox_status=target-evidence-recorded`, +`target_process_sandbox_status=target-evidence-recorded`, +`qtwebengineprocess_runtime_status=observed`, +`qtwebengineprocess_argv_status=no-sandbox-disable-arg-absent`, +`platform_sandbox_indicator_status=target-sandbox-indicator-recorded`, and +`production_sandbox_status=target-evidence-recorded`; missing or incomplete +required target proof exits with `status=blocked-target-proof`. Expected files include: @@ -22,6 +63,13 @@ Expected files include: - Cargo metadata for adblock and YubiKey native crates when available - RustSec audit outputs for adblock and YubiKey native crates when `cargo-audit` is installed +- `sbom/` +- `reproducibility/` +- `binary-smoke/` +- `webengine-sandbox/` +- `webengine-sandbox/target-sandbox-proof.txt` and + `webengine-sandbox/target-sandbox-proof.txt.sha256` when target proof is + supplied Release owners must treat missing `cargo-audit`, skipped Qt native tests, or missing target-OS sandbox/process evidence as production blockers, even when the --- a/docs/threat-model.md +++ b/docs/threat-model.md @@ -71,4 +71,10 @@ vendored YubiKey/FUSE dependencies. - Missing project release evidence or dependency audit output. - Hosted CI that cannot build the Qt backend and run the browser verification suite. -- No target-OS smoke evidence for sandboxed WebEngine helper processes. +- No target-OS smoke evidence for sandboxed WebEngine helper processes. Release + hosts must record `QtWebEngineProcess` argv and native sandbox inspection + output through the marker-validated `JBROWSER_TARGET_SANDBOX_PROOF_FILE`; + missing or incomplete required proof fails closed as + `status=blocked-target-proof`. +- No repeated binary reproducibility, SBOM/provenance, or no-secret binary + startup evidence for the release candidate. --- a/docs/webengine-sandbox.md +++ b/docs/webengine-sandbox.md @@ -36,6 +36,51 @@ Release evidence must record: - Linkage for `jerboa-browser`, `libjerboa_browser`, `libyubikey_native`, and FUSE helper libraries when present. - Confirmation that sandbox-disable knobs are absent from release source. +- `dist/release-evidence/webengine-sandbox/status.txt`, with process-smoke and + target sandbox proof statuses. -Target operating systems still need live sandbox/process evidence before a -public production claim. +By default, the sandbox evidence target records source policy, local Qt +versions, helper presence, and explicit target-proof blockers. On a host with a +built browser and Qt WebEngine runtime, release owners can also run a hermetic +helper-process smoke: + +```sh +JBROWSER_RUN_WEBENGINE_PROCESS_SMOKE=1 \ +JBROWSER_REQUIRE_WEBENGINE_PROCESS_SMOKE=1 \ +make webengine-sandbox-evidence +``` + +That smoke launches `jerboa-browser browse about:blank` with +`JWB_BROWSE_MS=6000`, observes a direct `QtWebEngineProcess` child, records its +argv in `qtwebengineprocess-argv.txt`, and fails if the helper argv contains +`--no-sandbox` or `QTWEBENGINE_DISABLE_SANDBOX`. On Darwin it also records the +Chromium `--seatbelt-client=` argv marker as a local sandbox indicator. This is +not, by itself, a production sandbox claim. + +For release-host evidence, capture the target OS's native sandbox proof and pass +it into the evidence bundle: + +```sh +JBROWSER_RUN_WEBENGINE_PROCESS_SMOKE=1 \ +JBROWSER_REQUIRE_WEBENGINE_PROCESS_SMOKE=1 \ +JBROWSER_TARGET_SANDBOX_PROOF_FILE=/path/to/target-sandbox-proof.txt \ +JBROWSER_REQUIRE_TARGET_SANDBOX_PROOF=1 \ +make webengine-sandbox-evidence +``` + +`JBROWSER_TARGET_SANDBOX_PROOF_FILE` should contain the platform-specific proof +used by the release owner, for example target process listings plus OS sandbox +inspection output. The proof is marker-validated and must contain: + +- `target_os_sandbox_status=target-evidence-recorded` +- `target_process_sandbox_status=target-evidence-recorded` +- `qtwebengineprocess_runtime_status=observed` +- `qtwebengineprocess_argv_status=no-sandbox-disable-arg-absent` +- `platform_sandbox_indicator_status=target-sandbox-indicator-recorded` +- `production_sandbox_status=target-evidence-recorded` + +Missing, empty, or marker-incomplete required proofs fail closed with +`status=blocked-target-proof`. Accepted target proofs are copied into the +evidence directory as `target-sandbox-proof.txt` with a SHA-256 sidecar. Target +operating systems still need live sandbox/process evidence before a public +production claim. --- a/jerboa-browser.md +++ b/jerboa-browser.md @@ -360,7 +360,7 @@ recommended next action in `browser/notes/qt-webengine-pin.md`. Goal: prove the local machine can build and run Qt WebEngine. -Result: PASS on macOS 15.5 arm64, Qt 6.11.0 (Homebrew). Probe loaded +Result: PASS on macOS/Darwin arm64, Qt 6.11.1 (Homebrew). Probe loaded `https://example.com` and `runJavaScript("document.title")` returned `"Example Domain"` with the sandbox enabled. Full host facts in `notes/qt-webengine-pin.md`. @@ -405,7 +405,7 @@ Result: PASS. `include/jerboa_browser.h` (full C ABI), `qt-webengine/` (`CMakeLists.txt`, `src/browser_internal.h`, `src/browser_abi.cpp`, `src/main_demo.cpp`), `README.md` created. `cmake --build` produces `libjerboa_browser.dylib` + `jwb_demo`; demo prints -`jerboa-browser 0.0.1 (Qt WebEngine 6.11.0)` and `runtime initialized OK` +`jerboa-browser 0.0.1 (Qt WebEngine 6.11.1)` and `runtime initialized OK` (exit 0). `jwb_runtime_init` makes the single QApplication (reuses an existing one for the Ticket 2.2 host case). @@ -744,11 +744,11 @@ engine, widget, JavaScript bridge, and process model already exist. ## Open blanks to fill during implementation -- Exact Qt version: **6.11.0** (Homebrew) -- Exact Qt installation path: **/opt/homebrew** (kegs under `/opt/homebrew/Cellar/qt*/6.11.0`); pass `-DCMAKE_PREFIX_PATH=/opt/homebrew` +- Exact Qt version: **6.11.1** (Homebrew) +- Exact Qt installation path: **/opt/homebrew** (kegs under `/opt/homebrew/Cellar/qt*/6.11.1`); pass `-DCMAKE_PREFIX_PATH=/opt/homebrew` - Exact required Qt modules: CMake components **Core, Widgets, WebEngineWidgets** (+ transitive WebEngineCore, Quick, Gui, Network, WebChannel, Positioning) - Exact CMake package names: `find_package(Qt6 REQUIRED COMPONENTS Core Widgets WebEngineWidgets)` -- Exact `QtWebEngineProcess` bundle path: **inside the framework** — `/opt/homebrew/Cellar/qtwebengine/6.11.0/lib/QtWebEngineCore.framework/Versions/A/Helpers/QtWebEngineProcess.app` (Chromium `.pak`s in the same framework's `Resources/`) +- Exact `QtWebEngineProcess` bundle path: **inside the framework** — `/opt/homebrew/Cellar/qtwebengine/6.11.1/lib/QtWebEngineCore.framework/Versions/A/Helpers/QtWebEngineProcess.app` (Chromium `.pak`s in the same framework's `Resources/`) - Exact macOS signing/notarization requirements: TBD (Ticket 3.1) - Exact Linux package format: TBD (not this host; macOS-first) - Exact policy hooks available in this Qt version: TBD (Ticket 1.3) --- a/notes/qt-webengine-pin.md +++ b/notes/qt-webengine-pin.md @@ -7,13 +7,15 @@ the host or Qt version changes, or if a stop condition is later hit. **Status: PASS** — probe built and ran; `runJavaScript("document.title")` returned `"Example Domain"` from `https://example.com`; exit code 0; Chromium sandbox left enabled (no `QTWEBENGINE_DISABLE_SANDBOX`). No stop condition hit. -Recorded 2026-05-24. +Recorded 2026-05-24 and refreshed 2026-06-22 after the Homebrew Qt 6.11.1 +upgrade. Current `pkg-config` and browser runtime evidence report Qt +WebEngine 6.11.1. ## Host -- OS: macOS 15.5 (build 24F74) +- OS: macOS/Darwin host (current release evidence: Darwin 25.5.0) - Arch: arm64 (Apple Silicon, `RELEASE_ARM64_T6041`) -- Kernel: Darwin 24.5.0 +- Kernel: Darwin 25.5.0 ## Toolchain @@ -23,7 +25,7 @@ Recorded 2026-05-24. ## Qt -- Qt version: **6.11.0**, installed via Homebrew at `/opt/homebrew` +- Qt version: **6.11.1**, installed via Homebrew at `/opt/homebrew` - Install kind: Homebrew split kegs (NOT a monolithic Qt installer tree) - CMake package entry point: `/opt/homebrew/lib/cmake/Qt6/Qt6Config.cmake` (component configs symlinked from the per-keg dirs; a plain `find` over @@ -37,7 +39,7 @@ Recorded 2026-05-24. `WebEngineWidgets` transitively pulls `WebEngineCore`, `Quick`, `Gui`, `Network`, `WebChannel`, `Positioning`. Backing Homebrew formulae present: `qtbase`, `qtdeclarative`, `qtwebchannel`, `qtwebengine`, `qtpositioning` -(all 6.11.0). +(all 6.11.1 via `pkg-config`). ## Runtime layout (macOS framework bundle — matters for packaging, Ticket 3.1) @@ -45,7 +47,7 @@ On macOS, Qt WebEngine ships as a **framework**, not loose libs. The helper process and Chromium resources live INSIDE the framework: - `QtWebEngineProcess` (the separate render/JS process): - `/opt/homebrew/Cellar/qtwebengine/6.11.0/lib/QtWebEngineCore.framework/Versions/A/Helpers/QtWebEngineProcess.app/Contents/MacOS/QtWebEngineProcess` + `/opt/homebrew/Cellar/qtwebengine/6.11.1/lib/QtWebEngineCore.framework/Versions/A/Helpers/QtWebEngineProcess.app/Contents/MacOS/QtWebEngineProcess` - has an `.entitlements` file → macOS sandbox is applied to the helper. - Chromium `.pak` resources + locales: `.../QtWebEngineCore.framework/Versions/A/Resources/` --- a/packaging/README.md +++ b/packaging/README.md @@ -19,12 +19,12 @@ any still references `/opt/homebrew`. ### Verified acceptance -Built on macOS 15 (Apple Silicon), Qt 6.11.0. Copied to a fresh `mktemp -d` +Built on macOS/Darwin Apple Silicon, Qt 6.11.1. Copied to a fresh `mktemp -d` with no `/opt/homebrew` on the search path and launched directly: ``` $ JWB_HOST_SMOKE=1 JerboaBrowser.app/Contents/MacOS/JerboaBrowser -jerboa-browser 0.0.1 (Qt WebEngine 6.11.0) +jerboa-browser 0.0.1 (Qt WebEngine 6.11.1) [PASS] runtime init reuses host QApplication [PASS] no second QApplication created [PASS] show browser pane new file mode 100755 --- /dev/null +++ b/scripts/binary-smoke.sh @@ -0,0 +1,66 @@ +#!/bin/sh +set -eu + +ROOT=$(cd "$(dirname "$0")/.." && pwd) +cd "$ROOT" + +OUT=${JBROWSER_BINARY_SMOKE_DIR:-${BINARY_SMOKE_DIR:-dist/binary-smoke}} +BIN=${BIN:-jerboa-browser} +PLATFORM=$(uname -s) +REQUIRE_CODESIGN=${JBROWSER_REQUIRE_CODESIGN:-0} + +rm -rf "$OUT" +mkdir -p "$OUT" + +write_status() { + status=$1 + version_status=$2 + exit_code=$3 + codesign_status=$4 + reason=$5 + { + echo "repo=jerboa-browser" + echo "platform=$PLATFORM" + echo "status=$status" + echo "offscreen_version_status=$version_status" + echo "version_exit_code=$exit_code" + echo "codesign_status=$codesign_status" + echo "reason=$reason" + } > "$OUT/status.txt" +} + +if [ ! -x "./$BIN" ]; then + write_status "missing-binary" "missing-binary" "not-run" "not-run" "$BIN is not present or executable" + exit 1 +fi + +set +e +QT_QPA_PLATFORM=offscreen QTWEBENGINE_CHROMIUM_FLAGS=--disable-gpu "./$BIN" version > "$OUT/version.stdout" 2> "$OUT/version.stderr" +rc=$? +set -e + +version_status=fail +if [ "$rc" -eq 0 ] && grep -q 'jerboa-browser' "$OUT/version.stdout" "$OUT/version.stderr" 2>/dev/null; then + version_status=pass +fi + +codesign_status=not-applicable +if [ "$PLATFORM" = "Darwin" ] && command -v codesign >/dev/null 2>&1; then + if codesign --verify "./$BIN" > "$OUT/codesign.stdout" 2> "$OUT/codesign.stderr"; then + codesign_status=pass + else + codesign_status=blocked-not-signed + fi +fi + +if [ "$version_status" != "pass" ]; then + write_status "fail" "$version_status" "$rc" "$codesign_status" "binary did not start through the no-secret offscreen version path" + exit 1 +fi + +if [ "$REQUIRE_CODESIGN" = "1" ] && [ "$codesign_status" = "blocked-not-signed" ]; then + write_status "fail" "$version_status" "$rc" "$codesign_status" "codesign verification is required for this release host" + exit 1 +fi + +write_status "pass" "$version_status" "$rc" "$codesign_status" "binary reached no-secret offscreen version path" new file mode 100755 --- /dev/null +++ b/scripts/reproducibility-report.sh @@ -0,0 +1,241 @@ +#!/bin/sh +set -eu + +ROOT=$(cd "$(dirname "$0")/.." && pwd) +REPRO_DIR=${JBROWSER_REPRO_DIR:-${REPRO_DIR:-dist/reproducibility}} +MAKE_CMD=${MAKE:-make} +JERBUILD=${JERBUILD:-jerbuild} +BIN=${BIN:-jerboa-browser} +TMP_PARENT=${JBROWSER_REPRO_TMPDIR:-${TMPDIR:-/tmp}} +TMP_DIR=$TMP_PARENT/jerboa-browser-repro-$$ +OBJ_DIR=$TMP_DIR/jerbuild-binary-browser +WARMUP_OBJ_DIR=$TMP_DIR/jerbuild-binary-browser-warmup + +cleanup() { + rm -rf "$TMP_DIR" +} +trap cleanup EXIT HUP INT TERM + +cd "$ROOT" +rm -rf "$TMP_DIR" +mkdir -p "$TMP_DIR" + +sha256_file() { + if command -v sha256sum >/dev/null 2>&1; then + sha256sum "$1" + else + shasum -a 256 "$1" + fi +} + +source_manifest() { + for path in \ + .build.yml \ + .dockerignore \ + .gitignore \ + .jerbuild \ + .jerboa/security.json \ + Dockerfile.qt \ + LICENSE \ + Makefile \ + README.md \ + SECURITY.md \ + build-binary.ss \ + docs \ + scripts \ + support \ + packaging \ + include \ + scheme \ + .github \ + qt-webengine/CMakeLists.txt \ + qt-webengine/src \ + qt-webengine/tests \ + qt-webengine/host \ + qt-webengine/probe \ + qt-webengine/vendor/adblock-rust-ffi/Cargo.toml \ + qt-webengine/vendor/adblock-rust-ffi/Cargo.lock \ + qt-webengine/vendor/adblock-rust-ffi/src \ + vendor/jerboa-yubikey/lib \ + vendor/jerboa-yubikey/yubikey-native/Cargo.toml \ + vendor/jerboa-yubikey/yubikey-native/Cargo.lock \ + vendor/jerboa-yubikey/yubikey-native/src \ + vendor/jerboa-fuse/lib + do + if [ -f "$path" ]; then + printf '%s\n' "$path" + elif [ -d "$path" ]; then + find "$path" -type f \ + ! -path '*/.git/*' \ + ! -path '*/dist/*' \ + ! -path '*/.jerboa/bin/*' \ + ! -path '*/target/*' \ + ! -path '*/build/*' \ + ! -name '*.so' \ + ! -name '*.dylib' \ + ! -name '*.o' \ + ! -name '*.boot' \ + ! -name '*.wpo' \ + ! -name '*.wp.so' + fi + done | LC_ALL=C sort -u | while IFS= read -r file; do + sha256_file "$file" + done +} + +hash_generated_inputs() { + label=$1 + manifest="$TMP_DIR/$label-generated-inputs.sha256" + if [ -d "$OBJ_DIR" ]; then + ( + cd "$OBJ_DIR" + find . -maxdepth 1 -type f \ + ! -name "$BIN" \ + -print | LC_ALL=C sort | while IFS= read -r file; do + sha256_file "$file" + done + ) > "$manifest" + else + printf 'missing_obj_dir=%s\n' "$OBJ_DIR" > "$manifest" + fi +} + +native_runtime_manifest() { + out=$1 + for pattern in qt-webengine/build/libjerboa_browser.* lib/libjerboa_native.* lib/libyubikey_native.* libjerboa_fuse_mount.so; do + for file in $pattern; do + if [ -f "$file" ]; then + sha256_file "$file" + fi + done + done | LC_ALL=C sort > "$out" +} + +build_once() { + label=$1 + rm -f "$BIN" + rm -rf "$OBJ_DIR" + mkdir -p "$OBJ_DIR" + env \ + JERBUILD="$JERBUILD" \ + JERBUILD_BINARY_OBJ_DIR="$OBJ_DIR" \ + JERBUILD_BINARY_KEEP_OBJ_DIR=1 \ + "$MAKE_CMD" --no-print-directory binary > "$TMP_DIR/$label-build.log" 2>&1 + test -f "$BIN" + cp "$BIN" "$TMP_DIR/$label-$BIN" + sha256_file "$TMP_DIR/$label-$BIN" > "$TMP_DIR/$label-binary.sha256" + hash_generated_inputs "$label" + native_runtime_manifest "$TMP_DIR/$label-native-runtime.sha256" +} + +warmup_once() { + rm -f "$BIN" + rm -rf "$WARMUP_OBJ_DIR" + mkdir -p "$WARMUP_OBJ_DIR" + env \ + JERBUILD="$JERBUILD" \ + JERBUILD_BINARY_OBJ_DIR="$WARMUP_OBJ_DIR" \ + JERBUILD_BINARY_KEEP_OBJ_DIR=1 \ + "$MAKE_CMD" --no-print-directory binary > "$TMP_DIR/warmup-build.log" 2>&1 + test -f "$BIN" + sha256_file "$BIN" > "$TMP_DIR/warmup-binary.sha256" + rm -f "$BIN" +} + +stabilize_once() { + rm -f "$BIN" + rm -rf "$OBJ_DIR" + mkdir -p "$OBJ_DIR" + env \ + JERBUILD="$JERBUILD" \ + JERBUILD_BINARY_OBJ_DIR="$OBJ_DIR" \ + JERBUILD_BINARY_KEEP_OBJ_DIR=1 \ + "$MAKE_CMD" --no-print-directory binary > "$TMP_DIR/stabilization-build.log" 2>&1 + test -f "$BIN" + sha256_file "$BIN" > "$TMP_DIR/stabilization-binary.sha256" + rm -f "$BIN" + rm -rf "$OBJ_DIR" +} + +{ + echo "repo=jerboa-browser" + echo "jerbuild=$JERBUILD" + "$JERBUILD" --version 2>/dev/null || true + printf 'jerboa_home=' + "$JERBUILD" --jerboa-home 2>/dev/null || true + echo "bin=$BIN" +} > "$TMP_DIR/build-env.txt" + +warmup_once +stabilize_once + +source_manifest > "$TMP_DIR/source-before.sha256" + +build_once first +build_once second + +source_manifest > "$TMP_DIR/source-after.sha256" + +if cmp -s "$TMP_DIR/first-$BIN" "$TMP_DIR/second-$BIN"; then + binary_status=match +else + binary_status=mismatch +fi + +if cmp -s "$TMP_DIR/first-generated-inputs.sha256" "$TMP_DIR/second-generated-inputs.sha256"; then + generated_input_status=match +else + generated_input_status=mismatch +fi + +if cmp -s "$TMP_DIR/first-native-runtime.sha256" "$TMP_DIR/second-native-runtime.sha256"; then + native_runtime_input_status=match +else + native_runtime_input_status=mismatch +fi + +if cmp -s "$TMP_DIR/source-before.sha256" "$TMP_DIR/source-after.sha256"; then + source_manifest_status=match +else + source_manifest_status=mismatch +fi + +status=match +for value in "$binary_status" "$generated_input_status" "$native_runtime_input_status" "$source_manifest_status"; do + if [ "$value" != "match" ]; then + status=mismatch + fi +done + +{ + echo "repo=jerboa-browser" + echo "binary_status=$binary_status" + echo "generated_input_status=$generated_input_status" + echo "native_runtime_input_status=$native_runtime_input_status" + echo "source_manifest_status=$source_manifest_status" + echo "jerbuild_binary_obj_dir=$OBJ_DIR" + echo "jerbuild_binary_keep_obj_dir=1" + echo "warmup_build_used=1" + echo "warmup_jerbuild_binary_obj_dir=$WARMUP_OBJ_DIR" + echo "stabilization_build_used=1" + echo "measurement_mode=post-warm" + echo "destructive_clean_used=0" + echo "status=$status" +} > "$TMP_DIR/result.txt" + +if [ "$binary_status" != "match" ]; then + cmp -l "$TMP_DIR/first-$BIN" "$TMP_DIR/second-$BIN" \ + | sed -n '1,200p' > "$TMP_DIR/binary-byte-diff-sample.txt" || true +fi + +if [ "$generated_input_status" != "match" ]; then + diff -u "$TMP_DIR/first-generated-inputs.sha256" "$TMP_DIR/second-generated-inputs.sha256" \ + > "$TMP_DIR/generated-inputs.diff" || true +fi + +rm -rf "$REPRO_DIR" +mkdir -p "$REPRO_DIR" +cp -R "$TMP_DIR"/. "$REPRO_DIR"/ + +cat "$REPRO_DIR/result.txt" +[ "$status" = "match" ] new file mode 100755 --- /dev/null +++ b/scripts/sbom.sh @@ -0,0 +1,186 @@ +#!/bin/sh +set -eu + +ROOT=$(cd "$(dirname "$0")/.." && pwd) +cd "$ROOT" + +OUT=${JBROWSER_SBOM_DIR:-${SBOM_DIR:-dist/sbom}} +JERBUILD=${JERBUILD:-jerbuild} +BIN=${BIN:-jerboa-browser} +CARGO=${CARGO:-cargo} +STATIC_QT_TARBALL=${STATIC_QT_TARBALL:-jerboa-browser-static-qt-linux-amd64.tar.gz} +ADBLOCK_MANIFEST=${ADBLOCK_MANIFEST:-qt-webengine/vendor/adblock-rust-ffi/Cargo.toml} +YUBIKEY_MANIFEST=${YUBIKEY_MANIFEST:-vendor/jerboa-yubikey/yubikey-native/Cargo.toml} +FUSE_SHIM=${FUSE_SHIM:-libjerboa_fuse_mount.so} + +rm -rf "$OUT" +mkdir -p "$OUT" + +sha256_file() { + if command -v sha256sum >/dev/null 2>&1; then + sha256sum "$1" + else + shasum -a 256 "$1" + fi +} + +write_source_files() { + for dir in scheme include support packaging docs scripts .github qt-webengine/src qt-webengine/tests qt-webengine/host qt-webengine/probe qt-webengine/vendor/adblock-rust-ffi/src vendor/jerboa-yubikey/lib vendor/jerboa-fuse/lib; do + if [ -d "$dir" ]; then + find "$dir" -type f ! -path '*/.git/*' ! -path '*/target/*' ! -path '*/build/*' + fi + done + + for file in \ + .build.yml \ + .dockerignore \ + .gitignore \ + .jerbuild \ + .jerboa/security.json \ + Dockerfile.qt \ + LICENSE \ + Makefile \ + README.md \ + SECURITY.md \ + build-binary.ss \ + qt-webengine/CMakeLists.txt \ + qt-webengine/vendor/adblock-rust-ffi/Cargo.toml \ + qt-webengine/vendor/adblock-rust-ffi/Cargo.lock \ + vendor/jerboa-yubikey/yubikey-native/Cargo.toml \ + vendor/jerboa-yubikey/yubikey-native/Cargo.lock + do + if [ -f "$file" ]; then + printf '%s\n' "$file" + fi + done +} + +write_release_inputs() { + for file in \ + .jerbuild \ + .jerboa/security.json \ + Dockerfile.qt \ + LICENSE \ + Makefile \ + README.md \ + SECURITY.md \ + docs/threat-model.md \ + docs/credential-handling.md \ + docs/ffi-boundary.md \ + docs/webengine-sandbox.md \ + docs/release-evidence.md \ + scripts/security-check.sh \ + scripts/sbom.sh \ + scripts/reproducibility-report.sh \ + scripts/binary-smoke.sh \ + scripts/webengine-sandbox-evidence.sh \ + support/ensure-jerboa.sh \ + support/ensure-vendor.sh + do + if [ -f "$file" ]; then + printf '%s\n' "$file" + fi + done +} + +qt_status=blocked-unavailable +if command -v pkg-config >/dev/null 2>&1; then + if pkg-config --modversion Qt6Core Qt6WebEngineCore Qt6WebEngineWidgets Qt6Widgets > "$OUT/qt-pkg-config.txt" 2>"$OUT/qt-pkg-config.stderr"; then + qt_status=version-recorded + fi +fi + +browser_version_status=missing-binary +if [ -x "./$BIN" ]; then + if QT_QPA_PLATFORM=offscreen QTWEBENGINE_CHROMIUM_FLAGS=--disable-gpu "./$BIN" version > "$OUT/browser-version.txt" 2>&1; then + browser_version_status=version-recorded + else + browser_version_status=version-failed + fi +fi + +qtwebengineprocess_status=missing +if find /opt/homebrew -path '*QtWebEngineProcess.app/Contents/MacOS/QtWebEngineProcess' -type f 2>/dev/null | sed -n '1p' > "$OUT/qtwebengineprocess.path"; then + if [ -s "$OUT/qtwebengineprocess.path" ]; then + qtwebengineprocess_status=present + fi +fi + +static_qt_tarb_status=missing +if [ -f "$STATIC_QT_TARBALL" ]; then + static_qt_tarb_status=present + sha256_file "$STATIC_QT_TARBALL" > "$OUT/static-qt-tarball-sha256.txt" + tar tzf "$STATIC_QT_TARBALL" > "$OUT/static-qt-tarball-files.txt" 2>"$OUT/static-qt-tarball.stderr" || true +fi + +adblock_manifest_status=missing +if [ -f "$ADBLOCK_MANIFEST" ]; then + adblock_manifest_status=present +fi + +yubikey_manifest_status=missing +if [ -f "$YUBIKEY_MANIFEST" ]; then + yubikey_manifest_status=present +fi + +fuse_shim_status=missing +if [ -f "$FUSE_SHIM" ]; then + fuse_shim_status=present +fi + +{ + echo "repo=jerboa-browser" + echo "sbom_status=present" + printf 'generated_at_utc=' + date -u '+%Y-%m-%dT%H:%M:%SZ' + if [ -x "$JERBUILD" ] || command -v "$JERBUILD" >/dev/null 2>&1; then + printf 'jerbuild_version=' + "$JERBUILD" --version 2>/dev/null || true + printf 'jerboa_home=' + "$JERBUILD" --jerboa-home 2>/dev/null || true + else + echo "jerbuild_status=missing" + fi + echo "qt_status=$qt_status" + echo "browser_version_status=$browser_version_status" + echo "qtwebengineprocess_status=$qtwebengineprocess_status" + echo "static_qt_tarb_status=$static_qt_tarb_status" + echo "adblock_manifest_status=$adblock_manifest_status" + echo "yubikey_manifest_status=$yubikey_manifest_status" + echo "fuse_shim_status=$fuse_shim_status" + echo "sandbox_claim=source-policy-local-helper-and-optional-process-evidence-only" + echo "target_process_sandbox_status=see-webengine-sandbox-evidence" +} > "$OUT/manifest.txt" + +write_source_files | LC_ALL=C sort -u > "$OUT/source-files.txt" +while IFS= read -r path; do + sha256_file "$path" +done < "$OUT/source-files.txt" > "$OUT/source-sha256.txt" + +write_release_inputs | LC_ALL=C sort -u > "$OUT/release-inputs.txt" +while IFS= read -r path; do + sha256_file "$path" +done < "$OUT/release-inputs.txt" > "$OUT/release-inputs-sha256.txt" + +if [ -f "$BIN" ]; then + sha256_file "$BIN" > "$OUT/binary-sha256.txt" + file "$BIN" > "$OUT/binary-file.txt" 2>&1 || true +fi + +for lib in qt-webengine/build/libjerboa_browser.* lib/libjerboa_native.* lib/libyubikey_native.* "$FUSE_SHIM"; do + for file in $lib; do + if [ -f "$file" ]; then + sha256_file "$file" + fi + done +done > "$OUT/native-runtime-sha256.txt" + +if [ "$adblock_manifest_status" = "present" ]; then + "$CARGO" metadata --manifest-path "$ADBLOCK_MANIFEST" --locked --format-version 1 \ + > "$OUT/cargo-metadata-adblock-ffi.json" 2>"$OUT/cargo-metadata-adblock-ffi.stderr" || true +fi + +if [ "$yubikey_manifest_status" = "present" ]; then + "$CARGO" metadata --manifest-path "$YUBIKEY_MANIFEST" --locked --format-version 1 \ + > "$OUT/cargo-metadata-yubikey-native.json" 2>"$OUT/cargo-metadata-yubikey-native.stderr" || true +fi --- a/scripts/security-check.sh +++ b/scripts/security-check.sh @@ -12,6 +12,12 @@ require_file() { [ -f "$1" ] || note_fail "missing required release file: $1" } +require_text() { + file=$1 + pattern=$2 + grep -q "$pattern" "$file" 2>/dev/null || note_fail "missing release marker '$pattern' in $file" +} + require_file LICENSE require_file SECURITY.md require_file README.md @@ -22,6 +28,21 @@ require_file docs/credential-handling.md require_file docs/ffi-boundary.md require_file docs/webengine-sandbox.md require_file docs/release-evidence.md +require_file scripts/security-check.sh +require_file scripts/sbom.sh +require_file scripts/reproducibility-report.sh +require_file scripts/binary-smoke.sh +require_file scripts/webengine-sandbox-evidence.sh + +require_text docs/webengine-sandbox.md 'JBROWSER_RUN_WEBENGINE_PROCESS_SMOKE=1' +require_text docs/webengine-sandbox.md 'JBROWSER_REQUIRE_TARGET_SANDBOX_PROOF=1' +require_text docs/webengine-sandbox.md 'qtwebengineprocess-argv.txt' +require_text docs/webengine-sandbox.md 'target_os_sandbox_status=target-evidence-recorded' +require_text docs/release-evidence.md 'process_smoke_status' +require_text docs/release-evidence.md 'target_sandbox_proof_status' +require_text docs/release-evidence.md 'status=blocked-target-proof' +require_text SECURITY.md 'JBROWSER_TARGET_SANDBOX_PROOF_FILE' +require_text scripts/webengine-sandbox-evidence.sh 'status=blocked-target-proof' scan_paths="scheme build-binary.ss packaging support" @@ -63,7 +84,9 @@ sandbox_matches=$( -path ./qt-webengine/build -prune -o \ -path ./dist -prune -o \ -path ./docs -prune -o \ - -type f ! -name '*.md' ! -path './scripts/security-check.sh' -print | + -type f ! -name '*.md' \ + ! -path './scripts/security-check.sh' \ + ! -path './scripts/webengine-sandbox-evidence.sh' -print | xargs grep -n -I -E 'QTWEBENGINE_DISABLE_SANDBOX[[:space:]]*=|--no-sandbox' 2>/dev/null || true ) if [ -n "$sandbox_matches" ]; then new file mode 100755 --- /dev/null +++ b/scripts/webengine-sandbox-evidence.sh @@ -0,0 +1,326 @@ +#!/bin/sh +set -eu + +ROOT=$(cd "$(dirname "$0")/.." && pwd) +cd "$ROOT" + +OUT=${JBROWSER_SANDBOX_DIR:-${SANDBOX_DIR:-dist/webengine-sandbox}} +BIN=${BIN:-jerboa-browser} +PLATFORM=$(uname -s) +RUN_PROCESS_SMOKE=${JBROWSER_RUN_WEBENGINE_PROCESS_SMOKE:-0} +PROCESS_SMOKE_MS=${JBROWSER_WEBENGINE_PROCESS_SMOKE_MS:-6000} +PROCESS_SMOKE_URL=${JBROWSER_WEBENGINE_PROCESS_SMOKE_URL:-about:blank} +PROCESS_SMOKE_SAMPLES=${JBROWSER_WEBENGINE_PROCESS_SMOKE_SAMPLES:-10} +REQUIRE_PROCESS_SMOKE=${JBROWSER_REQUIRE_WEBENGINE_PROCESS_SMOKE:-0} +TARGET_SANDBOX_PROOF_FILE=${JBROWSER_TARGET_SANDBOX_PROOF_FILE:-} +REQUIRE_TARGET_SANDBOX_PROOF=${JBROWSER_REQUIRE_TARGET_SANDBOX_PROOF:-0} + +rm -rf "$OUT" +mkdir -p "$OUT" + +source_status=absent +disable_env='QTWEBENGINE_DISABLE_SANDBOX' +disable_arg='--no-sandbox' +matches=$( + find . \ + -path ./.git -prune -o \ + -path ./.jerboa -prune -o \ + -path ./vendor -prune -o \ + -path ./qt-webengine/vendor -prune -o \ + -path ./qt-webengine/build -prune -o \ + -path ./dist -prune -o \ + -path ./docs -prune -o \ + -type f ! -name '*.md' \ + ! -path './scripts/security-check.sh' \ + ! -path './scripts/webengine-sandbox-evidence.sh' -print | + xargs grep -n -I -E "$disable_env[[:space:]]*=|$disable_arg" 2>/dev/null || true +) +if [ -n "$matches" ]; then + source_status=present + printf '%s\n' "$matches" > "$OUT/sandbox-disable-matches.txt" +else + : > "$OUT/sandbox-disable-matches.txt" +fi + +qt_version_status=blocked-unavailable +if command -v pkg-config >/dev/null 2>&1; then + if pkg-config --modversion Qt6Core Qt6WebEngineCore Qt6WebEngineWidgets Qt6Widgets > "$OUT/qt-pkg-config.txt" 2>"$OUT/qt-pkg-config.stderr"; then + qt_version_status=local-version-recorded + fi +fi + +browser_version_status=missing-binary