Harden drive release evidence privacy

ober

6a362858e941fac107f7e39254998bc4150acd24

diff --git a/.jerboa/security.json b/.jerboa/security.json
index c492ce4..b995b4c 100644
--- a/.jerboa/security.json
+++ b/.jerboa/security.json
@@ -29,6 +29,7 @@
     },
     "targetEvidence": {
       "storageProof": "fail-closed",
+      "forbidPrivateProofMaterial": true,
       "requiredMarkers": [
         "proton_live_drive_status=target-evidence-recorded",
         "s3_encrypted_roundtrip_status=target-evidence-recorded",
diff --git a/Makefile b/Makefile
index a27980b..d5403b5 100644
--- a/Makefile
+++ b/Makefile
@@ -66,6 +66,19 @@ TARGET_EVIDENCE_DIR ?= $(DIST_DIR)/target-evidence
 .PHONY: help ensure-jerboa-tools ensure-deps deps run test integration s3-integration doctor binary binary-doctor-smoke audit verify security dependency-evidence sbom reproducibility-report target-evidence release-evidence bundle install linux linux-amd64 linux-arm64 freebsd freebsd-amd64 clean
 .DEFAULT_GOAL := help
 
+lib/libjerboa_native$(SO_EXT): ensure-jerboa-tools
+	@mkdir -p lib
+	@src="$(JERBOA_NATIVE_DIR)/libjerboa_native$(SO_EXT)"; \
+	 sibling="$(CURDIR)/../jerboa/lib/libjerboa_native$(SO_EXT)"; \
+	 if [ -f "$$src" ]; then \
+	   ln -sf "$$src" "$@"; \
+	 elif [ -f "$$sibling" ]; then \
+	   ln -sf "$$sibling" "$@"; \
+	 else \
+	   echo "ERROR: libjerboa_native$(SO_EXT) not found in $(JERBOA_NATIVE_DIR) or sibling jerboa/lib; run make -C ../jerboa native" >&2; \
+	   exit 1; \
+	 fi
+
 help:
 	@echo "jerboa-drive"
 	@echo ""
@@ -129,7 +142,7 @@ ensure-deps:
 	@test -d "$(JERBOA_HTTPS_DIR)" || { echo "ERROR: JERBOA_HTTPS_DIR missing: $(JERBOA_HTTPS_DIR)" >&2; exit 1; }
 	@test -d "$(JERBOA_SSL_DIR)" || { echo "ERROR: JERBOA_SSL_DIR missing: $(JERBOA_SSL_DIR)" >&2; exit 1; }
 
-deps: ensure-jerboa-tools ensure-deps
+deps: ensure-jerboa-tools ensure-deps lib/libjerboa_native$(SO_EXT)
 	@if [ ! -f "$(JERBOA_SSL_DIR)/lib/jerboa-ssl.sls" ] || [ ! -f "$(JERBOA_SSL_DIR)/jerboa_ssl_shim$(SO_EXT)" ]; then \
 	  $(MAKE) -C "$(JERBOA_SSL_DIR)" JERBUILD="$(JERBUILD)" build; \
 	fi
@@ -174,9 +187,12 @@ binary-doctor-smoke: binary
 audit: deps
 	$(MAKE) -C "$(JERBOA_PROTON_BRIDGE_DIR)" JERBUILD="$(JERBUILD)" audit
 
-verify: security test doctor audit
+verify: security test doctor audit reproducibility-report sbom target-evidence
 
 security:
+	@test -f scripts/sanitize-evidence.sh || { echo "missing scripts/sanitize-evidence.sh" >&2; exit 1; }
+	@grep -q 'sanitize-evidence' docs/release-evidence.md || { echo "missing release evidence sanitizer docs" >&2; exit 1; }
+	@grep -q 'host-private' docs/release-evidence.md || { echo "missing host-private proof docs" >&2; exit 1; }
 	@set -eu; \
 	pattern="(BEGIN (RSA|OPENSSH|EC|DSA|PRIVATE) KEY|ghp_[A-Za-z0-9_]{20,}|github_pat_[A-Za-z0-9_]{20,}|glpat-[A-Za-z0-9_-]{20,}|gldt-[A-Za-z0-9_-]{20,}|glrt-[A-Za-z0-9_-]{20,}|Authorization['\\\"]?[[:space:]]*[:=][[:space:]]*['\\\"]?Bearer[[:space:]]+[A-Za-z0-9_-]{20,}\\.[A-Za-z0-9_-]{20,}\\.[A-Za-z0-9_-]{20,}|PROTONDRIVE_(USERNAME|PASSWORD|VAULT_PASSWORD|ACCESS_TOKEN|REFRESH_TOKEN)=['\\\"]?[A-Za-z0-9_./+=:-]{24,}|JDRIVE_(S3_TEST_SECRET|S3_TEST_ACCESS|VAULT_PASSWORD)=['\\\"]?[A-Za-z0-9_./+=:-]{24,}|AKIA[0-9A-Z]{16}|ASIA[0-9A-Z]{16})"; \
 	matches="$$(git grep -n -I -E "$$pattern" -- . ":!*.png" ":!*.jpg" ":!*.jpeg" ":!*.gif" ":!*.so" ":!*.dylib" ":!*.o" ":!*.a" ":!*.boot" ":!*.tar.gz" || true)"; \
@@ -313,6 +329,7 @@ freebsd-amd64:
 
 clean:
 	rm -rf cache tmp mount dist protonstorage-bin protonstorage-linux-amd64 protonstorage-linux-arm64 protonstorage-freebsd-amd64
+	rm -f lib/libjerboa_native$(SO_EXT)
 	rm -f protonstorage-bin-main.c protonstorage-bin.wp.so
 	rm -f protonstorage-linux-amd64-main.c protonstorage-linux-amd64.wp.so
 	rm -f protonstorage-linux-arm64-main.c protonstorage-linux-arm64.wp.so
diff --git a/SECURITY.md b/SECURITY.md
index afab891..7e5c800 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -18,6 +18,8 @@ No public production-support commitment exists yet. Security-sensitive releases 
 - Dependency and native-code audits are clean, or accepted risks are documented.
 - `make release-evidence` writes current test, audit, dependency, SBOM, and
   reproducibility evidence.
+- Release evidence is sanitized and contains no private paths, SSH remotes, or
+  host fingerprints from `uname -a`.
 - Target Drive/S3/FUSE release proof must be marker-validated. Raw Proton or
   S3 account output, tokens, credentials, vault passwords, PIV PINs, plaintext
   file bodies, and plaintext remote object manifests must not be preserved in
diff --git a/docs/release-evidence.md b/docs/release-evidence.md
index 5c1a014..bfe6a90 100644
--- a/docs/release-evidence.md
+++ b/docs/release-evidence.md
@@ -17,6 +17,11 @@ The gate records:
 - SBOM/source/dependency hashes, and
 - post-warm two-build binary reproducibility output.
 
+Release evidence is sanitized with `scripts/sanitize-evidence.sh`. The
+sanitizer redacts local user and temporary paths and fails closed if private
+paths, SSH remotes, or `uname -a` host fingerprints remain in the evidence
+bundle.
+
 The target is allowed to fail after writing evidence. A failed target means the
 repo is not production-releasable yet, but the evidence directory should explain
 which gate blocked publication.
@@ -63,7 +68,9 @@ make release-evidence
 The proof file must contain these exact marker lines and must not contain raw
 Proton/S3 account output, tokens, credentials, vault passwords, PIV PINs,
 plaintext file bodies, plaintext remote manifests, or plaintext object keys
-derived from local filenames:
+derived from local filenames. Oversized or host-private proofs, including
+private local paths, local temporary paths, SSH remotes, cookies, authorization
+headers, or high-confidence credential material, fail closed before copy:
 
 ```text
 proton_live_drive_status=target-evidence-recorded
@@ -79,6 +86,6 @@ raw_remote_output_status=not-preserved
 sensitive_artifact_policy=no-proton-passwords-tokens-s3-keys-vault-passwords-piv-pins-plaintext-files-or-raw-account-output-preserved
 ```
 
-Missing, empty, or marker-incomplete proof files fail closed as
-`status=blocked-target-proof`. Accepted proof files are copied to
-`dist/target-evidence/target-storage-proof.txt` and hashed.
+Missing, empty, oversized, host-private, sensitive, or marker-incomplete proof
+files fail closed as `status=blocked-target-proof`. Accepted proof files are
+copied to `dist/target-evidence/target-storage-proof.txt` and hashed.
diff --git a/scripts/release-evidence.sh b/scripts/release-evidence.sh
index 71f526c..630a704 100755
--- a/scripts/release-evidence.sh
+++ b/scripts/release-evidence.sh
@@ -43,6 +43,10 @@ sha256_file() {
   fi
 }
 
+sanitize_evidence() {
+  sh scripts/sanitize-evidence.sh "$evidence_dir" "$dist_dir/sbom" "$dist_dir/reproducibility" "$dist_dir/target-evidence"
+}
+
 git status --short > "$evidence_dir/git-status.txt"
 git rev-parse HEAD > "$evidence_dir/git-head.txt" 2>/dev/null || true
 
@@ -74,10 +78,12 @@ if [ "$fail" -ne 0 ]; then
     echo "release_evidence_status=blocked"
     echo "blocked_reason=one-or-more-production-gates-failed"
   } > "$evidence_dir/result.txt"
+  sanitize_evidence
   echo "Release evidence written to $evidence_dir"
   echo "Production gates are blocked; see $evidence_dir/*.status and logs."
   exit 1
 fi
 
 echo "release_evidence_status=pass" > "$evidence_dir/result.txt"
+sanitize_evidence
 echo "Release evidence written to $evidence_dir"
diff --git a/scripts/reproducibility-report.sh b/scripts/reproducibility-report.sh
index 7b47fc7..a505f46 100755
--- a/scripts/reproducibility-report.sh
+++ b/scripts/reproducibility-report.sh
@@ -115,9 +115,9 @@ stabilize_once() {
 cd "$repo_root"
 
 {
-  echo "repo=$repo_root"
+  echo "repo=jerboa-drive"
   echo "generated_at_utc=$(date -u '+%Y-%m-%dT%H:%M:%SZ')"
-  echo "jerbuild=$jerbuild"
+  echo "jerbuild_status=present"
   "$jerbuild" --version 2>/dev/null || true
   echo "binary=$binary_output"
 } > "$out_dir/build-env.txt"
@@ -168,10 +168,10 @@ esac
   echo "generated_input_status=$generated_input_status"
   echo "generated_input_policy=diagnostic-scratch-not-release-gate"
   echo "source_manifest_status=$source_manifest_status"
-  echo "jerbuild_binary_obj_dir=$obj_dir"
+  echo "jerbuild_binary_obj_dir_status=fixed-scratch"
   echo "jerbuild_binary_keep_obj_dir=1"
   echo "warmup_build_used=1"
-  echo "warmup_jerbuild_binary_obj_dir=$warmup_obj_dir"
+  echo "warmup_jerbuild_binary_obj_dir_status=fixed-scratch"
   echo "stabilization_build_used=1"
   echo "measurement_mode=post-warm"
   echo "status=$status"
diff --git a/scripts/sanitize-evidence.sh b/scripts/sanitize-evidence.sh
new file mode 100755
index 0000000..15bba44
--- /dev/null
+++ b/scripts/sanitize-evidence.sh
@@ -0,0 +1,45 @@
+#!/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#uname -a#uname -s -r -m#g' \
+    -e 's#repo=/[^[:space:]]+#repo=<redacted-path>#g' \
+    -e 's#jerbuild=[^[:space:]]+#jerbuild=<redacted-path>#g' \
+    -e 's#jerboa_home=[^[:space:]]+#jerboa_home=<redacted-path>#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|repo=/|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 a7549dd..148e163 100755
--- a/scripts/sbom.sh
+++ b/scripts/sbom.sh
@@ -56,13 +56,17 @@ cd "$repo_root"
 jerboa_home=$("$jerbuild" --jerboa-home 2>/dev/null || true)
 
 {
-  echo "repo=$repo_root"
+  echo "repo=jerboa-drive"
   echo "generated_at_utc=$(date -u '+%Y-%m-%dT%H:%M:%SZ')"
   echo "git_head=$(git rev-parse HEAD 2>/dev/null || true)"
-  echo "uname=$(uname -a)"
-  echo "jerbuild=$jerbuild"
+  printf 'platform=%s\n' "$(uname -srm 2>/dev/null || printf unknown)"
+  echo "jerbuild_status=present"
   "$jerbuild" --version 2>/dev/null || true
-  echo "jerboa_home=$jerboa_home"
+  if [ -n "$jerboa_home" ]; then
+    echo "jerboa_home_status=present"
+  else
+    echo "jerboa_home_status=missing"
+  fi
 } > "$out_dir/build-environment.txt"
 
 {
diff --git a/scripts/target-evidence.sh b/scripts/target-evidence.sh
index 689a2c4..f5f4c4d 100644
--- a/scripts/target-evidence.sh
+++ b/scripts/target-evidence.sh
@@ -6,6 +6,7 @@ dist_dir=${DIST_DIR:-"$repo_root/dist"}
 out_dir=${TARGET_EVIDENCE_DIR:-"$dist_dir/target-evidence"}
 proof_file=${JDRIVE_TARGET_STORAGE_PROOF_FILE:-}
 require_proof=${JDRIVE_REQUIRE_TARGET_STORAGE_PROOF:-0}
+proof_max_bytes=${JDRIVE_TARGET_STORAGE_PROOF_MAX_BYTES:-1048576}
 
 rm -rf "$out_dir"
 mkdir -p "$out_dir"
@@ -19,6 +20,22 @@ sha256_file() {
   fi
 }
 
+valid_uint() {
+  case "$1" in
+    ''|*[!0-9]*) return 1 ;;
+    *) return 0 ;;
+  esac
+}
+
+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,}|glpat-[A-Za-z0-9_-]{20,}|gldt-[A-Za-z0-9_-]{20,}|glrt-[A-Za-z0-9_-]{20,}|AKIA[0-9A-Z]{16}|ASIA[0-9A-Z]{16}|Authorization:[[:space:]]*(Bearer|Basic)|Cookie:|Set-Cookie:|PROTONDRIVE_(USERNAME|PASSWORD|VAULT_PASSWORD|ACCESS_TOKEN|REFRESH_TOKEN)=|JDRIVE_(S3_TEST_SECRET|S3_TEST_ACCESS|VAULT_PASSWORD)=' "$proof" >/dev/null 2>&1
+}
+
 write_default_status() {
   {
     echo "repo=jerboa-drive"
@@ -70,6 +87,16 @@ if [ ! -s "$proof_file" ]; then
   exit 1
 fi
 
+if ! valid_uint "$proof_max_bytes" || [ "$(proof_size "$proof_file")" -gt "$proof_max_bytes" ]; then
+  write_blocked_status "target-storage-proof-file-too-large"
+  exit 1
+fi
+
+if proof_has_forbidden_material "$proof_file"; then
+  write_blocked_status "target-storage-proof-contains-private-or-sensitive-material"
+  exit 1
+fi
+
 : > "$out_dir/missing-markers.txt"
 require_marker "proton_live_drive_status=target-evidence-recorded"
 require_marker "s3_encrypted_roundtrip_status=target-evidence-recorded"