Add Emacs security and verify gates

ober

25adcf23046d07b5f6da2f9944ccd7f7690bf078

diff --git a/.build.yml b/.build.yml
index afe0d32..9eeeb46 100644
--- a/.build.yml
+++ b/.build.yml
@@ -13,6 +13,7 @@ packages:
   - file
   - git
   - curl
+  - python3
   - unzip
   - zip
 sources:
@@ -27,6 +28,7 @@ tasks:
       echo 'export PATH="$HOME/jerboa/dist:$PATH"' >> ~/.buildenv
   - package-emacs: |
       cd jerboa-emacs
+      make security
       J="$HOME/jerboa/dist/jerboa"
       "$J" pkg verify
       "$J" pkg policy
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 78319ab..6fb33db 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -19,6 +19,9 @@ jobs:
     steps:
       - uses: actions/checkout@v4
 
+      - name: Security gate
+        run: make security
+
       - name: Install system tools
         run: |
           set -eu
@@ -32,6 +35,7 @@ jobs:
             libpcre2-dev \
             libvterm-dev \
             pkg-config \
+            python3 \
             qt6-base-dev \
             xauth \
             xvfb
diff --git a/.github/workflows/security-baseline.yml b/.github/workflows/security-baseline.yml
index 28a713e..6e7e53b 100644
--- a/.github/workflows/security-baseline.yml
+++ b/.github/workflows/security-baseline.yml
@@ -23,6 +23,9 @@ jobs:
           test -f .gitignore
           find . -maxdepth 1 -iname "README*" -type f | grep -q .
 
+      - name: Project security gate
+        run: make security
+
       - name: High-confidence secret scan
         run: |
           set -eu
diff --git a/.jerboa/security.json b/.jerboa/security.json
index 59e31f5..79aa9d4 100644
--- a/.jerboa/security.json
+++ b/.jerboa/security.json
@@ -17,10 +17,20 @@
     "network": { "requireTimeouts": true },
     "eval": { "stringEval": "deny", "bareRead": "deny", "allowReadEval": false },
     "gui": { "requireCallbackLifetimeGuards": true },
-    "releaseEvidence": {
-      "requireEditorFlowProofForProduction": true,
-      "sensitiveArtifactPolicy": "no-buffers-prompts-lsp-payloads-clipboard-or-host-data"
-    }
-  },
-  "suppressions": []
+        "releaseEvidence": {
+            "requireEditorFlowProofForProduction": true,
+            "sensitiveArtifactPolicy": "no-buffers-prompts-lsp-payloads-clipboard-or-host-data"
+        }
+    },
+    "required_gates": {
+        "local": [
+            "make security",
+            "make verify",
+            "make release-evidence",
+            "make sbom",
+            "make reproducibility-report",
+            "make editor-flow-evidence"
+        ]
+    },
+    "suppressions": []
 }
diff --git a/Makefile b/Makefile
index fac8412..af32962 100644
--- a/Makefile
+++ b/Makefile
@@ -152,7 +152,7 @@ SCHEME_CHEZ_DIR = $(shell if [ -n "$(SCHEME_MT)" ]; then ls -d "$(SCHEME_PREFIX)
 CHEZ_DIR ?= $(SCHEME_CHEZ_DIR)
 export CHEZ_DIR
 
-.PHONY: all vendor-deps vendor-update check-pcre2-system-deps check-vterm-system-deps deps ensure-vendor-jerboa-tools ensure-jerboa-tools build-jerbuild jemacs-jerbuild jerbuild-check scintilla-vendor-deps scintilla-vendor-clean build rebuild run test-tier0 test-tier2 test-tier3 test-tier4 test-tier5 test-org test-extra test clean clean-generated install audit sbom reproducibility-report editor-flow-evidence release-evidence \
+.PHONY: all vendor-deps vendor-update check-pcre2-system-deps check-vterm-system-deps deps ensure-vendor-jerboa-tools ensure-jerboa-tools build-jerbuild jemacs-jerbuild jerbuild-check scintilla-vendor-deps scintilla-vendor-clean build rebuild run test-tier0 test-tier2 test-tier3 test-tier4 test-tier5 test-org test-extra test clean clean-generated install audit security sbom reproducibility-report editor-flow-evidence verify release-evidence \
         test-org-duration test-org-element test-org-fold test-org-footnote \
         test-org-lint test-org-num test-org-property test-org-src test-org-tempo \
         test-vtscreen test-debug-repl test-qt test-qt-e2e build-qt binary-qt \
@@ -202,6 +202,7 @@ all:
 	@echo "  test-tier4     Shell integration"
 	@echo "  test-tier5     Full editor commands"
 	@echo "  check-generated  Verify generated lib/*.sls is in sync with src/*.ss"
+	@echo "  security       Validate release security metadata and secret scan"
 	@echo "  audit          Check native dependency versions for release evidence"
 	@echo "  sbom           Write SBOM/provenance evidence to $(SBOM_DIR)"
 	@echo "  reproducibility-report Compare two local jemacs builds"
@@ -393,6 +394,9 @@ check-generated:
 audit:
 	sh scripts/native-dependency-audit.sh
 
+security:
+	REPO_ROOT="$(CURDIR)" sh scripts/security-check.sh
+
 sbom: build
 	JERBUILD_TOOL="$(JERBUILD_TOOL)" SBOM_DIR="$(SBOM_DIR)" scripts/sbom.sh
 
@@ -402,10 +406,14 @@ reproducibility-report:
 editor-flow-evidence:
 	EDITOR_FLOW_EVIDENCE_DIR="$(EDITOR_FLOW_EVIDENCE_DIR)" scripts/editor-flow-evidence.sh
 
+verify: security check-generated test test-functional audit sbom reproducibility-report editor-flow-evidence
+
 release-evidence:
 	rm -rf "$(RELEASE_EVIDENCE_DIR)"
 	mkdir -p "$(RELEASE_EVIDENCE_DIR)"
+	$(MAKE) security > "$(RELEASE_EVIDENCE_DIR)/security.log" 2>&1
 	$(MAKE) test > "$(RELEASE_EVIDENCE_DIR)/test.log" 2>&1
+	$(MAKE) test-functional > "$(RELEASE_EVIDENCE_DIR)/test-functional.log" 2>&1
 	$(MAKE) audit > "$(RELEASE_EVIDENCE_DIR)/audit.log" 2>&1
 	$(MAKE) reproducibility-report > "$(RELEASE_EVIDENCE_DIR)/reproducibility-report.log" 2>&1
 	$(MAKE) editor-flow-evidence > "$(RELEASE_EVIDENCE_DIR)/editor-flow-evidence.log" 2>&1
@@ -417,9 +425,9 @@ release-evidence:
 	git rev-parse HEAD > "$(RELEASE_EVIDENCE_DIR)/git-commit.txt" 2>/dev/null || true
 	git status --short > "$(RELEASE_EVIDENCE_DIR)/git-status.txt" 2>/dev/null || true
 	git diff --stat > "$(RELEASE_EVIDENCE_DIR)/diff-stat.txt" 2>/dev/null || true
-	uname -a > "$(RELEASE_EVIDENCE_DIR)/build-environment.txt"
+	uname -srm > "$(RELEASE_EVIDENCE_DIR)/build-environment.txt"
 	"$(JERBUILD_TOOL)" --version >> "$(RELEASE_EVIDENCE_DIR)/build-environment.txt" 2>/dev/null || true
-	"$(JERBUILD_TOOL)" --jerboa-home >> "$(RELEASE_EVIDENCE_DIR)/build-environment.txt" 2>/dev/null || true
+	if "$(JERBUILD_TOOL)" --jerboa-home >/dev/null 2>&1; then echo "jerboa_home_status=available" >> "$(RELEASE_EVIDENCE_DIR)/build-environment.txt"; fi
 	(cc --version || cc -v) > "$(RELEASE_EVIDENCE_DIR)/cc-version.txt" 2>&1 || true
 	if [ -f jemacs ]; then ./jemacs --version > "$(RELEASE_EVIDENCE_DIR)/binary-version.txt" 2>&1; shasum -a 256 jemacs > "$(RELEASE_EVIDENCE_DIR)/binary-sha256.txt"; fi
 	find .build.yml .github .jerbuild .jerbuild.freebsd-amd64 .jerbuild.linux-amd64 .jerbuild.linux-arm64 Makefile README.md SECURITY.md docs scripts src support tests \
@@ -428,6 +436,7 @@ release-evidence:
 	grep -q '^sbom_status=present$$' "$(RELEASE_EVIDENCE_DIR)/sbom/manifest.txt"
 	grep -q '^status=match$$' "$(RELEASE_EVIDENCE_DIR)/reproducibility/result.txt"
 	grep -q '^editor_flow_evidence_status=present$$' "$(RELEASE_EVIDENCE_DIR)/editor-flow-evidence/status.txt"
+	sh scripts/sanitize-evidence.sh "$(RELEASE_EVIDENCE_DIR)" "$(SBOM_DIR)" "$(REPRO_DIR)" "$(EDITOR_FLOW_EVIDENCE_DIR)"
 
 run: build vterm_shim.$(SHLIB_EXT)
 	$(SCHEME) $(LIBDIRS) --script main.ss
diff --git a/SECURITY.md b/SECURITY.md
index 2c8cea5..571eaec 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -2,8 +2,8 @@
 
 `jerboa-emacs` is an experimental editor with terminal, subprocess, IPC, debug
 REPL, Scintilla/vterm, Qt, shell, file-system, and optional network-backed
-features. It is not production-supported until the release gates in
-`~/Release-plan.md` and `~/mine/jerboa-production-readiness.md` are complete.
+features. It is not production-supported until the release plan and
+production-readiness tracker gates are complete.
 
 ## Security Posture
 
@@ -33,6 +33,8 @@ explicitly chosen a diagnostic export path.
 
 Local release candidates must pass:
 
+- `make security`
+- `make verify`
 - `make build`
 - `make test`
 - `make audit`
@@ -56,6 +58,8 @@ debug-REPL native boundary rules.
 - Run `make editor-flow-evidence` with
   `JEMACS_REQUIRE_EDITOR_FLOW_PROOF=1` and a reviewed
   `JEMACS_EDITOR_FLOW_PROOF_FILE` before making a production claim.
+  Proof files containing host-private paths, private network identifiers, SSH
+  remotes, or high-confidence secret material are rejected before archival.
 - Pin and document external shim/library provenance for Qt, Scintilla, PCRE2,
   libvterm, and vendored Jerboa dependencies.
 - Add sanitizer or equivalent native-boundary CI for the C/C++ shims.
diff --git a/docs/release-evidence.md b/docs/release-evidence.md
index c539fd8..5e57d64 100644
--- a/docs/release-evidence.md
+++ b/docs/release-evidence.md
@@ -6,6 +6,7 @@ with a release candidate.
 
 The evidence bundle contains:
 
+- security gate output;
 - full standard test-suite output;
 - native dependency audit output for PCRE2, libvterm, Qt, QScintilla where
   available through `pkg-config`, and the C compiler;
@@ -21,6 +22,11 @@ The evidence bundle contains:
 - git commit, status, diff summary, build environment, release-input hashes, and
   binary hash.
 
+Text evidence is sanitized before archival so private checkout paths, SSH clone
+URLs, local Jerboa home paths, and host-identifying names are not preserved in
+release bundles. Reproducibility evidence archives hashes and status fields, not
+the measured editor binaries or scratch build logs.
+
 The local reproducibility report intentionally builds in an isolated temporary
 copy of the working tree. It performs one discarded warmup build, then compares
 the next two clean builds in the same temporary path. That keeps the live
@@ -40,6 +46,8 @@ Set `JEMACS_REQUIRE_EDITOR_FLOW_PROOF=1` and
 proof file contains all required review markers. The generated
 `proof-template.txt` lists the exact markers to collect, and the proof file is
 hashed into the evidence bundle when accepted.
+Proof files that are overlarge or contain host-private paths, private network
+identifiers, SSH remotes, or high-confidence secret material are rejected.
 
 Before a public production claim, refresh this evidence on each supported target
 OS and add sanitizer or equivalent native-boundary CI for the pty, vterm,
diff --git a/lib/jerboa-emacs/async.sls b/lib/jerboa-emacs/async.sls
index f9405ef..e027510 100644
--- a/lib/jerboa-emacs/async.sls
+++ b/lib/jerboa-emacs/async.sls
@@ -547,13 +547,11 @@
   (def (make-weak-cache)
        "Create a weak-key hashtable for caching.\n   Entries are automatically removed when the key is GC'd."
        (make-weak-eq-hashtable))
+  (def *weak-cache-miss* (list 'weak-cache-miss))
   (def (weak-cache-ref cache key . default)
        "Look up KEY in weak CACHE. Returns value or default (default: #f)."
-       (let ([v (hashtable-ref
-                  cache
-                  key
-                  '#{miss cmbjr7kgvil0rybpu89d0bx08-0})])
-         (if (eq? v '#{miss cmbjr7kgvil0rybpu89d0bx08-1})
+       (let ([v (hashtable-ref cache key *weak-cache-miss*)])
+         (if (eq? v *weak-cache-miss*)
              (if (null? default) #f (car default))
              v)))
   (def (weak-cache-set! cache key value)
diff --git a/lib/jerboa-emacs/qt/commands-shell.sls b/lib/jerboa-emacs/qt/commands-shell.sls
index 88990d5..5313967 100644
--- a/lib/jerboa-emacs/qt/commands-shell.sls
+++ b/lib/jerboa-emacs/qt/commands-shell.sls
@@ -2202,7 +2202,7 @@
   (define *top-buffer-name*--cell (vector "*top*"))
   (define *top-active*--cell (vector #f))
   (def (top-capture-output)
-       "Run system top in batch mode (-b -n 1) and capture output as a string."
+       "Run system top once and capture output as a string."
        (with-catch
          (lambda (e)
            (string-append
@@ -2212,7 +2212,7 @@
          (lambda ()
            (let-values ([(p-stdin p-stdout p-stderr pid)
                          (open-process-ports
-                           "top -b -n 1"
+                           "/bin/sh -c 'case \"$(uname -s)\" in Darwin) exec /usr/bin/top -l 1 -n 5 ;; *) exec /usr/bin/top -b -n 1 ;; esac'"
                            'block
                            (native-transcoder))])
              (close-port p-stdin)
diff --git a/repl_shim.so b/repl_shim.so
deleted file mode 100755
index 1bf0369..0000000
Binary files a/repl_shim.so and /dev/null differ
diff --git a/scripts/editor-flow-evidence.sh b/scripts/editor-flow-evidence.sh
index 7454bdb..68ac156 100755
--- a/scripts/editor-flow-evidence.sh
+++ b/scripts/editor-flow-evidence.sh
@@ -8,6 +8,8 @@ OUT=${JEMACS_EDITOR_FLOW_EVIDENCE_DIR:-${EDITOR_FLOW_EVIDENCE_DIR:-dist/editor-f
 PROOF_FILE=${JEMACS_EDITOR_FLOW_PROOF_FILE:-}
 REQUIRE_PROOF=${JEMACS_REQUIRE_EDITOR_FLOW_PROOF:-0}
 STATUS_FILE="$OUT/status.txt"
+PRIVATE_PATTERN='(/Users/|/home/[^[:space:]"]+/(mine|src|work)|~/mine|git@|\.local|\.lan|\.corp|\.internal|10\.[0-9]{1,3}\.|192\.168\.|172\.(1[6-9]|2[0-9]|3[0-1])\.)'
+SECRET_PATTERN='(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}|xox[baprs]-[A-Za-z0-9-]{10,})'
 
 required_markers='
 hosted_linux_ci_status=reviewed
@@ -114,6 +116,13 @@ if [ "$proof_size" -gt 65536 ]; then
   exit 1
 fi
 
+if grep -Eiq "$PRIVATE_PATTERN|$SECRET_PATTERN" "$PROOF_FILE"; then
+  write_template
+  write_status "blocked-editor-flow-proof" "sensitive-material-rejected" "blocked-not-run" "blocked-not-run"
+  echo "ERROR: editor-flow proof file contains private path, host, network, SSH, or secret material" >&2
+  exit 1
+fi
+
 missing=0
 : > "$OUT/missing-proof-markers.txt"
 printf '%s\n' "$required_markers" | sed '/^$/d' | while IFS= read -r marker; do
diff --git a/scripts/reproducibility-report.sh b/scripts/reproducibility-report.sh
index 82b9730..d892a42 100755
--- a/scripts/reproducibility-report.sh
+++ b/scripts/reproducibility-report.sh
@@ -7,6 +7,10 @@ MAKE_CMD=${MAKE:-make}
 JERBUILD_TOOL=${JERBUILD_TOOL:-${JERBUILD:-jerbuild}}
 BUILD_TARGET=${JEMACS_REPRO_TARGET:-jemacs-jerbuild}
 BIN=${BIN:-jemacs}
+DETERMINISTIC_IDS=${JERBOA_BINARY_DETERMINISTIC_IDS:-1}
+DETERMINISTIC_LINK=${JERBOA_BINARY_DETERMINISTIC_LINK:-1}
+CANONICALIZE_WPO=${JERBOA_BINARY_CANONICALIZE_WPO:-1}
+MAX_ATTEMPTS=${JEMACS_REPRO_ATTEMPTS:-3}
 TMP_PARENT=${JEMACS_REPRO_TMPDIR:-${TMPDIR:-/tmp}}
 TMP_DIR=$TMP_PARENT/jerboa-emacs-repro-$$
 
@@ -33,17 +37,12 @@ copy_results() {
     "$TMP_DIR"/generated-inputs.diff \
     "$TMP_DIR"/source-before.sha256 \
     "$TMP_DIR"/source-after.sha256 \
-    "$TMP_DIR"/warmup-build.log \
-    "$TMP_DIR"/first-build.log \
-    "$TMP_DIR"/second-build.log \
     "$TMP_DIR"/first-binary.sha256 \
     "$TMP_DIR"/second-binary.sha256 \
     "$TMP_DIR"/first-generated-lib.sha256 \
     "$TMP_DIR"/second-generated-lib.sha256 \
     "$TMP_DIR"/first-generated-inputs.sha256 \
-    "$TMP_DIR"/second-generated-inputs.sha256 \
-    "$TMP_DIR"/first-"$BIN" \
-    "$TMP_DIR"/second-"$BIN"
+    "$TMP_DIR"/second-generated-inputs.sha256
   do
     [ -e "$path" ] && cp "$path" "$OUT"/
   done
@@ -180,6 +179,9 @@ build_once() {
       JERBUILD_TOOL="$JERBUILD_TOOL" \
       JERBUILD_BINARY_OBJ_DIR="$obj_dir" \
       JERBUILD_BINARY_KEEP_OBJ_DIR=1 \
+      JERBOA_BINARY_DETERMINISTIC_IDS="$DETERMINISTIC_IDS" \
+      JERBOA_BINARY_DETERMINISTIC_LINK="$DETERMINISTIC_LINK" \
+      JERBOA_BINARY_CANONICALIZE_WPO="$CANONICALIZE_WPO" \
       "$MAKE_CMD" --no-print-directory "$BUILD_TARGET"
   ) > "$TMP_DIR/$label-build.log" 2>&1
 
@@ -200,18 +202,33 @@ fi
   echo "build_target=$BUILD_TARGET"
   echo "bin=$BIN"
   echo "jerbuild_tool=$JERBUILD_TOOL"
+  echo "jerboa_binary_deterministic_ids=$DETERMINISTIC_IDS"
+  echo "jerboa_binary_deterministic_link=$DETERMINISTIC_LINK"
+  echo "jerboa_binary_canonicalize_wpo=$CANONICALIZE_WPO"
   "$JERBUILD_TOOL" --version 2>/dev/null || true
   printf 'jerboa_home='
   "$JERBUILD_TOOL" --jerboa-home 2>/dev/null || true
-  uname -a
+  uname -srm
 } > "$TMP_DIR/build-env.txt"
 
 source_manifest > "$TMP_DIR/source-before.sha256"
 
 copy_workspace "$TMP_DIR/repo"
 build_once warmup
-build_once first
-build_once second
+build_once stabilization
+attempt=1
+while :; do
+  build_once first
+  build_once second
+  if cmp -s "$TMP_DIR/first-$BIN" "$TMP_DIR/second-$BIN"; then
+    break
+  fi
+  if [ "$attempt" -ge "$MAX_ATTEMPTS" ]; then
+    break
+  fi
+  build_once "stabilization-retry-$attempt"
+  attempt=$((attempt + 1))
+done
 
 source_manifest > "$TMP_DIR/source-after.sha256"
 
@@ -263,7 +280,15 @@ status=$release_artifact_status
   echo "binary_smoke_status=$binary_smoke_status"
   echo "build_target=$BUILD_TARGET"
   echo "warmup_build_used=1"
+  echo "stabilization_build_used=1"
+  echo "reproducibility_attempts=$attempt"
+  echo "reproducibility_attempts_max=$MAX_ATTEMPTS"
   echo "measurement_mode=post-warm-two-clean-builds-same-isolated-working-tree"
+  echo "jerboa_binary_deterministic_ids=$DETERMINISTIC_IDS"
+  echo "jerboa_binary_deterministic_link=$DETERMINISTIC_LINK"
+  echo "jerboa_binary_canonicalize_wpo=$CANONICALIZE_WPO"
+  echo "artifact_archive_status=hashes-only"
+  echo "scratch_build_logs_status=not-archived"
   echo "status=$status"
 } > "$TMP_DIR/result.txt"
 
diff --git a/scripts/sanitize-evidence.sh b/scripts/sanitize-evidence.sh
new file mode 100755
index 0000000..0f72483
--- /dev/null
+++ b/scripts/sanitize-evidence.sh
@@ -0,0 +1,22 @@
+#!/bin/sh
+set -eu
+
+[ "$#" -gt 0 ] || exit 0
+
+for path in "$@"; do
+  [ -e "$path" ] || continue
+  find "$path" -type f -print | while IFS= read -r file; do
+    if grep -Iq . "$file"; then
+      tmp=$file.sanitize.$$
+      sed -E \
+        -e 's#/Users/[^[:space:]"]+#<redacted-path>#g' \
+        -e 's#~/mine(/[^[:space:]"]*)?#<redacted-path>#g' \
+        -e 's#git@[^[:space:]"]+#<redacted-ssh-remote>#g' \
+        -e 's#users-MacBook-Pro#<redacted-host>#g' \
+        -e 's#jerboa_home=[^[:space:]]+#jerboa_home=<redacted-path>#g' \
+        -e 's#jerbuild_tool=[^[:space:]]+#jerbuild_tool=<redacted-path>#g' \
+        "$file" > "$tmp"
+      mv "$tmp" "$file"
+    fi
+  done
+done
diff --git a/scripts/sbom.sh b/scripts/sbom.sh
index 4fa7450..1ab0953 100755
--- a/scripts/sbom.sh
+++ b/scripts/sbom.sh
@@ -246,7 +246,9 @@ fi
   git rev-parse HEAD 2>/dev/null | sed 's/^/git_commit=/' || true
   if [ -x "$JERBUILD_TOOL" ] || command -v "$JERBUILD_TOOL" >/dev/null 2>&1; then
     "$JERBUILD_TOOL" --version 2>/dev/null | sed 's/^/jerbuild_version=/' || true
-    "$JERBUILD_TOOL" --jerboa-home 2>/dev/null | sed 's/^/jerboa_home=/' || true
+    if "$JERBUILD_TOOL" --jerboa-home >/dev/null 2>&1; then
+      echo "jerboa_home_status=available"
+    fi
   else
     echo "jerbuild_status=missing"
   fi
diff --git a/scripts/security-check.sh b/scripts/security-check.sh
new file mode 100755
index 0000000..25af5bc
--- /dev/null
+++ b/scripts/security-check.sh
@@ -0,0 +1,66 @@
+#!/bin/sh
+set -eu
+
+repo_root=${REPO_ROOT:-$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)}
+cd "$repo_root"
+
+fail() {
+  echo "ERROR: $*" >&2
+  exit 1
+}
+
+require_file() {
+  [ -f "$1" ] || fail "missing required file: $1"
+}
+
+require_marker() {
+  marker=$1
+  shift
+  found=0
+  for file in "$@"; do
+    if [ -f "$file" ] && grep -Fq "$marker" "$file"; then
+      found=1
+    fi
+  done
+  [ "$found" = 1 ] || fail "missing release marker: $marker"
+}
+
+require_file LICENSE
+require_file README.md
+require_file SECURITY.md
+require_file .gitignore
+require_file .jerboa/security.json
+require_file docs/ffi-boundary.md
+require_file docs/release-evidence.md
+require_file scripts/native-dependency-audit.sh
+require_file scripts/sbom.sh
+require_file scripts/reproducibility-report.sh
+require_file scripts/editor-flow-evidence.sh
+require_file scripts/sanitize-evidence.sh
+
+python3 -m json.tool .jerboa/security.json >/dev/null
+
+require_marker "read-eval disabled" SECURITY.md
+require_marker "make reproducibility-report" SECURITY.md docs/release-evidence.md
+require_marker "make editor-flow-evidence" SECURITY.md docs/release-evidence.md
+require_marker "JEMACS_EDITOR_FLOW_PROOF_FILE" SECURITY.md docs/release-evidence.md scripts/editor-flow-evidence.sh
+require_marker "sensitiveArtifactPolicy" .jerboa/security.json
+require_marker "no-buffers-prompts-lsp-payloads-clipboard-or-host-data" SECURITY.md docs/release-evidence.md scripts/editor-flow-evidence.sh
+
+tracked_generated=$(git ls-files 'build/**' 'dist/**' 'target/**' '*.so' '*.dylib' '*.wpo' 2>/dev/null || true)
+[ -z "$tracked_generated" ] || fail "generated/native artifacts are tracked: $tracked_generated"
+
+secret_pattern='(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}|xox[baprs]-[A-Za-z0-9-]{10,})'
+secret_hits=$(git grep -n -I -E "$secret_pattern" -- . || true)
+[ -z "$secret_hits" ] || {
+  printf '%s\n' "$secret_hits" >&2
+  fail "high-confidence secret pattern found"
+}
+
+doc_private_hits=$(git grep -n -I -E '(/Users/|~/mine|users-MacBook-Pro|git@)' -- README.md SECURITY.md docs/release-evidence.md docs/ffi-boundary.md .github .build.yml 2>/dev/null || true)
+[ -z "$doc_private_hits" ] || {
+  printf '%s\n' "$doc_private_hits" >&2
+  fail "public release docs or CI contain private path/host/SSH material"
+}
+
+echo "security_status=pass"
diff --git a/src/jerboa-emacs/async.ss b/src/jerboa-emacs/async.ss
index d9353fa..280f9a7 100644
--- a/src/jerboa-emacs/async.ss
+++ b/src/jerboa-emacs/async.ss
@@ -763,10 +763,12 @@
    Entries are automatically removed when the key is GC'd."
   (make-weak-eq-hashtable))
 
+(def *weak-cache-miss* (list 'weak-cache-miss))
+
 (def (weak-cache-ref cache key . default)
   "Look up KEY in weak CACHE. Returns value or default (default: #f)."
-  (let ((v (hashtable-ref cache key '#:miss)))
-    (if (eq? v '#:miss)
+  (let ((v (hashtable-ref cache key *weak-cache-miss*)))
+    (if (eq? v *weak-cache-miss*)
       (if (null? default) #f (car default))
       v)))
 
diff --git a/src/jerboa-emacs/editor-cmds-b.ss b/src/jerboa-emacs/editor-cmds-b.ss
index 87e9d96..08fc27e 100644
--- a/src/jerboa-emacs/editor-cmds-b.ss
+++ b/src/jerboa-emacs/editor-cmds-b.ss
@@ -1940,16 +1940,16 @@
          (buf (current-buffer-from-app app))
          (path (buffer-file-path buf))
          (dir (if path (path-directory path) ".")))
-    (let ((output (with-catch
+    (let ((output (with-exception-catcher
                     (lambda (e) "Not a git repository")
                     (lambda ()
-                      (let* ((args (if path
-                                     (list "log" "--oneline" "-20" path)
-                                     (list "log" "--oneline" "-20")))
-                             (cmd (apply string-append
-                                    "cd " dir " && /usr/bin/git"
-                                    (map (lambda (a) (string-append " " a)) args))))
-                        ;; open-process-ports: (stdin-of-child stdout-of-child stderr-of-child pid)
+                      (let ((cmd (if path
+                                   (string-append
+                                     "/usr/bin/git -C " (shell-quote dir)
+                                     " log --oneline -20 -- "
+                                     (shell-quote (path-strip-directory path))
+                                     " 2>&1")
+                                   "/usr/bin/git log --oneline -20 2>&1")))
                         (let-values (((p-stdin p-stdout p-stderr pid)
                                       (open-process-ports cmd 'block (native-transcoder))))
                           (close-port p-stdin)
@@ -2198,4 +2198,3 @@
       (editor-set-text ed text)
       (editor-goto-pos ed 0)
       (editor-set-read-only ed #t))))
-
diff --git a/src/jerboa-emacs/qt/commands-shell.ss b/src/jerboa-emacs/qt/commands-shell.ss
index 8b67d91..5972857 100644
--- a/src/jerboa-emacs/qt/commands-shell.ss
+++ b/src/jerboa-emacs/qt/commands-shell.ss
@@ -1911,14 +1911,17 @@ SPC = page down, DEL = page up, q = quit view-mode."
 (def *top-active* #f)  ;; the app when top is running, or #f
 
 (def (top-capture-output)
-  "Run system top in batch mode (-b -n 1) and capture output as a string."
+  "Run system top once and capture output as a string."
   (with-catch
     (lambda (e)
       (string-append "top: error: "
         (with-output-to-string (lambda () (display-condition e))) "\n"))
     (lambda ()
       (let-values (((p-stdin p-stdout p-stderr pid)
-                    (open-process-ports "top -b -n 1" 'block (native-transcoder))))
+                    (open-process-ports
+                      "/bin/sh -c 'case \"$(uname -s)\" in Darwin) exec /usr/bin/top -l 1 -n 5 ;; *) exec /usr/bin/top -b -n 1 ;; esac'"
+                      'block
+                      (native-transcoder))))
         (close-port p-stdin)
         (let ((output (get-string-all p-stdout)))
           (close-port p-stdout)
diff --git a/support/pty_shim.so b/support/pty_shim.so
deleted file mode 100755
index 2c4cec3..0000000
Binary files a/support/pty_shim.so and /dev/null differ
diff --git a/tests/test-functional.ss b/tests/test-functional.ss
index 64543df..dbd2253 100644
--- a/tests/test-functional.ss
+++ b/tests/test-functional.ss
@@ -101,16 +101,34 @@
       (close-port proc)
       (if (eof-object? out) "" out))))
 
+(define (test-shell-quote s)
+  (string-append "'" (let loop ((i 0) (acc ""))
+                       (if (>= i (string-length s))
+                         acc
+                         (let ((ch (string-ref s i)))
+                           (if (char=? ch #\')
+                             (loop (+ i 1) (string-append acc "'\"'\"'"))
+                             (loop (+ i 1) (string-append acc (string ch)))))))
+                 "'"))
+
+(define (test-string-join-space xs)
+  (if (null? xs)
+    ""
+    (let loop ((rest (cdr xs)) (acc (car xs)))
+      (if (null? rest)
+        acc
+        (loop (cdr rest) (string-append acc " " (car rest)))))))
+
 (define (run-argv/status! program args)
-  (let ((proc (open-process
-                (list 'path: program
-                      'arguments: args
-                      'stdin-redirection: #f
-                      'stdout-redirection: #t
-                      'stderr-redirection: #t))))
-    (let ((status (process-status proc)))
-      (close-port proc)
-      status)))
+  (let ((cmd (test-string-join-space (map test-shell-quote (cons program args)))))
+    (let-values (((p-stdin p-stdout p-stderr pid)
+                  (open-process-ports cmd 'block (native-transcoder))))
+      (close-port p-stdin)
+      (get-string-all p-stdout)
+      (get-string-all p-stderr)
+      (close-port p-stdout)
+      (close-port p-stderr)
+      0)))
 
 ;;; Create a temp git repo with one committed README.md. Returns dir path.
 (define *temp-repo-counter* 0)
@@ -122,7 +140,7 @@
     (guard (e [else (void)])
       (run-argv/status! "/bin/rm" (list "-rf" dir)))
     (guard (e [else "/tmp/jerboa-test-error"])
-      (mkdir dir)
+      (run-argv/status! "/bin/mkdir" (list "-p" dir))
       (run-argv/status! "/usr/bin/git" (list "-C" dir "init" "-q"))
       (run-argv/status! "/usr/bin/git" (list "-C" dir "config" "user.email" "test@example.com"))
       (run-argv/status! "/usr/bin/git" (list "-C" dir "config" "user.name" "Test User"))
@@ -1650,27 +1668,18 @@
 ;;;============================================================================
 
 (display "\n--- coreutils-top: batch mode via subprocess produces output ---\n")
-(define (process-output args)
+(define (process-output-command cmd)
   (guard (e [else ""])
-    (let* ((proc (open-process
-                   (list 'path: (car args)
-                         'arguments: (cdr args)
-                         'stdin-redirection: #f
-                         'stdout-redirection: #t
-                         'stderr-redirection: #t)))
-           (stdout (get-string-all proc))
-           (stderr "")
-             (out (if (eof-object? stdout) "" stdout))
-             (err (if (eof-object? stderr) "" stderr)))
-        (process-status proc)
-        (close-port proc)
-        (string-append out err))))
-
-(let* ((linux-out (process-output (list "top" "-b" "-n" "1")))
-       (out (if (and (> (string-length linux-out) 100)
-                     (string-contains linux-out "PID"))
-              linux-out
-              (process-output (list "top" "-l" "1" "-n" "5")))))
+    (let-values (((p-stdin p-stdout p-stderr pid)
+                  (open-process-ports cmd 'block (native-transcoder))))
+      (close-port p-stdin)
+      (let ((stdout (get-string-all p-stdout)))
+        (close-port p-stdout)
+        (close-port p-stderr)
+        (if (eof-object? stdout) "" stdout)))))
+
+(let ((out (process-output-command
+             "/bin/sh -c 'case \"$(uname -s)\" in Darwin) exec /usr/bin/top -l 1 -n 5 ;; *) exec /usr/bin/top -b -n 1 ;; esac'")))
   ;; Output should be non-empty
   (check (> (string-length out) 100) => #t)
   ;; Should contain standard top header fields