Security hardening and release readiness

ober

b51a90382e0ed83c069eaca3f8eb4d13ec495c5b

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..dfaf894
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,41 @@
+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:
+  build-test-audit:
+    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 curl ca-certificates pkg-config libssl-dev openssl
+
+      - name: Install Jerboa toolchain
+        run: sh support/ensure-jerboa.sh "$JERBOA_VERSION" .jerboa/bin
+
+      - name: Test
+        run: make test
+
+      - name: Audit native linkage
+        run: make audit
+
+      - name: Generate SBOM/platform notes
+        run: make sbom
+
+      - name: Check OpenSSL advisory floor
+        run: make openssl-advisory-check
diff --git a/.github/workflows/security-baseline.yml b/.github/workflows/security-baseline.yml
new file mode 100644
index 0000000..73910ed
--- /dev/null
+++ b/.github/workflows/security-baseline.yml
@@ -0,0 +1,39 @@
+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
+          test -f docs/release-evidence.md
+          test -f scripts/openssl-advisory-check.sh
+          test -f scripts/reproducibility-report.sh
+          test -f scripts/sbom.sh
+          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 8b502f4..43ae15f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,10 @@
 *.so
 *.wpo
 *.dylib
+*.o
 .jerbuild-hashes
+.jerboa/*
+!.jerboa/
+!.jerboa/security.json
+dist/
+tests/tmp/
diff --git a/.jerboa/security.json b/.jerboa/security.json
new file mode 100644
index 0000000..0e467eb
--- /dev/null
+++ b/.jerboa/security.json
@@ -0,0 +1,52 @@
+{
+  "repository": "jerboa-fuse",
+  "riskClass": "filesystem-native-ffi-vault",
+  "status": "private-experimental-lane-c",
+  "releaseEvidence": {
+    "requiredTargets": [
+      "security",
+      "test",
+      "audit",
+      "openssl-advisory-check",
+      "sbom",
+      "reproducibility-report",
+      "target-evidence",
+      "release-evidence"
+    ],
+    "sensitiveArtifactPolicy": "no-vault-data-private-paths-host-data-secrets-or-raw-mount-output"
+  },
+  "targetEvidence": {
+    "mountProofFileEnv": "JERBOA_FUSE_TARGET_MOUNT_PROOF_FILE",
+    "mountRequireProofEnv": "JERBOA_FUSE_REQUIRE_TARGET_MOUNT_PROOF",
+    "corruptionProofFileEnv": "JERBOA_FUSE_TARGET_CORRUPTION_PROOF_FILE",
+    "corruptionRequireProofEnv": "JERBOA_FUSE_REQUIRE_TARGET_CORRUPTION_PROOF",
+    "sandboxProofFileEnv": "JERBOA_FUSE_TARGET_SANDBOX_PROOF_FILE",
+    "sandboxRequireProofEnv": "JERBOA_FUSE_REQUIRE_TARGET_SANDBOX_PROOF",
+    "statusFile": "dist/target-evidence/status.txt",
+    "mountRequiredMarkers": [
+      "target_platform_status=linux-or-freebsd-recorded",
+      "mount_smoke_status=target-evidence-recorded",
+      "unmount_teardown_status=target-evidence-recorded",
+      "crash_teardown_status=target-evidence-recorded",
+      "fuse_package_status=target-evidence-recorded"
+    ],
+    "corruptionRequiredMarkers": [
+      "vault_corruption_corpus_status=target-evidence-recorded",
+      "wrong_passphrase_status=target-denied-recorded",
+      "interrupted_write_status=target-evidence-recorded",
+      "crash_recovery_status=target-evidence-recorded"
+    ],
+    "sandboxRequiredMarkers": [
+      "service_sandbox_status=target-evidence-recorded",
+      "mountpoint_policy_status=target-evidence-recorded",
+      "allow_other_status=disabled-or-reviewed-recorded",
+      "negative_access_test_status=target-denied-recorded"
+    ]
+  },
+  "ffiBoundary": {
+    "nativeLoading": "lazy-via-jerboa-ffi",
+    "collectSafeCalls": true,
+    "cShim": "src/mount_helper.c",
+    "cryptoProvider": "OpenSSL"
+  }
+}
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 d2228e6..d0ee2aa 100644
--- a/Makefile
+++ b/Makefile
@@ -1,46 +1,60 @@
-JERBUILD ?= jerbuild
-JH := $(shell $(JERBUILD) --jerboa-home 2>/dev/null)
-ifeq ($(JH),)
-$(error jerbuild not found on PATH (or '$(JERBUILD) --jerboa-home' failed). Install jerbuild, or set JERBUILD=/path/to/jerbuild)
-endif
-
-LIBDIRS      = lib:$(JH)/lib
+JERBOA_VERSION ?= v0.2.3
+JERBOA_TOOL_DIR ?= $(CURDIR)/.jerboa/bin
+JERBUILD ?= $(shell if [ -x "$(CURDIR)/../jerboa/dist/jerbuild" ]; then printf '%s\n' "$(CURDIR)/../jerboa/dist/jerbuild"; elif command -v jerbuild >/dev/null 2>&1; then command -v jerbuild; else printf '%s\n' "$(JERBOA_TOOL_DIR)/jerbuild"; fi)
+JH = $(shell "$(JERBUILD)" --jerboa-home 2>/dev/null)
+LIBDIRS = lib:$(JH)/lib
 
 CC ?= cc
+DIST_DIR ?= dist/release-evidence
+SBOM_DIR ?= dist/sbom
+REPRO_DIR ?= dist/reproducibility
+TARGET_EVIDENCE_DIR ?= dist/target-evidence
+WARN_CFLAGS ?= -Wall -Wextra -Werror
+HARDEN_CFLAGS ?= -D_FORTIFY_SOURCE=2 -fstack-protector-strong
+HARDEN_LDFLAGS ?=
 
 UNAME_S := $(shell uname -s)
 ifeq ($(UNAME_S),Darwin)
-  SHARED_FLAG    = -dynamiclib
-  SO_EXT         = .so
-  LD_VAR         = DYLD_LIBRARY_PATH
-  OPENSSL_PREFIX ?= $(shell brew --prefix openssl@3 2>/dev/null || echo /opt/homebrew/opt/openssl@3)
-  CFLAGS_EXTRA   = -I$(OPENSSL_PREFIX)/include
-  LDFLAGS_EXTRA  = -L$(OPENSSL_PREFIX)/lib
+  SHARED_FLAG = -dynamiclib
+  SO_EXT = .so
+  LD_VAR = DYLD_LIBRARY_PATH
+  OPENSSL_PREFIX ?= $(shell brew --prefix openssl@4 2>/dev/null || brew --prefix openssl@3 2>/dev/null || echo /opt/homebrew/opt/openssl@4)
+  OPENSSL_CFLAGS ?= -I$(OPENSSL_PREFIX)/include
+  OPENSSL_LIBS ?= -L$(OPENSSL_PREFIX)/lib -lcrypto
+  OPENSSL_BIN ?= $(OPENSSL_PREFIX)/bin/openssl
 else
-  SHARED_FLAG    = -shared
-  SO_EXT         = .so
-  LD_VAR         = LD_LIBRARY_PATH
-  CFLAGS_EXTRA   =
-  LDFLAGS_EXTRA  =
+  SHARED_FLAG = -shared
+  SO_EXT = .so
+  LD_VAR = LD_LIBRARY_PATH
+  OPENSSL_CFLAGS ?= $(shell pkg-config --cflags openssl 2>/dev/null)
+  OPENSSL_LIBS ?= $(shell pkg-config --libs openssl 2>/dev/null || printf '%s\n' -lcrypto)
+  OPENSSL_BIN ?= openssl
+  HARDEN_LDFLAGS += -Wl,-z,relro -Wl,-z,now
 endif
 
-CFLAGS = $(SHARED_FLAG) -fPIC -O2 $(CFLAGS_EXTRA) $(LDFLAGS_EXTRA)
+CFLAGS ?= -O2 -fPIC $(WARN_CFLAGS) $(HARDEN_CFLAGS)
 
 SHIM := libjerboa_fuse_mount$(SO_EXT)
 
-.PHONY: all build transpile test test-memfs test-secmem test-access test-vault clean shim
+.PHONY: all build transpile test test-memfs test-secmem test-access test-vault clean shim ensure-jerboa-tools audit security openssl-advisory-check sbom reproducibility-report target-evidence verify release-evidence
 
 all: build
 
+ensure-jerboa-tools:
+	@if ! "$(JERBUILD)" --jerboa-home >/dev/null 2>&1; then \
+		echo "=== Installing Jerboa toolchain $(JERBOA_VERSION) into $(JERBOA_TOOL_DIR) ==="; \
+		sh support/ensure-jerboa.sh "$(JERBOA_VERSION)" "$(JERBOA_TOOL_DIR)"; \
+	fi
+
 shim: $(SHIM)
 
 $(SHIM): src/mount_helper.c
-	$(CC) $(CFLAGS) -o $@ $<
+	$(CC) $(CFLAGS) $(OPENSSL_CFLAGS) $(SHARED_FLAG) $(HARDEN_LDFLAGS) -o $@ $< $(OPENSSL_LIBS)
 
-transpile:
-	$(JERBUILD) transpile src lib --force
+transpile: ensure-jerboa-tools
+	@$(JERBUILD) transpile src lib --force
 
-build: shim transpile
+build: ensure-jerboa-tools shim transpile
 
 ifeq ($(UNAME_S),Darwin)
   EXTRA_LDPATH = :$(OPENSSL_PREFIX)/lib
@@ -50,7 +64,7 @@ endif
 
 RUNTEST = JERBOA_FUSE_LIB=$(CURDIR) $(LD_VAR)=$(CURDIR)$(EXTRA_LDPATH) $(JERBUILD) exec --libdirs "$(LIBDIRS)"
 
-test: test-memfs test-secmem test-access
+test: test-memfs test-secmem test-access test-vault
 
 test-memfs: build
 	$(RUNTEST) tests/test-memfs.ss
@@ -64,6 +78,90 @@ test-access: build
 test-vault: build
 	$(RUNTEST) tests/test-vault.ss
 
+audit: shim
+	@echo "==> Platform"
+	@uname -srm
+	@echo "==> OpenSSL"
+	@if [ -x "$(OPENSSL_BIN)" ] || command -v "$(OPENSSL_BIN)" >/dev/null 2>&1; then "$(OPENSSL_BIN)" version -a; else echo "openssl CLI not found; shim compile/link is the local dependency check."; fi
+	@echo "==> pkg-config OpenSSL (informational)"
+	@if command -v pkg-config >/dev/null 2>&1; then \
+		if [ -n "$(OPENSSL_PREFIX)" ] && [ -d "$(OPENSSL_PREFIX)/lib/pkgconfig" ]; then \
+			printf 'openssl via %s: ' "$(OPENSSL_PREFIX)/lib/pkgconfig"; \
+			PKG_CONFIG_PATH="$(OPENSSL_PREFIX)/lib/pkgconfig:$${PKG_CONFIG_PATH:-}" pkg-config --modversion openssl || true; \
+		else \
+			printf 'openssl via default pkg-config path: '; \
+			pkg-config --modversion openssl || true; \
+		fi; \
+	else \
+		echo "pkg-config not found."; \
+	fi
+	@echo "==> Native linkage for $(SHIM)"
+	@if command -v otool >/dev/null 2>&1; then \
+		otool -L "$(SHIM)"; \
+	elif command -v ldd >/dev/null 2>&1; then \
+		ldd "$(SHIM)"; \
+	else \
+		echo "No otool or ldd available for linkage audit."; \
+	fi
+
+openssl-advisory-check:
+	@OPENSSL="$(OPENSSL_BIN)" scripts/openssl-advisory-check.sh
+
+security: scripts/security-check.sh
+	@sh scripts/security-check.sh
+
+sbom: build
+	@SHIM="$(SHIM)" OPENSSL_BIN="$(OPENSSL_BIN)" OPENSSL_PREFIX="$(OPENSSL_PREFIX)" JERBUILD="$(JERBUILD)" CC="$(CC)" scripts/sbom.sh "$(SBOM_DIR)"
+
+reproducibility-report:
+	@JERBUILD="$(JERBUILD)" OPENSSL_PREFIX="$(OPENSSL_PREFIX)" OPENSSL_BIN="$(OPENSSL_BIN)" scripts/reproducibility-report.sh "$(REPRO_DIR)"
+
+target-evidence:
+	@JERBOA_FUSE_TARGET_EVIDENCE_DIR="$(TARGET_EVIDENCE_DIR)" scripts/target-evidence.sh
+
+verify: security test audit openssl-advisory-check sbom reproducibility-report target-evidence
+
+release-evidence: security test audit sbom target-evidence
+	rm -rf "$(DIST_DIR)"
+	mkdir -p "$(DIST_DIR)"
+	OPENSSL="$(OPENSSL_BIN)" scripts/openssl-advisory-check.sh "$(DIST_DIR)/openssl-advisory-check.txt"
+	JERBUILD="$(JERBUILD)" OPENSSL_PREFIX="$(OPENSSL_PREFIX)" OPENSSL_BIN="$(OPENSSL_BIN)" scripts/reproducibility-report.sh "$(REPRO_DIR)"
+	rm -rf "$(DIST_DIR)/reproducibility" "$(DIST_DIR)/sbom" "$(DIST_DIR)/target-evidence"
+	cp -R "$(REPRO_DIR)" "$(DIST_DIR)/reproducibility"
+	cp -R "$(SBOM_DIR)" "$(DIST_DIR)/sbom"
+	cp -R "$(TARGET_EVIDENCE_DIR)" "$(DIST_DIR)/target-evidence"
+	git rev-parse HEAD > "$(DIST_DIR)/git-commit.txt"
+	git status --short > "$(DIST_DIR)/git-status.txt"
+	{ printf 'platform='; uname -srm; printf 'jerbuild_version='; "$(JERBUILD)" --version; if "$(JERBUILD)" --jerboa-home >/dev/null 2>&1; then printf 'jerboa_home_status=present\n'; else printf 'jerboa_home_status=missing\n'; fi; } > "$(DIST_DIR)/build-environment.txt"
+	($(CC) --version || $(CC) -v) > "$(DIST_DIR)/cc-version.txt" 2>&1 || true
+	$(MAKE) security > "$(DIST_DIR)/security.log" 2>&1
+	@if [ -x "$(OPENSSL_BIN)" ] || command -v "$(OPENSSL_BIN)" >/dev/null 2>&1; then "$(OPENSSL_BIN)" version -a > "$(DIST_DIR)/openssl-version.txt"; else echo "openssl CLI not found." > "$(DIST_DIR)/openssl-version.txt"; fi
+	@if command -v otool >/dev/null 2>&1; then \
+		otool -L "$(SHIM)" > "$(DIST_DIR)/native-linkage.txt"; \
+	elif command -v ldd >/dev/null 2>&1; then \
+		ldd "$(SHIM)" > "$(DIST_DIR)/native-linkage.txt"; \
+	else \
+		echo "No native linkage inspector found." > "$(DIST_DIR)/native-linkage.txt"; \
+	fi
+	{ test -f .jerboa/security.json && printf '%s\n' .jerboa/security.json; find Makefile src lib tests docs scripts examples -type f \( -name 'Makefile' -o -name '*.c' -o -name '*.h' -o -name '*.sh' -o -name '*.ss' -o -name '*.sls' -o -name '*.md' \) -print; } | sort > "$(DIST_DIR)/source-inputs.txt"
+	@{ \
+		echo "privilege_policy_status=present"; \
+		grep -E 'privilege_model_status: documented|mount_permission_status: operator-granted-not-root-by-default|allow_other_status: disabled-by-default|sandbox_policy_status: documented|mountpoint_policy_status: private-owned-empty-directory|unmount_teardown_status: target-host-required|vault_secret_policy_status: no-passphrase-in-argv-or-env' docs/deployment-hardening.md; \
+	} > "$(DIST_DIR)/privilege-policy.txt"
+	grep -q 'privilege_model_status: documented' "$(DIST_DIR)/privilege-policy.txt"
+	grep -q 'allow_other_status: disabled-by-default' "$(DIST_DIR)/privilege-policy.txt"
+	grep -q 'sandbox_policy_status: documented' "$(DIST_DIR)/privilege-policy.txt"
+	shasum -a 256 src/mount_helper.c "$(SHIM)" > "$(DIST_DIR)/native-sha256.txt"
+	sh scripts/sanitize-evidence.sh "$(DIST_DIR)"
+	sh scripts/sanitize-evidence.sh "$(SBOM_DIR)"
+	sh scripts/sanitize-evidence.sh "$(REPRO_DIR)"
+	sh scripts/sanitize-evidence.sh "$(TARGET_EVIDENCE_DIR)"
+	grep -q '^sbom_status=present$$' "$(DIST_DIR)/sbom/manifest.txt"
+	grep -q '^deployment_hardening_status=present$$' "$(DIST_DIR)/sbom/manifest.txt"
+	grep -q '^platform_notes_status=present$$' "$(DIST_DIR)/sbom/manifest.txt"
+	grep -q '^status=' "$(DIST_DIR)/target-evidence/status.txt"
+	grep -q '^status=match$$' "$(DIST_DIR)/reproducibility/result.txt"
+
 clean:
 	rm -f $(SHIM) src/libchez_fuse_mount.*
-	rm -rf lib
+	rm -rf lib tests/tmp .jerboa/bin
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..2f5dfd6
--- /dev/null
+++ b/README.md
@@ -0,0 +1,48 @@
+# jerboa-fuse
+
+Experimental FUSE filesystem bindings, memory-backed filesystems, and encrypted-vault helpers for Jerboa.
+
+The repository contains Jerboa modules for building FUSE filesystems, an in-memory test filesystem, secure-memory helpers, access-control helpers, vault-oriented examples, and a small native mount helper.
+
+## Status
+
+Private/experimental Lane C. Do not treat this repository as production-ready until the filesystem threat model, deployment hardening policy, FFI boundary documentation, vault recovery behavior, and native dependency review are complete.
+
+The current code is intended for local development and review. It is not ready for unattended mounts, privileged deployment, or use with irreplaceable data.
+
+## Build
+
+```sh
+make build
+```
+
+The build compiles `src/mount_helper.c`, which owns FUSE mount calls, secure memory helpers, blockstore file I/O, and OpenSSL-backed vault crypto wrappers.
+
+## Test
+
+```sh
+make test
+make audit
+make security
+make sbom
+make openssl-advisory-check
+make reproducibility-report
+make target-evidence
+make release-evidence
+```
+
+`make test` runs memfs, secure-memory, access-controller, and vault tests. It does not perform a real FUSE mount in CI; mount smoke tests require an explicit Linux/FreeBSD host with FUSE configured.
+
+`make release-evidence` records the local release artifact bundle under `dist/release-evidence/`, including toolchain identity, native linkage, an SBOM/platform-support bundle, deployment privilege policy markers, an OpenSSL advisory gate, source inputs, hashes, and a two-clean-build reproducibility report. Production candidates must link a patched OpenSSL line: 4.0.1+, 3.6.3+, 3.5.7+, 3.4.6+, or 3.0.21+.
+
+Production support is blocked until `make target-evidence` records marker-
+complete target mount, vault corruption/recovery, and service-sandbox proof
+files through `JERBOA_FUSE_TARGET_MOUNT_PROOF_FILE`,
+`JERBOA_FUSE_TARGET_CORRUPTION_PROOF_FILE`, and
+`JERBOA_FUSE_TARGET_SANDBOX_PROOF_FILE`, or equivalent release-host evidence is
+attached. Proof files must not contain vault contents, passphrases, keys,
+private paths, hostnames, raw mount output, or raw command output.
+
+## Security
+
+Filesystem code can expose or corrupt local data if path, mount, permission, or lifecycle handling is wrong. Review [SECURITY.md](SECURITY.md), [docs/threat-model.md](docs/threat-model.md), [docs/deployment-hardening.md](docs/deployment-hardening.md), [docs/ffi-boundary.md](docs/ffi-boundary.md), and [docs/release-evidence.md](docs/release-evidence.md) before using it outside tests.
diff --git a/SECURITY.md b/SECURITY.md
new file mode 100644
index 0000000..6baacd2
--- /dev/null
+++ b/SECURITY.md
@@ -0,0 +1,55 @@
+# Security Policy
+
+`jerboa-fuse` is private/experimental Lane C code. It must not be marketed as production-ready or used for unattended privileged mounts until the release plan and production-readiness tracker gates are complete.
+
+## Sensitive Data
+
+Treat the following as sensitive:
+
+- Mounted filesystem contents and metadata, including names, sizes, modes, UID/GID values, timestamps, and symlink targets.
+- Vault files, vault passphrases, derived keys, plaintext block contents, and decrypted temporary buffers.
+- FUSE mountpoints and any host paths used for vault files or test fixtures.
+- Native crash dumps, diagnostics, and linkage logs that may reveal local paths or loaded library versions.
+
+Do not commit real vault files, private data mounted through FUSE, production mount paths, passphrases, keys, tokens, or operational hostnames.
+
+## Current Security Posture
+
+- Native code is restricted to `src/mount_helper.c`.
+- Scheme FFI bindings load the native helper lazily through `(jerboa ffi)` and do not import raw Chez FFI directly.
+- Mountpoint, filesystem-name, and vault-file paths cross C as length-checked, NUL-free UTF-8 bytevectors.
+- FUSE read/write, blockstore pread/pwrite/fsync, and crypto calls are collect-safe so they do not stall the runtime during blocking native work.
+- Secure-memory helpers reject null pointers, zero sizes, and oversized allocations before copying or freeing.
+- Vault encryption is delegated to OpenSSL through the C shim. Release evidence rejects OpenSSL lines below the current accepted upstream-supported minimums: 4.0.1+, 3.6.3+, 3.5.7+, 3.4.6+, or 3.0.21+.
+- `make sbom` records source/artifact hashes, OpenSSL/native linkage evidence, local FUSE package evidence, and explicit target-platform mount-smoke blockers.
+- `make security` passes, including release-file, native-boundary, generated
+  artifact, private-path, and target-proof marker checks.
+- `docs/deployment-hardening.md` documents the privilege model, mountpoint
+  policy, `allow_other` default, service sandboxing expectations, teardown
+  evidence, and vault secret handling.
+- `make release-evidence` archives native linkage, OpenSSL advisory status, SBOM/platform support notes, source inputs, hashes, and a two-clean-build reproducibility report under `dist/release-evidence/`.
+
+## Required Before Production
+
+- Threat model reviewed and updated for the target deployment.
+- Real mount smoke tests on every supported platform, including teardown after process crash.
+- Filesystem operation corpus for malformed names, oversized offsets, symlink/link behavior, rename replacement, truncate, and permission edge cases.
+- Vault corruption/recovery corpus for truncated headers, bad magic/version, failed authentication, interrupted writes, and bitmap/superblock mismatch.
+- Target-host evidence that the deployment follows `docs/deployment-hardening.md`
+  for any helper that opens `/dev/fuse` or performs mounts.
+- Target-host mount, vault corruption/recovery, and sandbox evidence captured
+  through the proof controls documented in `docs/release-evidence.md`; required
+  missing, incomplete, host-private, or high-confidence secret proofs fail
+  closed with `status=blocked-target-proof`.
+- Target-host review of the SBOM/platform support notes for OpenSSL, libc/platform mount interfaces, FUSE packages, the C compiler, and Jerboa toolchain.
+- External review before any public production claim.
+
+Target proof files are marker-only release attestations. Do not include vault
+contents, passphrases, keys, private filesystem paths, hostnames, SSH clone
+strings, raw mount output, or raw command output. The target-evidence script
+rejects overlarge, marker-incomplete, host-private, or high-confidence secret
+proof material before it is copied into release evidence.
+
+## Reporting
+
+Before public release, report issues privately to the repository owner. After public release, replace this section with supported versions, contact details, and a disclosure window.
diff --git a/docs/deployment-hardening.md b/docs/deployment-hardening.md
new file mode 100644
index 0000000..b702af7
--- /dev/null
+++ b/docs/deployment-hardening.md
@@ -0,0 +1,91 @@
+# Deployment Hardening
+
+`jerboa-fuse` crosses a local filesystem and kernel mount boundary. Production
+use requires a target-host deployment review; local unit tests and native
+linkage checks are not enough to prove mount safety.
+
+Required release markers:
+
+- `privilege_model_status: documented`
+- `mount_permission_status: operator-granted-not-root-by-default`
+- `allow_other_status: disabled-by-default`
+- `sandbox_policy_status: documented`
+- `mountpoint_policy_status: private-owned-empty-directory`
+- `unmount_teardown_status: target-host-required`
+- `vault_secret_policy_status: no-passphrase-in-argv-or-env`
+- `target_mount_proof_status: documented`
+- `target_corruption_proof_status: documented`
+- `target_sandbox_proof_status: documented`
+- `proof_failure_status: blocked-target-proof`
+
+## Privilege Model
+
+Do not run the mounting process as root by default. The preferred production
+shape is:
+
+- a dedicated unprivileged service account;
+- explicit membership in the OS group or policy that grants `/dev/fuse` access;
+- a private, service-owned mountpoint created before mount time;
+- no `allow_other` unless a target-host review explicitly requires it; and
+- service-manager teardown that unmounts on normal exit and after crashes.
+
+If the host requires a privileged helper to perform the mount, that helper must
+be a small audited wrapper with a fixed mountpoint policy, fixed option
+allowlist, and no caller-controlled shell command construction.
+
+## Mountpoint Policy
+
+Mountpoints must be absolute, owned by the service user, not world-writable, and
+empty before mount. Production deployments should avoid mounting over existing
+application directories. The release-host smoke must record mount, read/write
+where supported, unmount, and post-crash cleanup results for every supported
+platform.
+
+## `allow_other`
+
+`allow_other` is disabled by default. Enabling it changes the local threat model
+because unrelated users may see names, metadata, or decrypted content through
+the mount. A release may enable it only with:
+
+- an explicit operator configuration value;
+- a documented business reason;
+- host-level `/etc/fuse.conf` review where applicable; and
+- tests proving permissions and teardown still behave as expected.
+
+## Sandboxing
+
+Run the process under service-manager confinement where available:
+
+- Linux: systemd service user, `NoNewPrivileges=true`, `PrivateTmp=true`,
+  restricted writable paths, a narrow `DeviceAllow` for FUSE when feasible, and
+  a kill/umount cleanup path.
+- FreeBSD: jail or service account confinement with only the required FUSE
+  device and vault paths exposed.
+- macOS development smoke: use macFUSE only for local validation; it is not a
+  production release-host substitute for Linux or FreeBSD evidence.
+
+## Vault Secrets
+
+Vault passphrases and derived keys must not appear in command-line arguments,
+environment variables, logs, release evidence, or crash dumps. Interactive or
+file-descriptor based secret input is preferred. Temporary decrypted data must
+stay inside the mount process and must not be written to release evidence.
+
+## Target Evidence
+
+`make target-evidence` records target-host proof status and is archived by
+`make release-evidence`. Production promotion requires reviewed target proof
+files for the actual supported hosts:
+
+- Linux or FreeBSD real mount, read/write where supported, unmount, crash
+  teardown, and FUSE package provenance through
+  `JERBOA_FUSE_TARGET_MOUNT_PROOF_FILE`.
+- Vault corruption/recovery coverage for malformed vault files, wrong
+  passphrases, interrupted writes, and crash recovery through
+  `JERBOA_FUSE_TARGET_CORRUPTION_PROOF_FILE`.
+- Service-manager confinement, mountpoint policy, reviewed `allow_other`
+  posture, and negative access tests through
+  `JERBOA_FUSE_TARGET_SANDBOX_PROOF_FILE`.
+
+The matching `JERBOA_FUSE_REQUIRE_TARGET_*_PROOF=1` variables make missing or
+incomplete proof fail closed with `status=blocked-target-proof`.
diff --git a/docs/ffi-boundary.md b/docs/ffi-boundary.md
new file mode 100644
index 0000000..1c53be3
--- /dev/null
+++ b/docs/ffi-boundary.md
@@ -0,0 +1,41 @@
+# FFI Boundary
+
+`jerboa-fuse` uses one native helper, `src/mount_helper.c`, loaded lazily by `(jerboa-fuse mount)`.
+
+## Native Responsibilities
+
+- Open `/dev/fuse`, mount, unmount, read, write, and close FUSE file descriptors.
+- Copy mountpoint, filesystem-name, and vault-file paths from NUL-free bytevectors into temporary C strings.
+- Perform fixed-arity blockstore open/create wrappers so Scheme never declares variadic `open(2)`.
+- Retry `read`, `write`, `pread`, `pwrite`, and `fsync` around `EINTR` where retrying is safe.
+- Allocate, zero, copy, and free mlock-backed secure memory with null/size guards.
+- Delegate vault CSPRNG, PBKDF2-HMAC-SHA256, and AES-256-GCM to OpenSSL.
+
+## Scheme Responsibilities
+
+- Validate path strings before crossing C: non-empty, NUL-free, UTF-8, and no more than 4096 bytes.
+- Validate FUSE filesystem names before crossing C: non-empty, NUL-free, UTF-8, and no more than 255 bytes.
+- Validate file descriptors, block numbers, bytevector lengths, offsets, key sizes, and payload sizes before native calls.
+- Keep blocking FUSE, blockstore, and crypto calls behind collect-safe FFI declarations.
+- Close blockstore file descriptors through `blockstore-close!` and destroy FUSE sessions with `fuse-session-destroy!`.
+
+## Ownership
+
+- C strings created from Scheme bytevectors are owned and freed by `src/mount_helper.c` before returning.
+- Scheme owns all bytevectors passed to C. C must not retain bytevector pointers after the call returns.
+- Secure-memory pointers are opaque integers on the Scheme side. They must only be freed through `secmem-free!` or `secure-key-destroy!`.
+- `make-secure-key` copies key material into native secure memory and zeros the source bytevector.
+- `blockstore-set-key!` zeros the caller-provided master-key bytevector after moving it into secure memory.
+
+## Error Mapping
+
+- Native syscall and mount failures return `-1` and preserve `errno` for `fuse-get-errno`.
+- Native crypto wrappers return `1` for success, `0` for authentication failure where applicable, and `-1` for misuse or provider failure.
+- Scheme raises on API misuse, mount failures, short reads/writes, failed `fsync`, and native crypto/provider errors.
+- AES-GCM authentication failure maps to `#f` for decrypt operations so callers can distinguish bad credentials/corruption from API misuse.
+
+## Known Gaps
+
+- The vault crypto boundary still uses OpenSSL through the C shim. `make openssl-advisory-check` and `make release-evidence` enforce the current accepted OpenSSL minimums: 4.0.1+, 3.6.3+, 3.5.7+, 3.4.6+, or 3.0.21+.
+- CI does not mount FUSE. A production gate must run real mount/unmount smoke tests on Linux and FreeBSD hosts with FUSE configured.
+- Crash-recovery and corruption behavior are not yet covered by a durable regression corpus.
diff --git a/docs/openssl-advisory-review.md b/docs/openssl-advisory-review.md
new file mode 100644
index 0000000..14efa2a
--- /dev/null
+++ b/docs/openssl-advisory-review.md
@@ -0,0 +1,50 @@
+# OpenSSL Advisory Review
+
+Review date: 2026-06-21
+
+`jerboa-fuse` delegates vault CSPRNG, PBKDF2-HMAC-SHA256, and AES-256-GCM to
+OpenSSL through `src/mount_helper.c`. A release candidate is not acceptable if
+it links an OpenSSL release line below the patched versions listed by upstream
+for the 2026-06-09 advisory set.
+
+## Accepted OpenSSL Lines
+
+Release evidence must pass `make openssl-advisory-check`. The current policy
+accepts these upstream-supported minimums:
+
+- OpenSSL 4.0.1 or newer on the 4.0 line.
+- OpenSSL 3.6.3 or newer on the 3.6 line.
+- OpenSSL 3.5.7 or newer on the 3.5 LTS line.
+- OpenSSL 3.4.6 or newer on the 3.4 line.
+- OpenSSL 3.0.21 or newer on the 3.0 LTS line.
+
+OpenSSL 3.6.2 is rejected for production release. It predates the 2026-06-09
+security patch release set, which includes a High severity issue fixed in
+3.6.3 and 4.0.1.
+
+Official sources:
+
+- https://openssl-library.org/source/
+- https://openssl-library.org/news/secadv/20260609.txt
+- https://openssl-library.org/news/vulnerabilities/
+
+## Local Review Result
+
+The local release path uses Homebrew `openssl@4` after upgrading that keg to
+OpenSSL 4.0.1.
+
+`jerboa-fuse` does not call the PKCS#7, CMS decryption, PKCS#12, QUIC, OCSP,
+or AES-OCB one-shot APIs implicated by several 2026-06-09 issues, but it must
+still link a patched OpenSSL because vault passphrase derivation and encrypted
+block contents are high-value boundaries.
+
+## Release Gate
+
+Before publishing:
+
+- Run `make clean`.
+- Run `make release-evidence JERBUILD=/path/to/jerbuild`.
+- Confirm `dist/release-evidence/openssl-advisory-check.txt` reports
+  `status=pass`.
+- Confirm `dist/release-evidence/reproducibility/result.txt` reports
+  `status=match`.
diff --git a/docs/release-evidence.md b/docs/release-evidence.md
new file mode 100644
index 0000000..a421150
--- /dev/null
+++ b/docs/release-evidence.md
@@ -0,0 +1,105 @@
+# Release Evidence
+
+`jerboa-fuse` is private Lane C filesystem and vault code with a native C shim.
+It is not production-ready until each release candidate has current build/test,
+OpenSSL linkage, advisory/CVE, reproducibility, mount smoke, and vault
+corruption evidence.
+
+Run from a clean checkout:
+
+```sh
+make clean
+make release-evidence JERBUILD=/path/to/jerbuild
+```
+
+The `release-evidence` target runs `make security`, `make test`, `make audit`,
+`make sbom`, `make target-evidence`, the OpenSSL advisory gate, and a
+two-clean-build reproducibility check. It writes untracked artifacts under
+`dist/release-evidence/`:
+
+- `git-commit.txt` and `git-status.txt`.
+- `build-environment.txt` with host-neutral OS and Jerboa toolchain identity.
+- `security.log` from the repository security gate.
+- `cc-version.txt` and `openssl-version.txt`.
+- `native-linkage.txt` from `otool -L` or `ldd`.
+- `privilege-policy.txt` with deployment hardening, `allow_other`, sandbox, and
+  teardown policy markers.
+- `sbom/manifest.txt`, `sbom/source-sha256.txt`,
+  `sbom/artifact-sha256.txt`, `sbom/dependencies.txt`,
+  `sbom/native-linkage.txt`, and `sbom/platform-support.txt`.
+- `openssl-advisory-check.txt` with the accepted OpenSSL release-line result.
+- `reproducibility/result.txt` plus first/second native shim and generated
+  library hashes.
+- `source-inputs.txt` for source, examples, docs, tests, and evidence scripts.
+- `native-sha256.txt` for the C shim source and built native shim.
+- `target-evidence/status.txt` plus optional copied target proof files and
+  `.sha256` sidecars.
+
+Before publishing, review OpenSSL advisories for the linked package or distro
+build and document any accepted CVE or platform risk in the release notes.
+The current accepted OpenSSL release lines are documented in
+`docs/openssl-advisory-review.md`, and `make release-evidence` rejects older
+OpenSSL versions automatically.
+
+The SBOM/platform support bundle is explicit about what the local host can and
+cannot prove. It records OpenSSL, compiler, Jerboa toolchain, native linkage,
+FUSE package evidence when available, and source/artifact hashes. It also keeps
+Linux and FreeBSD real mount smoke as `blocked-not-run` until those target hosts
+run mount/unmount, crash-teardown, and package provenance checks. The privilege
+policy evidence records the documented production default that `allow_other` is
+off, mount permission is operator-granted rather than root-by-default, and
+service-manager sandboxing is required for production deployments.
+
+The local test suite exercises the in-memory filesystem, secure memory, access
+controller, and vault lifecycle. It does not replace real Linux/FreeBSD mount
+smoke, crash-teardown evidence, or a vault corruption/recovery corpus.
+
+Reproducibility evidence is claimed only for two clean builds in the same
+toolchain environment. The automated report compares the native shim and the
+generated Jerboa library manifest. Reproducibility archives hashes/status/diffs
+only; raw scratch build logs and raw native shims are not retained in release
+evidence.
+
+Release evidence is sanitized before publication. Do not preserve vault data,
+passphrases, keys, private paths, hostnames, SSH clone strings, raw mount output,
+or raw command output in release artifacts or target proof files.
+
+## Target Proof Files
+
+`scripts/target-evidence.sh` accepts three target proof files. Setting a file
+path records the proof; setting the matching `REQUIRE` variable to `1` makes a
+missing, empty, or marker-incomplete proof block with
+`status=blocked-target-proof`.
+
+- `JERBOA_FUSE_TARGET_MOUNT_PROOF_FILE` with
+  `JERBOA_FUSE_REQUIRE_TARGET_MOUNT_PROOF=1` records Linux or FreeBSD real
+  mount evidence. Required markers:
+  `target_platform_status=linux-or-freebsd-recorded`,
+  `mount_smoke_status=target-evidence-recorded`,
+  `unmount_teardown_status=target-evidence-recorded`,
+  `crash_teardown_status=target-evidence-recorded`, and
+  `fuse_package_status=target-evidence-recorded`.
+- `JERBOA_FUSE_TARGET_CORRUPTION_PROOF_FILE` with
+  `JERBOA_FUSE_REQUIRE_TARGET_CORRUPTION_PROOF=1` records vault
+  corruption/recovery evidence. Required markers:
+  `vault_corruption_corpus_status=target-evidence-recorded`,
+  `wrong_passphrase_status=target-denied-recorded`,
+  `interrupted_write_status=target-evidence-recorded`, and
+  `crash_recovery_status=target-evidence-recorded`.
+- `JERBOA_FUSE_TARGET_SANDBOX_PROOF_FILE` with
+  `JERBOA_FUSE_REQUIRE_TARGET_SANDBOX_PROOF=1` records service confinement.
+  Required markers:
+  `service_sandbox_status=target-evidence-recorded`,
+  `mountpoint_policy_status=target-evidence-recorded`,
+  `allow_other_status=disabled-or-reviewed-recorded`, and
+  `negative_access_test_status=target-denied-recorded`.
+
+Proof files must be 64 KiB or smaller and marker-only. Files containing private
+paths, host details, SSH clone strings, vault contents, passphrases, keys, or
+high-confidence secret material fail closed before they are copied into release
+evidence.
+
+target_mount_proof_status: documented
+target_corruption_proof_status: documented
+target_sandbox_proof_status: documented
+proof_failure_status: blocked-target-proof
diff --git a/docs/threat-model.md b/docs/threat-model.md
new file mode 100644
index 0000000..b0574bb
--- /dev/null
+++ b/docs/threat-model.md
@@ -0,0 +1,65 @@
+# Threat Model
+
+`jerboa-fuse` lets Jerboa code present filesystem views through FUSE and provides an encrypted vault example. This is security-sensitive local system code, not a general-purpose production filesystem.
+
+## Assets
+
+- Host filesystem integrity outside the mountpoint.
+- Mounted file contents and metadata.
+- Vault passphrases, master keys, per-block keys, and plaintext block buffers.
+- Vault file integrity and availability.
+- Process boundaries between the mounting process, descendant tools, and unrelated local processes.
+
+## Trust Boundaries
+
+- Kernel FUSE requests enter Jerboa as untrusted byte-level filesystem operations.
+- Mountpoint and vault-file paths cross from Scheme into native C and then into platform syscalls.
+- Vault key material crosses between Scheme bytevectors, mlock-backed native memory, and OpenSSL calls.
+- Access-control decisions depend on local process identity and parentage, which can race with process exit and PID reuse.
+
+## Primary Risks
+
+- Mounting at an unintended path or exposing data through `allow-other`.
+- Path, name, or symlink handling that lets mounted operations escape intended policy.
+- Partial writes, interrupted writes, or crashes corrupting vault metadata.
+- Decryption failures or corrupt blocks being treated as empty/valid data.
+- Key material remaining in Scheme heap bytevectors longer than expected.
+- Native FFI misuse: null pointers, embedded NUL strings, invalid lengths, blocking calls that freeze the runtime, or variadic libc declarations.
+- Unmount failures leaving active mounts after the controlling process exits.
+
+## Current Controls
+
+- Public status remains private/experimental.
+- Native FFI loads lazily and rejects NUL-containing path/name inputs.
+- Scheme validates file descriptors, block numbers, path lengths, key lengths, payload sizes, and offsets before native calls.
+- Blockstore file I/O uses fixed native wrappers for create/open and collect-safe pread/pwrite/fsync wrappers.
+- Secure-memory helpers use mlock-backed mmap where available, reject invalid sizes, and zero on destroy.
+- Access-control helpers deny when locked and only trust the owner PID and cached descendants.
+- Default CI runs memfs, access-control, secure-memory, vault, and native-linkage checks without performing privileged mounts.
+
+## Release Gates
+
+- Add real mount/unmount smoke tests for Linux and FreeBSD with explicit
+  teardown checks.
+- Add hostile filesystem operation tests for long names, embedded NUL
+  rejection, invalid UTF-8 byte names where applicable, symlink/link behavior,
+  rename replacement, truncate, large offsets, and permission changes.
+- Add vault corruption/recovery tests for truncated files, bad header
+  magic/version, bad superblock locator, bitmap mismatch, interrupted writes,
+  and wrong passphrase lock/unlock.
+- Keep `docs/deployment-hardening.md` current for the exact `allow_other`
+  policy, privilege model, mountpoint policy, and service sandboxing for any
+  process with mount permission.
+- Generate `make release-evidence` output for Jerboa, the C compiler,
+  libc/platform mount APIs, and OpenSSL, including advisory status, native
+  linkage, source inputs, hashes, and two-clean-build reproducibility.