Harden browser release evidence privacy
ober
b80116de4de80e8af79238828cd6422160153dde
--- a/Makefile +++ b/Makefile @@ -257,18 +257,18 @@ webengine-sandbox-evidence: BIN="$(BIN)" \ sh scripts/webengine-sandbox-evidence.sh -verify: test-all security audit +verify: test-all security audit reproducibility-report sbom webengine-sandbox-evidence binary-smoke -release-evidence: verify reproducibility-report sbom webengine-sandbox-evidence binary-smoke +release-evidence: verify @rm -rf "$(DIST_DIR)" @mkdir -p "$(DIST_DIR)" @git status --short > "$(DIST_DIR)/git-status.txt" @git rev-parse HEAD > "$(DIST_DIR)/git-commit.txt" 2>/dev/null || true @{ \ echo "date=$$(date -u +%Y-%m-%dT%H:%M:%SZ)"; \ - echo "uname=$$(uname -a)"; \ - echo "jerbuild=$(JERBUILD)"; \ - echo "jerboa_home=$(JH)"; \ + printf 'platform=%s\n' "$$(uname -srm 2>/dev/null || printf unknown)"; \ + echo "jerbuild_status=present"; \ + echo "jerboa_home_status=present"; \ "$(JERBUILD)" --version 2>/dev/null || true; \ command -v cmake >/dev/null 2>&1 && cmake --version | head -1 || true; \ command -v "$(CARGO)" >/dev/null 2>&1 && "$(CARGO)" --version || true; \ @@ -276,7 +276,7 @@ release-evidence: verify reproducibility-report sbom webengine-sandbox-evidence @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 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 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 scripts/sanitize-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 "$(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"; \ @@ -312,6 +312,8 @@ release-evidence: verify reproducibility-report sbom webengine-sandbox-evidence echo "current WebEngine sandbox evidence failed; see $(DIST_DIR)/webengine-sandbox/status.txt"; \ exit 1; \ fi + @sh scripts/sanitize-evidence.sh "$(DIST_DIR)" + @printf 'release_evidence_status=pass\n' > "$(DIST_DIR)/result.txt" @echo "release evidence written to $(DIST_DIR)" # REPL with (browser) preloaded, in the interpreter. --- a/SECURITY.md +++ b/SECURITY.md @@ -11,6 +11,8 @@ requires: - `make verify` passes on a clean checkout. - `make release-evidence` produces a complete `dist/release-evidence/` bundle. +- Release evidence is sanitized and contains no private paths, SSH remotes, or + host fingerprints from `uname -a`. - `jerboa_security_scan` reports no medium-or-higher findings in non-vendored production source. - SBOM/provenance, repeated binary reproducibility, no-secret binary-smoke, and --- a/docs/release-evidence.md +++ b/docs/release-evidence.md @@ -8,6 +8,9 @@ make release-evidence The target runs `make verify`, refreshes reproducibility/SBOM/sandbox/binary smoke evidence, and writes evidence to `dist/release-evidence/`. +The final bundle is sanitized with `scripts/sanitize-evidence.sh`, which +redacts local user and temporary paths and fails closed if private paths, SSH +remotes, or `uname -a` host fingerprints remain. `make release-evidence` also runs: @@ -48,7 +51,9 @@ and writes `process_smoke_status`, `qtwebengineprocess_argv_status`, `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`. +required target proof exits with `status=blocked-target-proof`. Oversized, +host-private, cookie, authorization, SSH remote, or high-confidence credential +material in the proof is rejected before copying. Expected files include: @@ -70,6 +75,7 @@ Expected files include: - `webengine-sandbox/target-sandbox-proof.txt` and `webengine-sandbox/target-sandbox-proof.txt.sha256` when target proof is supplied +- `result.txt` with `release_evidence_status=pass` 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/webengine-sandbox.md +++ b/docs/webengine-sandbox.md @@ -80,7 +80,9 @@ inspection output. The proof is marker-validated and must contain: - `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. +`status=blocked-target-proof`. Oversized or host-private proofs, including +private local paths, local temporary paths, SSH remotes, cookies, authorization +headers, or high-confidence credential material, also fail closed before copy. +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. new file mode 100755 --- /dev/null +++ b/scripts/sanitize-evidence.sh @@ -0,0 +1,44 @@ +#!/bin/sh +set -eu + +fail() { + printf 'sanitize-evidence: %s\n' "$*" >&2 + exit 1 +} + +redact_file() { + file=$1 + grep -Iq . "$file" || return 0 + tmp=$file.sanitize.$$ + sed -E \ + -e 's#/Users/[^[:space:]"]+#<redacted-path>#g' \ + -e 's#/home/[^/[:space:]"]+[^[:space:]"]*#<redacted-path>#g' \ + -e 's#/var/folders/[^[:space:]"]+#<redacted-path>#g' \ + -e 's#~/mine(/[^[:space:]"]*)?#<redacted-path>#g' \ + -e 's#git@[^[:space:]"]+#<redacted-ssh-remote>#g' \ + -e 's#jerbuild=[^[:space:]]+#jerbuild=<redacted-path>#g' \ + -e 's#jerboa_home=[^[:space:]]+#jerboa_home=<redacted-path>#g' \ + -e 's#browser_pid=[0-9]+#browser_pid=<redacted-pid>#g' \ + "$file" > "$tmp" + mv "$tmp" "$file" +} + +[ "$#" -gt 0 ] || exit 0 + +for path in "$@"; do + [ -e "$path" ] || continue + find "$path" -type f -print | while IFS= read -r file; do + redact_file "$file" + done +done + +pattern='/Users/|/home/[^/[:space:]]+|/var/folders/|~/mine|git@|uname -a|jerbuild=/Users|jerboa_home=/Users' +if command -v rg >/dev/null 2>&1; then + if rg -n -H "$pattern" "$@"; then + fail "private path, SSH remote, or uname -a marker found in release evidence" + fi +else + if grep -R -n -E "$pattern" "$@"; then + fail "private path, SSH remote, or uname -a marker found in release evidence" + fi +fi --- a/scripts/sbom.sh +++ b/scripts/sbom.sh @@ -74,6 +74,7 @@ write_release_inputs() { scripts/reproducibility-report.sh \ scripts/binary-smoke.sh \ scripts/webengine-sandbox-evidence.sh \ + scripts/sanitize-evidence.sh \ support/ensure-jerboa.sh \ support/ensure-vendor.sh do --- a/scripts/security-check.sh +++ b/scripts/security-check.sh @@ -33,16 +33,21 @@ 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_file scripts/sanitize-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/webengine-sandbox.md 'host-private' 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 docs/release-evidence.md 'sanitize-evidence' require_text SECURITY.md 'JBROWSER_TARGET_SANDBOX_PROOF_FILE' require_text scripts/webengine-sandbox-evidence.sh 'status=blocked-target-proof' +require_text scripts/webengine-sandbox-evidence.sh 'proof_has_forbidden_material' +require_text Makefile 'scripts/sanitize-evidence.sh' scan_paths="scheme build-binary.ss packaging support" --- a/scripts/webengine-sandbox-evidence.sh +++ b/scripts/webengine-sandbox-evidence.sh @@ -14,6 +14,7 @@ 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} +TARGET_PROOF_MAX_BYTES=${JBROWSER_TARGET_SANDBOX_PROOF_MAX_BYTES:-1048576} rm -rf "$OUT" mkdir -p "$OUT" @@ -99,6 +100,15 @@ require_marker() { fi } +proof_size() { + wc -c < "$1" | tr -d '[:space:]' +} + +proof_has_forbidden_material() { + proof=$1 + grep -n -I -E '/Users/|/home/[^/[:space:]]+|/var/folders/|~/mine|git@|BEGIN (RSA|OPENSSH|EC|DSA|PRIVATE) KEY|ghp_[A-Za-z0-9_]{20,}|github_pat_[A-Za-z0-9_]{20,}|sk-(ant-api03|proj|svcacct)-[A-Za-z0-9_-]{30,}|AKIA[0-9A-Z]{16}|Authorization:[[:space:]]*(Bearer|Basic)|Cookie:|Set-Cookie:' "$proof" >/dev/null 2>&1 +} + record_proof() { proof=$1 destination=$2 @@ -293,6 +303,16 @@ if [ "$target_sandbox_proof_requested" = "1" ]; then exit 1 fi + if ! valid_uint "$TARGET_PROOF_MAX_BYTES" || [ "$(proof_size "$TARGET_SANDBOX_PROOF_FILE")" -gt "$TARGET_PROOF_MAX_BYTES" ]; then + write_blocked_target_proof oversized "target WebEngine sandbox proof is larger than JBROWSER_TARGET_SANDBOX_PROOF_MAX_BYTES" + exit 1 + fi + + if proof_has_forbidden_material "$TARGET_SANDBOX_PROOF_FILE"; then + write_blocked_target_proof host-private "target WebEngine sandbox proof contains private path, local temporary path, credential, cookie, authorization, or SSH remote material" + exit 1 + fi + if ! require_marker "$TARGET_SANDBOX_PROOF_FILE" "target_os_sandbox_status=target-evidence-recorded" || ! require_marker "$TARGET_SANDBOX_PROOF_FILE" "target_process_sandbox_status=target-evidence-recorded" || ! require_marker "$TARGET_SANDBOX_PROOF_FILE" "qtwebengineprocess_runtime_status=observed" ||