security: add tcb release evidence

Jaime Fournier <jaimef@linbsd.org>

de890de477a3aea3f124d8c4eb3cf46ef7f49591

diff --git a/Makefile b/Makefile
index 6d497d2..91a0dc4 100644
--- a/Makefile
+++ b/Makefile
@@ -76,7 +76,7 @@ TYPED_LLVMIR_PARITY_FN ?= sample_typed_llvmir_smoke::main
 LLVM_BIN ?= $(shell if command -v llvm-as >/dev/null 2>&1; then dirname "$$(command -v llvm-as)"; elif [ -x /opt/homebrew/opt/llvm/bin/llvm-as ]; then echo /opt/homebrew/opt/llvm/bin; elif [ -x /usr/local/opt/llvm/bin/llvm-as ]; then echo /usr/local/opt/llvm/bin; fi)
 TYPED_WRAPPER_DIR ?= build/typed/jerboa
 
-.PHONY: help chez static-supported-check chez-cross build binary binary-typed binary-typed-smoke binary-cross native-cross pure-audit typecheck typed-rust typed-llvmir typed-llvmir-check typed-llvmir-smoke typed-llvmir-parity typed-wrappers typed-build typed-wrapper-smoke typed-split-tree-smoke typed-test typed-clean test test-known-flaky test-reader test-core test-runtime test-try-debug test-stdlib test-ffi test-modules test-expanded test-contract test-ergo test-sqlite-robustness test-limits-primitives test-typed-core test-typed-parser test-typed-checker test-typed-rust test-typed-kotlin test-typed-llvmir test-typed-wrappers test-pure-audit test-features test-wrappers test-phase4a test-phase4b test-phase4c test-phase4d test-phase4e test-phase4f test-phase5 test-phase5e test-phase6 test-phase7 test-phase8 test-functional test-repl test-security test-security-profile test-native test-gaps native clean-native audit audit-native jpkg-audit import-policy-check source-balance restrict-closure-check security-audit clean security security-production security-profile security-hardware-smoke sbom reproducibility-report reproducibility-compare verify release-evidence fuzz fuzz-smoke fuzz-deep fuzz-reader-fuzz fuzz-json-fuzz fuzz-http2-fuzz fuzz-dns-fuzz fuzz-pregexp-fuzz fuzz-csv-fuzz fuzz-base64-fuzz fuzz-hex-fuzz fuzz-uri-fuzz fuzz-format-fuzz fuzz-router-fuzz fuzz-sandbox-fuzz test-rawstring test-regex test-rx test-peg test-regex-all check-docs check-docs-strict data-check docker-build docker-push lint
+.PHONY: help chez static-supported-check chez-cross build binary binary-typed binary-typed-smoke binary-cross native-cross pure-audit typecheck typed-rust typed-llvmir typed-llvmir-check typed-llvmir-smoke typed-llvmir-parity typed-wrappers typed-build typed-wrapper-smoke typed-split-tree-smoke typed-test typed-clean test test-known-flaky test-reader test-core test-runtime test-try-debug test-stdlib test-ffi test-modules test-expanded test-contract test-ergo test-sqlite-robustness test-limits-primitives test-typed-core test-typed-parser test-typed-checker test-typed-rust test-typed-kotlin test-typed-llvmir test-typed-wrappers test-pure-audit test-features test-wrappers test-phase4a test-phase4b test-phase4c test-phase4d test-phase4e test-phase4f test-phase5 test-phase5e test-phase6 test-phase7 test-phase8 test-functional test-repl test-security test-security-profile test-native test-gaps native clean-native audit audit-native jpkg-audit tcb-drift-check import-policy-check source-balance restrict-closure-check security-audit clean security security-production security-profile security-hardware-smoke sbom reproducibility-report reproducibility-compare verify release-evidence fuzz fuzz-smoke fuzz-deep fuzz-reader-fuzz fuzz-json-fuzz fuzz-http2-fuzz fuzz-dns-fuzz fuzz-pregexp-fuzz fuzz-csv-fuzz fuzz-base64-fuzz fuzz-hex-fuzz fuzz-uri-fuzz fuzz-format-fuzz fuzz-router-fuzz fuzz-sandbox-fuzz test-rawstring test-regex test-rx test-peg test-regex-all check-docs check-docs-strict data-check docker-build docker-push lint
 .PHONY: check-cross-tools docker fuzz-websocket-fuzz jlsp jlsp-freebsd-amd64 \
         jlsp-install jlsp-linux-amd64 jlsp-portable jmcp-freebsd-amd64 \
         jmcp-freebsd-arm64 jmcp-linux-amd64 jmcp-linux-arm64 \
@@ -616,6 +616,8 @@ release-evidence:
 	$(MAKE) unification-release-check > "$(EVIDENCE_DIR)/unification-release-check.txt" 2>&1
 	$(MAKE) security-hardware-smoke > "$(EVIDENCE_DIR)/security-hardware-smoke.txt" 2>&1
 	$(MAKE) reproducibility-report > "$(EVIDENCE_DIR)/reproducibility-report.txt" 2>&1
+	$(MAKE) tcb-report > "$(EVIDENCE_DIR)/tcb-report.sexp" 2>&1
+	$(MAKE) tcb-drift-check > "$(EVIDENCE_DIR)/tcb-drift-check.txt" 2>&1
 	support/check-release-path-leaks.sh dist/jerboa > "$(EVIDENCE_DIR)/path-leaks.txt" 2>&1
 	JERBOA_RELEASE_DIR="$(RELEASE_DIR)" JERBOA_SIGNING_EVIDENCE_DIR="$(EVIDENCE_DIR)/signing" support/sign-release-artifacts.sh evidence > "$(EVIDENCE_DIR)/signing-evidence.txt" 2>&1
 	$(MAKE) sbom > "$(EVIDENCE_DIR)/sbom.txt" 2>&1
@@ -625,6 +627,7 @@ release-evidence:
 		support/install.sh support/ensure-jerboa.sh support/sign-release-artifacts.sh \
 		support/sbom.sh support/sanitize-evidence.sh support/fasl-cache-equivalence.ss \
 		support/check-release-path-leaks.sh support/reproducibility-report.sh tools/security-audit.sh \
+		tools/tcb-report.ss .jerboa-system \
 		> "$(EVIDENCE_DIR)/release-inputs-sha256.txt"
 	rm -rf "$(EVIDENCE_DIR)/sbom" "$(EVIDENCE_DIR)/reproducibility"
 	cp -R "$(SBOM_DIR)" "$(EVIDENCE_DIR)/sbom"
@@ -633,6 +636,7 @@ release-evidence:
 	grep -q '^path_leak_status=pass$$' "$(EVIDENCE_DIR)/path-leaks.txt"
 	grep -q '^status=match$$' "$(EVIDENCE_DIR)/reproducibility/result.txt"
 	grep -q '^sbom_status=present$$' "$(EVIDENCE_DIR)/sbom/manifest.txt"
+	grep -q '^(tcb-report' "$(EVIDENCE_DIR)/tcb-report.sexp"
 	support/sanitize-evidence.sh "$(EVIDENCE_DIR)"
 	printf 'release_evidence_status=pass\n' > "$(EVIDENCE_DIR)/result.txt"
 
@@ -1738,6 +1742,19 @@ audit-native:
 jpkg-audit: build
 	@$(SCHEME) --libdirs $(LIBDIRS) --script tools/jpkg-main.ss audit
 
+tcb-drift-check:
+	@set -eu; \
+	if git diff --quiet -- .jerboa-system support/container-dependencies.lock \
+		support/*.c support/*.h jerboa-native-rs/Cargo.lock browser-repl/Cargo.lock \
+		vendor/jsqlite; then \
+		echo "tcb-drift: OK"; \
+	elif git diff --quiet -- data/changelog.sexp; then \
+		echo "tcb-drift: TCB-affecting files changed; add a data/changelog.sexp note" >&2; \
+		exit 1; \
+	else \
+		echo "tcb-drift: TCB-affecting change has changelog evidence"; \
+	fi
+
 security-audit:
 	@tools/security-audit.sh
 
@@ -1758,7 +1775,7 @@ unsafe-deserialize-check:
 import-policy-check: build
 	@$(SCHEME) --libdirs $(LIBDIRS) --script tools/check-import-policy.ss examples
 
-audit: source-balance restrict-closure-check unsafe-deserialize-check import-policy-check security-audit audit-native jpkg-audit data-check security-production unification-check
+audit: source-balance restrict-closure-check unsafe-deserialize-check import-policy-check security-audit audit-native jpkg-audit tcb-drift-check data-check security-production unification-check
 
 test-gaps:
 	$(SCHEME) --libdirs $(LIBDIRS) --script tests/test-gaps.ss
diff --git a/docs/kimi3-security-recommmendations.md b/docs/kimi3-security-recommmendations.md
index c9a3243..d8c7763 100644
--- a/docs/kimi3-security-recommmendations.md
+++ b/docs/kimi3-security-recommmendations.md
@@ -880,6 +880,15 @@ shims, each Rust crate + its `unsafe` count, vendored C like
 `support/container-dependencies.lock`). Gate: TCB size growth requires a
 changelog note. `Philosophy.md` open tension #6 asks for exactly this.
 
+- **Status:** complete for release evidence and drift gating. `make
+  release-evidence` now writes `dist/release-evidence/tcb-report.sexp`, hashes
+  `.jerboa-system` and `tools/tcb-report.ss` with the release inputs, and checks
+  that the report is present. `make audit` runs `tcb-drift-check`, which fails
+  TCB-affecting changes to `.jerboa-system`, C shims, native lockfiles,
+  `support/container-dependencies.lock`, or `vendor/jsqlite` unless
+  `data/changelog.sexp` also changes. `docs/release-security.md` documents the
+  evidence file and changelog gate.
+
 ### K3-P2-04 — Standing AI red-team practice
 **Serves:** G3. **Effort:** ongoing; 2 days to bootstrap.
 
diff --git a/docs/release-security.md b/docs/release-security.md
index 18ee170..41d03ec 100644
--- a/docs/release-security.md
+++ b/docs/release-security.md
@@ -136,9 +136,15 @@ status fields for each image, the selected match mode, `bundle_status=match`,
 
 `make release-evidence` archives the production audit, multicall smoke,
 release unification check, hardware-hardening smoke, signing evidence, SBOM,
-reproducibility report, release-input hashes, git state, and host-neutral build
-environment under `dist/release-evidence/`. The evidence target sanitizes and
-fails closed on private path, SSH remote, host, or `uname -a` markers.
+reproducibility report, TCB report, release-input hashes, git state, and
+host-neutral build environment under `dist/release-evidence/`. The evidence
+target sanitizes and fails closed on private path, SSH remote, host, or
+`uname -a` markers.
+`tcb-report.sexp` records the declared TCB, vendored dependencies, native
+crates, C shims, external tools, and current FFI-site count. The audit gate
+also runs `tcb-drift-check`, which requires a `data/changelog.sexp` note when
+TCB-affecting manifests, C shims, native lockfiles, container locks, or
+vendored C inputs change.
 It also runs `support/check-release-path-leaks.sh` against `dist/jerboa` and
 requires `path_leak_status=pass` in `path-leaks.txt`. Release packaging runs
 the same checker against the final `.tar.gz`, so shipped artifacts cannot