Harden browser release evidence privacy

ober

b80116de4de80e8af79238828cd6422160153dde

diff --git a/Makefile b/Makefile
index dc20654..d75ad8e 100644
--- 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.
diff --git a/SECURITY.md b/SECURITY.md
index b92fac4..0c3bee7 100644
--- 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
diff --git a/docs/release-evidence.md b/docs/release-evidence.md
index 33a54ba..8046210 100644
--- 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
diff --git a/docs/webengine-sandbox.md b/docs/webengine-sandbox.md
index e50d754..a228688 100644
--- 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.
diff --git a/scripts/sanitize-evidence.sh b/scripts/sanitize-evidence.sh
new file mode 100755
index 0000000..8e62d32
--- /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
diff --git a/scripts/sbom.sh b/scripts/sbom.sh
index 09253d9..228ea5e 100755
--- 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
diff --git a/scripts/security-check.sh b/scripts/security-check.sh
index 14264a6..4b01f60 100755
--- 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"
 
diff --git a/scripts/webengine-sandbox-evidence.sh b/scripts/webengine-sandbox-evidence.sh
index 69d31e4..3409ae5 100755
--- 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" ||