Security hardening and release readiness

ober

c12aa89ee863ae8750496cd651c380156064c135

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..9c4ed70
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,40 @@
+name: CI
+
+on:
+  push:
+    branches: [main, master]
+  pull_request:
+  workflow_dispatch:
+
+permissions:
+  contents: read
+
+env:
+  JERBOA_VERSION: v0.2.3
+  JERBUILD: ${{ github.workspace }}/.jerboa/bin/jerbuild
+
+jobs:
+  verify:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v4
+
+      - name: Install system tools
+        run: |
+          set -eu
+          sudo apt-get update
+          sudo apt-get install -y --no-install-recommends build-essential git curl ca-certificates
+          command -v cargo
+          cargo --version
+
+      - name: Install Jerboa toolchain
+        run: sh support/ensure-jerboa.sh "$JERBOA_VERSION" .jerboa/bin
+
+      - name: Install cargo-audit
+        run: cargo install cargo-audit --locked
+
+      - name: Verify
+        run: make verify
+
+      - name: Release evidence
+        run: make release-evidence
diff --git a/.github/workflows/security-baseline.yml b/.github/workflows/security-baseline.yml
new file mode 100644
index 0000000..28a713e
--- /dev/null
+++ b/.github/workflows/security-baseline.yml
@@ -0,0 +1,35 @@
+name: Security Baseline
+
+on:
+  push:
+    branches: [main, master]
+  pull_request:
+  workflow_dispatch:
+
+permissions:
+  contents: read
+
+jobs:
+  baseline:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v4
+
+      - name: Required release files
+        run: |
+          set -eu
+          test -f LICENSE
+          test -f SECURITY.md
+          test -f .gitignore
+          find . -maxdepth 1 -iname "README*" -type f | grep -q .
+
+      - name: High-confidence secret scan
+        run: |
+          set -eu
+          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})"
+          matches="$(git grep -n -I -E "$pattern" -- . ":!*.png" ":!*.jpg" ":!*.jpeg" ":!*.gif" ":!*.so" ":!*.dylib" ":!*.o" ":!*.a" ":!*.boot" ":!*.tar.gz" || true)"
+          if [ -n "$matches" ]; then
+            echo "$matches"
+            echo "High-confidence secret pattern found."
+            exit 1
+          fi
diff --git a/.gitignore b/.gitignore
index 05ee0cd..f0f9ab0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -18,3 +18,6 @@ signal_tui_shim.so
 signal_log_shim.dylib
 signal_log_shim.so
 *.db
+*.trace
+/trace
+/dist/
diff --git a/.jerboa/security.json b/.jerboa/security.json
index 808f3ec..4aa9ee0 100644
--- a/.jerboa/security.json
+++ b/.jerboa/security.json
@@ -3,7 +3,7 @@
   "repo": "jerboa-signal",
   "extends": ["jerboa:cli", "jerboa:network-client", "jerboa:crypto"],
   "paths": {
-    "production": ["*.ss", "*.sls", "lib/**/*.ss", "lib/**/*.sls", "src/**/*.{ss,sls,c,h,rs}", "Makefile"],
+    "production": ["*.ss", "*.sls", "signal/**/*.{ss,sls,c,h}", "scripts/**/*.sh", "Makefile"],
     "tests": ["test/**", "tests/**", "**/*-test.ss"],
     "generated": ["build/**", "dist/**", "target/**", "*.so", "*.dylib", "*.wpo"],
     "vendor": ["vendor/**", "third_party/**"],
@@ -12,9 +12,13 @@
   "policy": {
     "failOn": ["critical", "high"],
     "imports": { "directChezscheme": "deny" },
-    "ffi": { "allowed": false },
+    "ffi": { "allowed": true, "requireLazyLoading": true, "requireBoundaryDocs": true },
     "process": { "shellInterpolation": "deny" },
     "network": { "requireTimeouts": true },
+    "releaseEvidence": {
+      "targetLiveProof": "marker-validated-fail-closed",
+      "signalCliAdvisoryReview": "version-mismatch-fail-closed"
+    },
     "eval": { "stringEval": "deny", "bareRead": "deny", "allowReadEval": false },
     "secrets": { "forbidLogging": true, "strictStateFilePermissions": true }
   },
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..261eeb9
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,201 @@
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
diff --git a/Makefile b/Makefile
index 85637b6..bf0d5fe 100644
--- a/Makefile
+++ b/Makefile
@@ -1,8 +1,7 @@
-# jerbuild bundles Chez Scheme, the jerboa stdlib, and the jerboa-native-rs
-# source under ~/.cache/jerbuild/, so building jerboa-signal needs no jerboa
-# source checkout and no separately-built Chez. Cargo is used to build the
-# bundled native archive when it is not already present.
-JERBOA_VERSION ?= v0.2.0
+# jerbuild bundles Chez Scheme and the Jerboa stdlib. When a sibling Jerboa
+# checkout is present, use its patched jerboa-native-rs crate for the encrypted
+# log backend; otherwise fall back to the native crate bundled with jerbuild.
+JERBOA_VERSION ?= v0.2.3
 JERBOA_TOOL_DIR ?= $(CURDIR)/.jerboa/bin
 JERBUILD ?= $(shell if [ -x ./jerbuild ] && [ -x ./jerboa ]; then \
 	printf '%s\n' ./jerbuild; \
@@ -20,9 +19,15 @@ JSQLITE_REPO ?= $(VENDOR)/jsqlite
 JSQLITE_URL ?= https://git.sr.ht/~lisp/jsqlite
 JSQLITE_SRC ?= $(JSQLITE_REPO)/src
 LIBDIRS = --libdirs $(CURDIR):$(JSQLITE_SRC):$(JH)/lib
-DEFAULT_JERBOA_NATIVE_A = $(JH)/jerboa-native-rs/target/release/libjerboa_native.a
+LOCAL_JERBOA_DIR ?= $(CURDIR)/../jerboa
+DEFAULT_JERBOA_NATIVE_MANIFEST = $(if $(wildcard $(LOCAL_JERBOA_DIR)/jerboa-native-rs/Cargo.toml),$(LOCAL_JERBOA_DIR)/jerboa-native-rs/Cargo.toml,$(JH)/jerboa-native-rs/Cargo.toml)
+JERBOA_NATIVE_MANIFEST ?= $(DEFAULT_JERBOA_NATIVE_MANIFEST)
+JERBOA_NATIVE_CRATE_DIR = $(patsubst %/Cargo.toml,%,$(JERBOA_NATIVE_MANIFEST))
+DEFAULT_JERBOA_NATIVE_DIR = $(JERBOA_NATIVE_CRATE_DIR)/target/release
+DEFAULT_JERBOA_NATIVE_A = $(DEFAULT_JERBOA_NATIVE_DIR)/libjerboa_native.a
 JERBOA_NATIVE_A ?= $(DEFAULT_JERBOA_NATIVE_A)
-JERBOA_NATIVE_LIB ?= $(JH)/jerboa-native-rs/target/release/libjerboa_native.$(TUI_SHIM_EXT)
+JERBOA_NATIVE_LIB ?= $(DEFAULT_JERBOA_NATIVE_DIR)/libjerboa_native.$(TUI_SHIM_EXT)
+CARGO_AUDIT ?= $(shell command -v cargo-audit 2>/dev/null || printf '%s/.cargo/bin/cargo-audit' "$$HOME")
 export JERBOA_NATIVE_A
 JEXEC = JERBOA_NATIVE_LIB="$(JERBOA_NATIVE_LIB)" $(JERBUILD) exec $(LIBDIRS)
 BIN := jerboa-signal
@@ -37,8 +42,21 @@ endif
 TUI_SHIM := $(TUI_SHIM_DIR)/signal_tui_shim.$(TUI_SHIM_EXT)
 LOG_SHIM := signal_log_shim.$(TUI_SHIM_EXT)
 SQLCIPHER_PREFIX := $(shell brew --prefix sqlcipher 2>/dev/null)
+DIST_DIR ?= dist/release-evidence
+SBOM_DIR ?= dist/sbom
+REPRO_DIR ?= dist/reproducibility
+SOAK_DIR ?= dist/soak
+BINARY_SMOKE_DIR ?= dist/binary-smoke
+SIGNAL_CLI_ADVISORY_DIR ?= dist/signal-cli-advisory
+SIGNAL_CLI ?= $(shell if [ -x "$(HOME)/.local/bin/signal-cli-jvm" ]; then \
+	printf '%s\n' "$(HOME)/.local/bin/signal-cli-jvm"; \
+elif command -v signal-cli >/dev/null 2>&1; then \
+	command -v signal-cli; \
+else \
+	printf '%s\n' signal-cli; \
+fi)
 
-.PHONY: all build binary run run-tui test install install-log-shim clean help vendor-deps tui-shim log-shim ensure-jerboa-tools ensure-jerboa-native ensure-jsqlite
+.PHONY: all build binary run run-tui test security audit sbom reproducibility-report soak-evidence binary-smoke signal-cli-advisory-check verify release-evidence install install-log-shim clean help vendor-deps tui-shim log-shim ensure-jerboa-tools ensure-jerboa-native ensure-jsqlite
 .DEFAULT_GOAL := help
 
 all: binary
@@ -63,6 +81,108 @@ test: binary
 	$(JEXEC) tests/test-history-replay.ss
 	./$(BIN) --help >/dev/null && echo "smoke ok"
 
+security:
+	sh scripts/security-check.sh
+
+audit: ensure-jerboa-native
+	@mkdir -p "$(DIST_DIR)"
+	@if "$(SIGNAL_CLI)" --version > "$(DIST_DIR)/signal-cli-version.txt" 2>&1; then \
+	  echo "signal_cli=$(SIGNAL_CLI)" >> "$(DIST_DIR)/signal-cli-version.txt"; \
+	else \
+	  echo "signal-cli unavailable via SIGNAL_CLI=$(SIGNAL_CLI); live Signal smoke is a release blocker." > "$(DIST_DIR)/signal-cli-version.txt"; \
+	fi
+	@if [ -x "$(CARGO_AUDIT)" ]; then \
+	  cd "$(JERBOA_NATIVE_CRATE_DIR)" && "$(CARGO_AUDIT)" audit > "$(CURDIR)/$(DIST_DIR)/rustsec-jerboa-native.txt"; \
+	else \
+	  echo "cargo-audit not installed; CI installs it for the native dependency audit gate." > "$(DIST_DIR)/rustsec-jerboa-native.txt"; \
+	fi
+
+sbom:
+	JSIGNAL_SBOM_DIR="$(SBOM_DIR)" \
+	JERBUILD="$(JERBUILD)" \
+	BIN="$(BIN)" \
+	JERBOA_NATIVE_MANIFEST="$(JERBOA_NATIVE_MANIFEST)" \
+	JERBOA_NATIVE_A="$(JERBOA_NATIVE_A)" \
+	JSQLITE_SRC="$(JSQLITE_SRC)" \
+	SIGNAL_CLI="$(SIGNAL_CLI)" \
+	sh scripts/sbom.sh
+
+reproducibility-report:
+	JSIGNAL_REPRO_DIR="$(REPRO_DIR)" \
+	JERBUILD="$(JERBUILD)" \
+	BIN="$(BIN)" \
+	JERBOA_NATIVE_MANIFEST="$(JERBOA_NATIVE_MANIFEST)" \
+	JERBOA_NATIVE_A="$(JERBOA_NATIVE_A)" \
+	JSQLITE_SRC="$(JSQLITE_SRC)" \
+	sh scripts/reproducibility-report.sh
+
+soak-evidence:
+	JSIGNAL_SOAK_DIR="$(SOAK_DIR)" \
+	SIGNAL_CLI="$(SIGNAL_CLI)" \
+	JSIGNAL_TARGET_LIVE_PROOF_FILE="$(JSIGNAL_TARGET_LIVE_PROOF_FILE)" \
+	JSIGNAL_REQUIRE_TARGET_LIVE_PROOF="$(JSIGNAL_REQUIRE_TARGET_LIVE_PROOF)" \
+	sh scripts/soak-evidence.sh
+
+binary-smoke:
+	@if [ ! -x "./$(BIN)" ]; then $(MAKE) --no-print-directory binary; fi
+	JSIGNAL_BINARY_SMOKE_DIR="$(BINARY_SMOKE_DIR)" \
+	BIN="$(BIN)" \
+	sh scripts/binary-smoke.sh
+
+signal-cli-advisory-check:
+	JSIGNAL_SIGNAL_CLI_ADVISORY_DIR="$(SIGNAL_CLI_ADVISORY_DIR)" \
+	SIGNAL_CLI="$(SIGNAL_CLI)" \
+	JSIGNAL_SIGNAL_CLI_REVIEW_PROOF_FILE="$(JSIGNAL_SIGNAL_CLI_REVIEW_PROOF_FILE)" \
+	JSIGNAL_REQUIRE_SIGNAL_CLI_REVIEW_PROOF="$(JSIGNAL_REQUIRE_SIGNAL_CLI_REVIEW_PROOF)" \
+	sh scripts/signal-cli-advisory-check.sh
+
+verify: test security audit signal-cli-advisory-check
+
+release-evidence: verify reproducibility-report sbom soak-evidence binary-smoke
+	@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)"; \
+	  "$(JERBUILD)" --version 2>/dev/null || true; \
+	} > "$(DIST_DIR)/build-environment.txt"
+	@if "$(SIGNAL_CLI)" --version > "$(DIST_DIR)/signal-cli-version.txt" 2>&1; then \
+	  echo "signal_cli=$(SIGNAL_CLI)" >> "$(DIST_DIR)/signal-cli-version.txt"; \
+	else \
+	  echo "signal-cli unavailable via SIGNAL_CLI=$(SIGNAL_CLI); live Signal smoke is a release blocker." > "$(DIST_DIR)/signal-cli-version.txt"; \
+	fi
+	@find signal tests -type f \( -name '*.ss' -o -name '*.c' -o -name '*.h' \) -print | sort | xargs shasum -a 256 > "$(DIST_DIR)/source-sha256.txt"
+	@find vendor/jsqlite/src -type f -name '*.ss' -print | sort | xargs shasum -a 256 > "$(DIST_DIR)/jsqlite-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/release-evidence.md docs/signal-cli-advisory-review.md scripts/security-check.sh scripts/sbom.sh scripts/reproducibility-report.sh scripts/soak-evidence.sh scripts/binary-smoke.sh scripts/signal-cli-advisory-check.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)" > "$(DIST_DIR)/native-linkage.txt"; elif command -v ldd >/dev/null 2>&1; then ldd "$(BIN)" > "$(DIST_DIR)/native-linkage.txt"; else echo "no otool/ldd available" > "$(DIST_DIR)/native-linkage.txt"; fi
+	@if command -v cargo >/dev/null 2>&1 && [ -f "$(JERBOA_NATIVE_MANIFEST)" ]; then \
+	  cargo metadata --format-version 1 --manifest-path "$(JERBOA_NATIVE_MANIFEST)" > "$(DIST_DIR)/cargo-metadata-jerboa-native.json"; \
+	fi
+	@if [ -x "$(CARGO_AUDIT)" ] && [ -f "$(JERBOA_NATIVE_MANIFEST)" ]; then \
+	  cd "$(JERBOA_NATIVE_CRATE_DIR)" && "$(CARGO_AUDIT)" audit > "$(CURDIR)/$(DIST_DIR)/rustsec-jerboa-native.txt"; \
+	else \
+	  echo "cargo-audit not installed or jerboa-native-rs manifest missing; release owner must review before publication." > "$(DIST_DIR)/rustsec-jerboa-native.txt"; \
+	fi
+	@$(MAKE) --no-print-directory signal-cli-advisory-check
+	@rm -rf "$(DIST_DIR)/sbom" "$(DIST_DIR)/reproducibility" "$(DIST_DIR)/soak" "$(DIST_DIR)/binary-smoke" "$(DIST_DIR)/signal-cli-advisory"
+	@cp -R "$(SBOM_DIR)" "$(DIST_DIR)/sbom"
+	@cp -R "$(REPRO_DIR)" "$(DIST_DIR)/reproducibility"
+	@cp -R "$(SOAK_DIR)" "$(DIST_DIR)/soak"
+	@cp -R "$(BINARY_SMOKE_DIR)" "$(DIST_DIR)/binary-smoke"
+	@cp -R "$(SIGNAL_CLI_ADVISORY_DIR)" "$(DIST_DIR)/signal-cli-advisory"
+	@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)/soak/status.txt"; then \
+	  echo "current Signal live-smoke evidence failed; see $(DIST_DIR)/soak/status.txt"; \
+	  exit 1; \
+	fi
+	@echo "release evidence written to $(DIST_DIR)"
+
 install: binary
 	mkdir -p $(BIN_DIR)
 	install -m 0755 $(BIN) $(BIN_DIR)/$(BIN)
@@ -87,6 +207,7 @@ clean:
 	rm -f signal_tui_shim.dylib signal_tui_shim.so
 	rm -f signal_log_shim.dylib signal_log_shim.so
 	find signal \( -name '*.so' -o -name '*.wpo' \) -delete 2>/dev/null || true
+	rm -rf dist
 
 vendor-deps: ensure-jsqlite vendor/termbox2
 
@@ -123,8 +244,8 @@ ensure-jerboa-native: ensure-jerboa-tools
 	    echo "ERROR: JERBOA_NATIVE_A is set but missing: $(JERBOA_NATIVE_A)"; \
 	    exit 1; \
 	  fi; \
-	  if [ ! -f "$(JH)/jerboa-native-rs/Cargo.toml" ]; then \
-	    echo "ERROR: bundled jerboa-native-rs source not found under $(JH)"; \
+	  if [ ! -f "$(JERBOA_NATIVE_MANIFEST)" ]; then \
+	    echo "ERROR: jerboa-native-rs manifest not found: $(JERBOA_NATIVE_MANIFEST)"; \
 	    exit 1; \
 	  fi; \
 	  command -v cargo >/dev/null 2>&1 || { \
@@ -132,7 +253,7 @@ ensure-jerboa-native: ensure-jerboa-tools
 	    exit 1; \
 	  }; \
 	  echo "=== Building Jerboa native archive: $(JERBOA_NATIVE_A) ==="; \
-	  cargo build --release --manifest-path "$(JH)/jerboa-native-rs/Cargo.toml"; \
+	  cargo build --release --manifest-path "$(JERBOA_NATIVE_MANIFEST)"; \
 	fi
 	@test -f "$(JERBOA_NATIVE_A)" || { \
 	  echo "ERROR: missing Jerboa native archive: $(JERBOA_NATIVE_A)"; \
@@ -166,6 +287,14 @@ help:
 	@echo "  run ARGS='<args>'   Run from source via 'jerbuild exec'"
 	@echo "  run-tui             Build shim and start the TUI"
 	@echo "  test                Run tests"
+	@echo "  security            Run local security checks"
+	@echo "  audit               Record signal-cli/native dependency audit status"
+	@echo "  sbom                Write SBOM/provenance evidence under dist/sbom"
+	@echo "  reproducibility-report  Rebuild twice and compare release artifacts"
+	@echo "  soak-evidence       Record Signal live-smoke status without secrets"
+	@echo "  binary-smoke        Start the shipped binary through a no-secret path"
+	@echo "  verify              Run tests, security checks, and audit"
+	@echo "  release-evidence    Write release evidence under dist/release-evidence"
 	@echo "  install             Install ./jerboa-signal to ~/.local/bin"
 	@echo "  install-log-shim    Install SQLCipher shim for legacy logs"
 	@echo "  ensure-jerboa-tools Ensure jerboa/jerbuild are available"
diff --git a/README.md b/README.md
index a7acd91..38b42d9 100644
--- a/README.md
+++ b/README.md
@@ -23,6 +23,8 @@ jerboa-signal tui [-a +PHONE]                      # terminal UI shell
   `scripts/signal-cli-jvm` wrapper uses `~/.local/opt/signal-cli-0.14.5`).
   Older 0.14.4.1 builds drop current sealed-sender receive envelopes with
   `getServerGuid(...) must not be null`.
+  Release evidence currently reviews exactly `signal-cli 0.14.5`; a newer
+  installed version must be reviewed before publication.
 - Jerboa build tools. `make binary`/`make install` use project-local
   `./jerbuild` first, then `.jerboa/bin`, then `PATH`, and download the
   matching release artifact if none are available.
@@ -114,17 +116,33 @@ jerboa-signal --trace-all /tmp/jerboa-signal.raw.trace tui
 ```
 
 That file is sensitive: it can include message content and Signal identifiers.
-When debugging send hangs caused by rewriting a large encrypted log, this mode
-is also useful:
+To force the encrypted log to be rewritten after every row while debugging
+durability issues, use:
 
 ```sh
-jerboa-signal --trace-all /tmp/jerboa-signal.raw.trace --log-persist close tui
+jerboa-signal --trace-all /tmp/jerboa-signal.raw.trace --log-persist immediate tui
 ```
 
-`--log-persist close` defers jsqlite encrypted-container writes until clean
-exit. The default is `immediate`, which persists after every logged message. If
-the process crashes in close-only mode, new log rows from that session may be
-lost.
+The default is `close`, which defers jsqlite encrypted-container writes until
+clean exit so the TUI does not pause while rewriting a large encrypted history.
+`--log-persist immediate` persists after every logged message. If the process
+crashes in close-only mode, new log rows from that session may be lost.
+
+## Security
+
+`jerboa-signal` is not a Signal protocol implementation. It delegates Signal
+cryptography, account state, server communication, and device linking to
+`signal-cli`. The local security boundary is the Jerboa orchestration layer:
+JSON-RPC framing, trace redaction, attachment export, encrypted local message
+logs, terminal UI FFI, and process execution.
+
+Before using this on production accounts, read `SECURITY.md`,
+`docs/threat-model.md`, `docs/credential-handling.md`, and
+`docs/ffi-boundary.md`. Run `make verify` from a clean checkout and preserve
+`make release-evidence` output for release candidates. The release evidence
+includes SBOM/provenance, repeated binary reproducibility, a no-secret binary
+startup smoke, `signal-cli` advisory/version review, and explicit status fields
+for live Signal smokes that require a non-production linked account.
 
 ## Encrypted message log
 
@@ -189,4 +207,4 @@ Modules:
 
 ## License
 
-ISC.
+Apache-2.0.
diff --git a/SECURITY.md b/SECURITY.md
new file mode 100644
index 0000000..459e44d
--- /dev/null
+++ b/SECURITY.md
@@ -0,0 +1,67 @@
+# Security Policy
+
+`jerboa-signal` is a local Signal client wrapper around `signal-cli`. Treat it
+as experimental until a release has clean local and hosted verification evidence.
+
+## Supported Status
+
+No public production-support commitment exists yet. Keep production accounts on
+reviewed private builds until these gates pass from a clean checkout:
+
+- `make verify`
+- `make release-evidence`
+- clean `jerboa_security_scan` for medium-or-higher findings
+- SBOM, repeated reproducibility, no-secret binary-smoke, and explicit
+  live-smoke status evidence from the release host
+- marker-complete redacted target live-smoke proof through
+  `JSIGNAL_TARGET_LIVE_PROOF_FILE` and `JSIGNAL_REQUIRE_TARGET_LIVE_PROOF=1`,
+  or an equivalent reviewed release-host evidence bundle
+- reviewed `signal-cli` version and upstream advisory posture through
+  `make signal-cli-advisory-check`, with required proof enforced by
+  `JSIGNAL_SIGNAL_CLI_REVIEW_PROOF_FILE` and
+  `JSIGNAL_REQUIRE_SIGNAL_CLI_REVIEW_PROOF=1` for production release approval
+- external review of local logging, trace, attachment, process, and FFI paths
+
+## Sensitive Data
+
+This repository handles Signal account identifiers, message bodies,
+attachments, group names, profile/contact metadata, local `signal-cli` account
+state, encrypted log passphrases, and optional raw JSON-RPC traces.
+
+Do not commit Signal databases, traces, attachments, exported logs, passphrases,
+phone numbers from real accounts, or production operational data. The default
+trace mode redacts message bodies and raw RPC frames; `--trace-all` is sensitive
+and must stay local.
+
+## Local Storage
+
+Encrypted message logs live under `~/.local/share/jerboa-signal/` by default.
+They are encrypted through the Jerboa native crypto backend and jsqlite
+container format, but filenames, file existence, backups, and crash artifacts
+can still reveal account activity. Use local disk encryption and restrict
+backups for Signal client hosts.
+
+`JERBOA_SIGNAL_DB_KEY` is accepted for unattended sessions but exposes the log
+passphrase through the process environment. Prefer interactive prompting or a
+supervisor secret store.
+
+## External Dependencies
+
+Signal protocol security is delegated to `signal-cli` and its
+`libsignal-service-java` dependency chain. Release candidates must record the
+exact `signal-cli --version`, Java/runtime source, and advisory review status.
+The dated local review currently accepts `signal-cli 0.14.5`; any different
+installed `signal-cli` version requires a refreshed review before publication.
+Linked-account and send/receive smoke tests must use a non-production account,
+and evidence must not preserve raw account identifiers or message contents.
+Missing, empty, or marker-incomplete required target live-smoke proof fails
+closed as `status=blocked-target-proof`.
+
+The Jerboa native crypto archive is used for the encrypted log container.
+Release candidates must audit the bundled `jerboa-native-rs` dependencies.
+
+## Reporting
+
+Before public release, report issues privately to the repository owner. After
+public release, replace this section with a dedicated advisory contact,
+supported versions, and disclosure window.
diff --git a/docs/credential-handling.md b/docs/credential-handling.md
new file mode 100644
index 0000000..9bd91ce
--- /dev/null
+++ b/docs/credential-handling.md
@@ -0,0 +1,60 @@
+# jerboa-signal Credential Handling
+
+`jerboa-signal` should not store Signal account secrets itself. Account linking,
+identity keys, sessions, and Signal service credentials are owned by
+`signal-cli`.
+
+## Signal Account State
+
+- Link accounts with `signal-cli link` outside this repository.
+- Keep the `signal-cli` data directory owned by the local user and inaccessible
+  to other users.
+- Do not copy production `signal-cli` data into fixtures, traces, release
+  evidence, or support bundles.
+- Use a dedicated non-production linked device for CI/manual release smoke.
+
+## Log Passphrases
+
+The encrypted message log prompts for a passphrase before the TUI starts. Leave
+the prompt blank to disable logging for a session.
+
+`JERBOA_SIGNAL_DB_KEY` is supported for unattended runs. It is convenient but
+less private because environment variables may be visible to supervisors,
+diagnostics, shell history wrappers, or crash reports. Production deployments
+should prefer interactive entry or a protected service secret store.
+
+Rotate the log passphrase by creating a new encrypted log and importing only the
+history you still need. Treat old `.sqlcipher.bak` migration backups as
+sensitive until deleted.
+
+## Traces And Logs
+
+Default tracing records timing, byte counts, backend selection, and failure
+summaries without raw message bodies or RPC frames.
+
+`--trace-all` and `JERBOA_SIGNAL_TRACE_SENSITIVE=1` can include message bodies,
+Signal identifiers, raw envelopes, stderr from `signal-cli`, and operational
+state. Use them only on trusted machines, write them to mode `0600` files, and
+delete them after debugging.
+
+## Attachments
+
+Attachments are copied from the local `signal-cli` attachment cache to
+`~/Downloads/jerboa-signal/` or `JERBOA_SIGNAL_DOWNLOAD_DIR`.
+
+Destination names are sanitized and never overwrite existing files, but the
+exported content is plaintext. Keep the download root outside synced or shared
+directories unless that is intentional.
+
+## Release Checklist
+
+- `make verify` passes.
+- No traces, logs, attachments, databases, or real account identifiers are
+  present in the working tree.
+- `dist/release-evidence/signal-cli-version.txt` records the external client
+  version or explicitly records that no live Signal smoke was run.
+- `dist/release-evidence/signal-cli-advisory/status.txt` records the reviewed
+  upstream `signal-cli` version/advisory posture, and any required proof file is
+  marker-complete and redacted.
+- The first public release has a history secret scan or an intentional history
+  reset.
diff --git a/docs/ffi-boundary.md b/docs/ffi-boundary.md
new file mode 100644
index 0000000..2ef8e5e
--- /dev/null
+++ b/docs/ffi-boundary.md
@@ -0,0 +1,48 @@
+# jerboa-signal FFI Boundary
+
+`jerboa-signal` has three native-adjacent surfaces: bundled Jerboa native crypto,
+the termbox2 TUI shim, and the optional legacy SQLCipher log shim.
+
+## Jerboa Native Crypto
+
+The encrypted jsqlite log container uses `jerboa_scrypt`, `jerboa_aead_seal`,
+`jerboa_aead_open`, and `jerboa_last_error` from the bundled Jerboa native
+archive or dynamic library.
+
+- Native loading is lazy through `(jerboa ffi)` `load-shared-object*`.
+- Importing the log module must not load or require a dynamic library.
+- Scheme passes bytevectors and explicit lengths for key derivation and AEAD.
+- Output lengths return through Scheme-managed bytevectors, not `foreign-alloc`.
+- Release evidence must include RustSec output or a documented local
+  `cargo-audit` gap for `jerboa-native-rs`.
+
+## termbox2 TUI Shim
+
+The TUI shim wraps termbox2 event polling and drawing.
+
+- The last-event buffer is thread-local, not process-global.
+- Blocking event polling uses a collect-safe foreign call where the runtime
+  surface supports it.
+- Terminal text remains local to the user's terminal. It can still contain
+  message text and contact names, so terminal scrollback is sensitive.
+- Release candidates should smoke-test `make tui-shim` on every supported OS.
+
+## Legacy SQLCipher Shim
+
+The SQLCipher shim exists only to migrate older logs.
+
+- Loading is lazy and optional.
+- The new jsqlite container is preferred for new logs.
+- Migration writes a new encrypted jsqlite file, verifies the row count, and
+  preserves the old SQLCipher database as a sensitive backup.
+- Release candidates with legacy migration support should build the shim on a
+  host with SQLCipher installed and record native linkage.
+
+## Do Not Do
+
+- Do not add top-level `load-shared-object` calls.
+- Do not store native handles in untracked global mutable state without
+  ownership rules.
+- Do not pass Scheme strings with embedded NUL bytes into native path or command
+  surfaces.
+- Do not add custom Signal crypto or protocol parsing below `signal-cli`.
diff --git a/docs/release-evidence.md b/docs/release-evidence.md
new file mode 100644
index 0000000..fe4a17e
--- /dev/null
+++ b/docs/release-evidence.md
@@ -0,0 +1,111 @@
+# jerboa-signal Release Evidence
+
+Run from a clean checkout:
+
+```sh
+make release-evidence
+```
+
+The target runs `make verify`, then writes ignored artifacts under
+`dist/release-evidence/`.
+
+`make release-evidence` also refreshes:
+
+- `make reproducibility-report`, which builds the standalone binary twice with a
+  fixed Jerbuild object directory and compares the binary, generated inputs,
+  selected native archive input, and source manifest.
+- `make sbom`, which records source/release input hashes, Jerboa toolchain
+  identity, `signal-cli` version status, jsqlite source status, and selected
+  `jerboa-native-rs` metadata.
+- `make binary-smoke`, which starts the shipped binary through the no-secret
+  `--help` path and records macOS code-signature status when available.
+- `make signal-cli-advisory-check`, which records the reviewed upstream
+  `signal-cli` release, local `signal-cli --version`, GitHub advisory posture,
+  and optional reviewed proof material.
+- `make soak-evidence`, which records explicit live-smoke status fields without
+  storing Signal account identifiers, message contents, traces, or attachment
+  paths.
+
+## Evidence Files
+
+- `git-status.txt` and `git-commit.txt`
+- `build-environment.txt`
+- `signal-cli-version.txt` from `SIGNAL_CLI` or the same
+  `~/.local/bin/signal-cli-jvm` preference used by the runtime
+- `source-sha256.txt`
+- `jsqlite-sha256.txt`
+- `release-inputs-sha256.txt`
+- `binary-sha256.txt`
+- `native-linkage.txt`
+- `cargo-metadata-jerboa-native.json` when Cargo is available
+- `rustsec-jerboa-native.txt`
+- `sbom/`
+- `signal-cli-advisory/`
+- `reproducibility/`
+- `binary-smoke/`
+- `soak/`
+
+## Review Notes
+
+`signal-cli-version.txt` may state that `signal-cli` was unavailable through
+`SIGNAL_CLI` on the build host. That is acceptable for local development, but
+production release requires a separate linked non-production test-account smoke
+on the supported target OS. Run the redacted account-count smoke with
+`JSIGNAL_RUN_LINKED_SMOKE=1 make soak-evidence`; the script must not preserve
+raw `signal-cli listAccounts` output.
+
+Production release also requires a current `signal-cli` upstream advisory and
+version review. The local dated review currently accepts `signal-cli 0.14.5` and
+records GitHub's 2026-06-23 release/advisory posture in
+`docs/signal-cli-advisory-review.md`. If a different `signal-cli` version is
+installed, `make signal-cli-advisory-check` fails with
+`status=blocked-version-review-required`.
+
+External release-owner review proof can be attached as a redacted marker file:
+
+```sh
+JSIGNAL_SIGNAL_CLI_REVIEW_PROOF_FILE=/path/to/signal-cli-review.txt \
+JSIGNAL_REQUIRE_SIGNAL_CLI_REVIEW_PROOF=1 \
+make signal-cli-advisory-check
+```
+
+The proof file is marker-validated and must contain:
+
+- `signal_cli_review_status=reviewed`
+- `signal_cli_latest_release=v0.14.5`
+- `signal_cli_security_advisory_status=no-published-github-advisories`
+- `upstream_security_policy_status=no-upstream-security-policy`
+- `release_signature_status=github-verified-release`
+- `signal_cli_update_window_status=within-reviewed-window`
+- `sensitive_artifact_policy=no-signal-account-state-or-message-data`
+
+Missing, empty, marker-incomplete, or sensitive-looking required proof fails
+closed with `status=blocked-review-proof`. Accepted proof is copied to
+`signal-cli-advisory/signal-cli-review-proof.txt` with a SHA-256 sidecar.
+
+Release-host live-smoke proof can also be attached as a reviewed, redacted proof
+file:
+
+```sh
+JSIGNAL_TARGET_LIVE_PROOF_FILE=/path/to/target-live-proof.txt \
+JSIGNAL_REQUIRE_TARGET_LIVE_PROOF=1 \
+make soak-evidence
+```
+
+The proof file is marker-validated and must contain:
+
+- `linked_account_status=target-evidence-recorded`
+- `send_receive_status=target-evidence-recorded`
+- `trace_redaction_live_status=target-evidence-recorded`
+- `attachment_export_live_status=target-evidence-recorded`
+- `production_smoke_status=current-run-recorded`
+- `production_min_seconds_met=1`
+- `sensitive_artifact_policy=no-raw-account-traces-attachments-or-message-bodies`
+
+Missing, empty, or marker-incomplete required proofs fail closed with
+`status=blocked-target-proof`. Accepted proofs are copied to
+`soak/target-live-proof.txt` with a SHA-256 sidecar.
+
+Do not publish `dist/release-evidence/` if it was produced from a working tree
+containing real Signal traces, message logs, attachments, or production account
+state.
diff --git a/docs/signal-cli-advisory-review.md b/docs/signal-cli-advisory-review.md
new file mode 100644
index 0000000..bfb4707
--- /dev/null
+++ b/docs/signal-cli-advisory-review.md
@@ -0,0 +1,55 @@
+# signal-cli Advisory And Version Review
+
+Review date: 2026-06-23
+
+`jerboa-signal` delegates Signal protocol, account state, and service
+compatibility to upstream `signal-cli`. A production release must therefore
+record the exact `signal-cli` version and the upstream advisory posture used for
+the release.
+
+## Reviewed Upstream State
+
+- Upstream project: <https://github.com/AsamK/signal-cli>
+- Reviewed release page: <https://github.com/AsamK/signal-cli/releases>
+- Reviewed GitHub security page: <https://github.com/AsamK/signal-cli/security>
+- Reviewed latest release: `v0.14.5`
+- Reviewed release date: 2026-06-11
+- Reviewed release commit shown by GitHub: `6bef205`
+- GitHub release signature status shown by GitHub: verified
+- GitHub security-advisory status shown by GitHub: no published advisories
+- Upstream security-policy status shown by GitHub: no `SECURITY.md` policy
+  configured
+
+The lack of a published GitHub advisory is not a guarantee that the dependency
+is free of vulnerabilities. It is a dated release-owner review signal. The lack
+of an upstream security policy means Jerboa release owners must keep this
+dependency private until an explicit update and vulnerability-monitoring process
+is in place.
+
+## Release Requirements
+
+`make signal-cli-advisory-check` records:
+
+- local `signal-cli --version` output when available;
+- whether the local version matches the reviewed upstream release;
+- the dated upstream advisory/security-policy posture above;
+- optional reviewed proof material supplied through
+  `JSIGNAL_SIGNAL_CLI_REVIEW_PROOF_FILE`.
+
+If a local `signal-cli` binary is present and its version is not the reviewed
+version, the check fails with `status=blocked-version-review-required`.
+
+If `JSIGNAL_REQUIRE_SIGNAL_CLI_REVIEW_PROOF=1` is set, the proof file must be
+non-empty and marker-complete. Required markers are:
+
+- `signal_cli_review_status=reviewed`
+- `signal_cli_latest_release=v0.14.5`
+- `signal_cli_security_advisory_status=no-published-github-advisories`
+- `upstream_security_policy_status=no-upstream-security-policy`
+- `release_signature_status=github-verified-release`
+- `signal_cli_update_window_status=within-reviewed-window`
+- `sensitive_artifact_policy=no-signal-account-state-or-message-data`
+
+The proof file must not contain Signal account state, phone numbers, message
+bodies, raw traces, attachments, private keys, API tokens, or other operational
+secrets. Accepted proof is copied into release evidence with a SHA-256 sidecar.
diff --git a/docs/threat-model.md b/docs/threat-model.md
new file mode 100644
index 0000000..b6d355a
--- /dev/null
+++ b/docs/threat-model.md
@@ -0,0 +1,73 @@
+# jerboa-signal Threat Model
+
+`jerboa-signal` is a local Signal client wrapper. It is not the protocol TCB:
+Signal transport, device linking, account state, and end-to-end cryptography are
+delegated to `signal-cli`.
+
+## Assets
+
+- Signal account identifiers, phone numbers, UUIDs, profile names, group IDs,
+  group names, and contact metadata.
+- Message bodies, attachments, reactions, edits, deletes, receipts, calls, and
+  typing events.
+- Local `signal-cli` account state under the user's Signal CLI data directory.
+- Encrypted jsqlite message logs, legacy SQLCipher logs, log backups, WAL/SHM
+  sidecars, removed-conversation state, theme files, and attachment exports.
+- Log passphrases from interactive input or `JERBOA_SIGNAL_DB_KEY`.
+- Optional trace files, especially `--trace-all` raw RPC traces.
+
+## Trust Boundaries
+
+- Jerboa starts and drives `signal-cli jsonRpc` as a subprocess. Treat stdout,
+  stderr, and every JSON-RPC frame as hostile input.
+- The terminal UI crosses into a termbox2 C shim for rendering and input.
+- Encrypted logs cross into the bundled Jerboa native crypto backend and jsqlite
+  parser/writer.
+- Legacy log migration can cross into an optional SQLCipher shim.
+- Attachment export copies files from `signal-cli`'s attachment directory into a
+  user-visible download tree.
+
+## Entry Points
+
+- CLI args: `send`, `listen`, `tui`, `migrate-log`, `--trace`, `--trace-all`,
+  `--log-persist`, and `-a`.
+- Environment variables: `JERBOA_SIGNAL_CLI`, `JERBOA_SIGNAL_CLI_LOG`,
+  `JERBOA_SIGNAL_CLI_VERBOSE`, `JERBOA_SIGNAL_TRACE`,
+  `JERBOA_SIGNAL_TRACE_SENSITIVE`, `JERBOA_SIGNAL_DB_KEY`,
+  `JERBOA_SIGNAL_LOG_BACKEND`, `JERBOA_SIGNAL_LOG_PERSIST`,
+  `JERBOA_SIGNAL_DOWNLOAD_DIR`, and `JERBOA_SIGNAL_CLI_DATA_DIR`.
+- Local files: `signal-cli` account state, attachments, encrypted logs,
+  migration backups, trace files, removed-conversation state, and themes.
+- Native libraries: bundled Jerboa native crypto, optional termbox2 shim, and
+  optional legacy SQLCipher shim.
+
+## Required Mitigations
+
+- Do not implement Signal protocol cryptography in this repo. Delegate to
+  reviewed `signal-cli` and record the version in release evidence.
+- Use argv process APIs for `signal-cli` execution. Do not construct shell
+  command strings from contacts, messages, paths, or account identifiers.
+- Keep traces redacted by default. Raw traces must be opt-in and marked
+  sensitive in docs and release notes.
+- Write state and trace files through checked paths and safe wrappers. State
+  files created by this app must use mode `0600` when they contain account or
+  preference state.
+- Sanitize attachment destination directories and filenames, never overwrite
+  existing downloads, and keep exports under an explicit user download root.
+- Load native libraries lazily through `(jerboa ffi)` so import and static
+  binary startup do not crash before user policy can run.
+- Keep C shim mutable event buffers thread-local or caller-owned.
+
+## Residual Production Blockers
+
+- Hosted CI evidence for Linux, including `signal-cli` installation and CLI
+  smoke tests against a non-production linked test account.
+- Upstream `signal-cli` advisory review and documented supported version range.
+- History secret scan before first public release.
+- Target-host linked-account, send/receive, trace-redaction, and attachment