Security hardening and release readiness

ober

cab0aa9862c022fc8f154aae2ca5708fa11b349a

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..c69284a
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,35 @@
+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 git 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 OpenSSL linkage
+        run: make audit
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 5381726..0501772 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,8 @@
 lib/
+.jerboa/*
+!.jerboa/
+!.jerboa/security.json
+dist/
 *.so
 *.dylib
 *.wpo
diff --git a/.jerboa/security.json b/.jerboa/security.json
new file mode 100644
index 0000000..ada67b3
--- /dev/null
+++ b/.jerboa/security.json
@@ -0,0 +1,63 @@
+{
+  "version": 1,
+  "repo": "jerboa-crypto",
+  "extends": ["jerboa:ffi", "jerboa:crypto"],
+  "classification": "OpenSSL libcrypto bindings for hashes, HMAC, symmetric encryption, Ed25519, and scrypt",
+  "license": "Apache-2.0",
+  "paths": {
+    "production": ["src/**/*.ss", "jerboa_crypto_shim.c", "Makefile"],
+    "tests": ["tests/**"],
+    "generated": ["dist/**", "lib/**", ".jerboa/bin/**", "*.so", "*.dylib", "*.wpo", "*.o"],
+    "docs": ["README.md", "SECURITY.md", "docs/**", "*.md"]
+  },
+  "policy": {
+    "failOn": ["critical", "high", "medium"],
+    "crypto": {
+      "primitiveImplementations": "deny-custom-primitives",
+      "legacyAlgorithms": "compatibility-only",
+      "algorithmPolicy": "docs/algorithm-policy.md"
+    },
+    "ffi": {
+      "nativeBoundary": "jerboa_crypto_shim.c",
+      "requireLazyLoading": true,
+      "requireSchemeLengthChecks": true,
+      "requireCNullAndLengthChecks": true
+    },
+    "release": {
+      "requireEvidence": true,
+      "requireOpenSSLAdvisoryCheck": true,
+      "requireTargetProofBeforeProductionClaim": true,
+      "rejectHostPrivateEvidence": true
+    }
+  },
+  "targetEvidence": {
+    "script": "scripts/target-evidence.sh",
+    "optionalProofFileEnv": "JCRYPTO_TARGET_PROOF_FILE",
+    "requiredProofEnv": "JCRYPTO_REQUIRE_TARGET_PROOF",
+    "maxProofBytes": 65536,
+    "targetProofStatus": "target_crypto_proof_status",
+    "productionStatus": "production_crypto_status",
+    "sensitiveArtifactPolicy": "no-secrets-private-paths-host-data-or-plaintext-crypto-material",
+    "requiredMarkers": [
+      "release_host_openssl_advisory_status=reviewed",
+      "target_openssl_linkage_status=reviewed",
+      "platform_cve_review_status=reviewed",
+      "external_crypto_ffi_review_status=reviewed",
+      "algorithm_policy_exception_review_status=reviewed",
+      "downstream_protocol_integration_status=reviewed",
+      "cross_platform_smoke_status=reviewed",
+      "sensitive_artifact_policy=no-secrets-private-paths-host-data-or-plaintext-crypto-material"
+    ]
+  },
+  "releaseGates": [
+    "make security",
+    "make test",
+    "make audit",
+    "make openssl-advisory-check",
+    "make sbom",
+    "make reproducibility-report",
+    "make target-evidence",
+    "make release-evidence"
+  ],
+  "suppressions": []
+}
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 07fb330..f0774ea 100644
--- a/Makefile
+++ b/Makefile
@@ -1,51 +1,122 @@
-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
+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
+TARGET_EVIDENCE_DIR ?= dist/target-evidence
+WARN_CFLAGS ?= -Wall -Wextra -Werror
+HARDEN_CFLAGS ?= -D_FORTIFY_SOURCE=2 -fstack-protector-strong
+HARDEN_LDFLAGS ?=
 
-# OpenSSL — Linux has it in standard paths; macOS needs brew's openssl@3
+# OpenSSL — Linux has it in standard paths; macOS should prefer the newest
+# patched Homebrew line available.
 UNAME   := $(shell uname -s)
 ifeq ($(UNAME),Darwin)
-OPENSSL_PREFIX ?= $(shell brew --prefix openssl@3 2>/dev/null)
+OPENSSL_PREFIX ?= $(shell brew --prefix openssl@4 2>/dev/null || brew --prefix openssl@3 2>/dev/null)
 ifneq ($(OPENSSL_PREFIX),)
 OPENSSL_CFLAGS := -I$(OPENSSL_PREFIX)/include
 OPENSSL_LIBS   := -L$(OPENSSL_PREFIX)/lib -lcrypto
+OPENSSL_BIN    ?= $(OPENSSL_PREFIX)/bin/openssl
 else
 OPENSSL_LIBS   := -lcrypto
+OPENSSL_BIN    ?= openssl
 endif
 else
 OPENSSL_LIBS   := -lcrypto
+OPENSSL_BIN    ?= openssl
+HARDEN_LDFLAGS += -Wl,-z,relro -Wl,-z,now
 endif
 
-CFLAGS  ?= -shared -fPIC -O2 $(OPENSSL_CFLAGS)
-LIBS    ?= $(OPENSSL_LIBS)
+CFLAGS  ?= -shared -fPIC -O2 $(WARN_CFLAGS) $(HARDEN_CFLAGS) $(OPENSSL_CFLAGS)
+LIBS    ?= $(HARDEN_LDFLAGS) $(OPENSSL_LIBS)
 SHIM     = jerboa_crypto_shim.so
 
-.PHONY: all build transpile test clean shim
+.PHONY: all build transpile test clean shim ensure-jerboa-tools security audit 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): jerboa_crypto_shim.c
 	$(CC) $(CFLAGS) -o $@ $< $(LIBS)
 
-transpile:
-	$(JERBUILD) transpile src lib --force
+transpile: ensure-jerboa-tools
+	@$(JERBUILD) transpile src lib --force
 
 build: shim transpile
 
 test: build
-	JERBOA_CRYPTO_LIB=$(CURDIR) \
+	@JERBOA_CRYPTO_LIB=$(CURDIR) \
 	DYLD_LIBRARY_PATH=$(CURDIR) \
 	LD_LIBRARY_PATH=$(CURDIR) \
 	$(JERBUILD) exec --libdirs "$(LIBDIRS)" tests/crypto-test.ss
 
+security: scripts/security-check.sh
+	@sh scripts/security-check.sh
+
+audit: shim
+	@"$(OPENSSL_BIN)" version -a
+	@if command -v otool >/dev/null 2>&1; then otool -L "$(SHIM)"; elif command -v ldd >/dev/null 2>&1; then ldd "$(SHIM)"; fi
+
+openssl-advisory-check:
+	@OPENSSL="$(OPENSSL_BIN)" scripts/openssl-advisory-check.sh
+
+sbom: build
+	@OPENSSL_BIN="$(OPENSSL_BIN)" SHIM="$(SHIM)" scripts/sbom.sh "$(SBOM_DIR)"
+
+reproducibility-report:
+	@JERBUILD="$(JERBUILD)" OPENSSL_PREFIX="$(OPENSSL_PREFIX)" OPENSSL_BIN="$(OPENSSL_BIN)" scripts/reproducibility-report.sh dist/reproducibility
+
+target-evidence: scripts/target-evidence.sh
+	@REPO_ROOT="$(CURDIR)" TARGET_EVIDENCE_DIR="$(TARGET_EVIDENCE_DIR)" sh scripts/target-evidence.sh
+
+verify: security test audit sbom reproducibility-report target-evidence
+
+release-evidence: security test audit 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 dist/reproducibility
+	OPENSSL_BIN="$(OPENSSL_BIN)" SHIM="$(SHIM)" scripts/sbom.sh "$(SBOM_DIR)" > "$(DIST_DIR)/sbom.txt"
+	rm -rf "$(DIST_DIR)/reproducibility" "$(DIST_DIR)/sbom" "$(DIST_DIR)/target-evidence"
+	cp -R dist/reproducibility "$(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
+	"$(OPENSSL_BIN)" version -a > "$(DIST_DIR)/openssl-version.txt"
+	$(MAKE) security > "$(DIST_DIR)/security.log" 2>&1
+	$(MAKE) test > "$(DIST_DIR)/test.log" 2>&1
+	$(MAKE) audit > "$(DIST_DIR)/audit.log" 2>&1
+	@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 jerboa_crypto_shim.c src lib tests docs scripts -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"
+	shasum -a 256 jerboa_crypto_shim.c "$(SHIM)" > "$(DIST_DIR)/shim-sha256.txt"
+	sh scripts/sanitize-evidence.sh "$(DIST_DIR)"
+	sh scripts/sanitize-evidence.sh "$(SBOM_DIR)"
+	sh scripts/sanitize-evidence.sh dist/reproducibility
+	sh scripts/sanitize-evidence.sh "$(TARGET_EVIDENCE_DIR)"
+	grep -q '^sbom_status=present$$' "$(DIST_DIR)/sbom/manifest.txt"
+	grep -q '^status=match$$' "$(DIST_DIR)/reproducibility/result.txt"
+
 clean:
 	rm -f $(SHIM) chez_crypto_shim.so
 	rm -rf lib
diff --git a/README.md b/README.md
index 0577660..ead49d5 100644
--- a/README.md
+++ b/README.md
@@ -1,13 +1,17 @@
-# chez-crypto
+# jerboa-crypto
 
-OpenSSL libcrypto bindings for Chez Scheme.
+OpenSSL `libcrypto` bindings for Jerboa.
 
 Provides hash digests, HMAC, symmetric encryption, Ed25519 signatures, and scrypt KDF.
 
+Status: experimental. Treat this as a low-level FFI binding until the release
+gates in `SECURITY.md` are complete.
+
 ## Requirements
 
-- Chez Scheme 10.x
-- OpenSSL 3.x (`libcrypto`)
+- Jerboa and `jerbuild`
+- A supported, patched OpenSSL `libcrypto`. Release evidence currently accepts
+  OpenSSL 4.0.1+, 3.6.3+, 3.5.7+, 3.4.6+, or 3.0.21+.
 - GCC
 
 ## Build & Test
@@ -15,8 +19,21 @@ Provides hash digests, HMAC, symmetric encryption, Ed25519 signatures, and scryp
 ```bash
 make
 make test
+make audit
+make sbom
+make reproducibility-report
+make target-evidence
+make release-evidence
 ```
 
+Production support is still blocked until `make target-evidence` records a
+marker-complete proof through `JCRYPTO_TARGET_PROOF_FILE`; production release
+hosts should set `JCRYPTO_REQUIRE_TARGET_PROOF=1`. The proof covers target
+OpenSSL advisory/linkage review, platform CVE review, external crypto/FFI
+review, algorithm-policy exceptions, downstream protocol integration, and
+cross-platform smoke. It must not contain keys, tokens, plaintext crypto
+material, private paths, hostnames, customer data, or raw command output.
+
 ## API
 
 ### Random
@@ -29,6 +46,12 @@ make test
 - `(digest-size algo)` → integer
 - Streaming: `make-digest-ctx`, `digest-init!`, `digest-update!`, `digest-final!`, `free-digest-ctx`
 
+MD5 and SHA-1 are exposed for compatibility and test-vector use, not for new
+security designs.
+
+Production consumers should use the default allowlist in
+`docs/algorithm-policy.md` unless a protocol standard requires otherwise.
+
 ### HMAC
 - `(hmac algo key data)` → bytevector
 - `(hmac-md5 key data)`, `(hmac-sha256 key data)`, etc.
@@ -46,4 +69,7 @@ make test
 
 ### KDF
 - `(scrypt password salt size [N r p])` → derived key bytevector
-# chez-crypto
+
+## License
+
+Apache License 2.0.
diff --git a/SECURITY.md b/SECURITY.md
new file mode 100644
index 0000000..b219ab7
--- /dev/null
+++ b/SECURITY.md
@@ -0,0 +1,61 @@
+# Security Policy
+
+`jerboa-crypto` is an experimental OpenSSL `libcrypto` binding for Jerboa. It
+must not be advertised as production-ready until the release gates tracked in
+`~/Release-plan.md` and the repository production-readiness tracker are
+complete.
+
+## Supported Status
+
+No public production-support commitment exists yet. Security-sensitive releases
+must be cut from a clean checkout after:
+
+- `make test` passes.
+- `make audit` records the OpenSSL version and linked library path for the
+  release artifact.
+- `make openssl-advisory-check` accepts the linked OpenSSL release line.
+- `make sbom` records source, generated library, native shim, OpenSSL, and
+  linkage evidence.
+- `make reproducibility-report` reports `status=match`.
+- `make target-evidence` records reviewed release-host crypto proof through
+  `JCRYPTO_TARGET_PROOF_FILE`, or records the local blocked status. Set
+  `JCRYPTO_REQUIRE_TARGET_PROOF=1` on production release hosts.
+- `make release-evidence` archives the release-candidate evidence described in
+  `docs/release-evidence.md`.
+- The high-confidence secret scan and Jerboa security scanner are clean.
+- OpenSSL CVE posture for the target platform is reviewed and documented in
+  `docs/openssl-advisory-review.md` or the release notes.
+- New protocol integrations follow `docs/algorithm-policy.md` or document a
+  reviewed exception.
+- History has been reviewed or intentionally reset before first public release.
+
+## Cryptography Posture
+
+- This repo delegates cryptographic primitives to OpenSSL. Do not add custom
+  primitive implementations.
+- MD5 and SHA-1 are exposed only for compatibility and test-vector use. They
+  must not be recommended for new security designs.
+- CBC-mode ciphers are compatibility surfaces only unless a protocol-specific
+  construction and padding behavior review is recorded.
+- Cipher APIs validate key, IV, nonce, tag, and Ed25519 key lengths before
+  crossing the native boundary.
+- See `docs/ffi-boundary.md` for ownership, lifetime, and release-review notes.
+
+## FFI Expectations
+
+- Native symbols are loaded lazily through `(jerboa ffi)` so imports and static
+  binaries do not crash before the caller needs crypto functionality.
+- Scheme owns all bytevector inputs and outputs. C functions must treat pointers
+  as borrowed for the duration of the call.
+- C entry points must reject null pointers and negative lengths even when the
+  Scheme layer validates normal callers.
+- OpenSSL dependency drift must be reviewed for every release candidate.
+- Target proof files must be marker-only summaries. Do not store private keys,
+  tokens, plaintext crypto material, raw command output, private paths,
+  hostnames, or customer data in release evidence.
+
+## 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/algorithm-policy.md b/docs/algorithm-policy.md
new file mode 100644
index 0000000..2e9db15
--- /dev/null
+++ b/docs/algorithm-policy.md
@@ -0,0 +1,51 @@
+# jerboa-crypto Algorithm Policy
+
+`jerboa-crypto` is a low-level binding over OpenSSL. It exposes some generic
+OpenSSL digest and cipher names for compatibility, but production applications
+must choose an explicit protocol-level allowlist instead of passing user or
+configuration strings through unchecked.
+
+## Default Production Allowlist
+
+Use these names for new designs unless a protocol standard requires otherwise:
+
+- Digests: `sha256`, `sha384`, `sha512`.
+- HMAC: `hmac-sha256`, `hmac-sha384`, `hmac-sha512`.
+- AEAD encryption: `chacha20-poly1305`, `aes-256-gcm`, or `aes-128-gcm`.
+- Signatures: Ed25519.
+- Password KDF: `scrypt` with parameters selected by the calling protocol and
+  documented in that protocol's release evidence.
+
+## Legacy Or Compatibility Only
+
+These remain available for parsing old formats, test vectors, or compatibility
+with an existing protocol, but must not be selected for new authentication,
+integrity, or encryption designs:
+
+- `md5`, `sha1`, and HMAC variants built on MD5 or SHA-1.
+- CBC-mode ciphers such as `aes-256-cbc`, unless the protocol already provides
+  a reviewed MAC-then-encrypt or encrypt-then-MAC construction and padding
+  behavior is covered by tests.
+
+## Rejected For Production Defaults
+
+Do not add new wrappers or examples for:
+
+- Custom cryptographic primitives implemented in this repository.
+- Raw RSA/DSA/ECDSA flows without a protocol-specific padding, hash, key-size,
+  and side-channel review.
+- Unauthenticated encryption for new data at rest or transport protocols.
+- OpenSSL algorithms enabled only by legacy providers unless the release notes
+  document the provider, platform, and accepted risk.
+
+## Release Evidence
+
+Every release candidate should record:
+
+- The linked OpenSSL release line and advisory check output.
+- SBOM evidence for source, native shim, generated Jerboa library, and OpenSSL
+  toolchain/linkage.
+- Reproducibility evidence for the native shim and generated library.
+- Any protocol-specific deviations from the default allowlist above.
+- Marker-complete target proof for external crypto/FFI review and downstream
+  protocol integration review before production support is claimed.
diff --git a/docs/ffi-boundary.md b/docs/ffi-boundary.md
new file mode 100644
index 0000000..884f207
--- /dev/null
+++ b/docs/ffi-boundary.md
@@ -0,0 +1,56 @@
+# jerboa-crypto FFI Boundary
+
+`jerboa-crypto` exposes a small Jerboa API over OpenSSL `libcrypto` through
+`jerboa_crypto_shim.c`.
+
+## Trust Boundaries
+
+- Scheme callers are trusted to choose algorithms appropriate to their protocol,
+  but all bytevector sizes are still validated before native calls.
+- OpenSSL algorithm names are untrusted strings. The Scheme layer rejects empty
+  names and names containing NUL before passing them to C.
+- Native pointers are borrowed. C must not retain Scheme-owned bytevectors after
+  returning.
+- The shared object is loaded lazily through `(jerboa ffi)` to avoid import-time
+  crashes in static binaries or feature probes.
+
+## Memory And Lifetime Rules
+
+- Scheme owns all input and output buffers.
+- Digest, HMAC, cipher, AEAD, Ed25519, and scrypt functions check bytevector
+  lengths before converting them to C `int`.
+- Cipher and AEAD calls verify key/IV/nonce lengths against OpenSSL metadata
+  before passing pointers to `EVP_*` APIs.
+- C entry points reject null pointers, negative lengths, and unsupported tag or
+  Ed25519 key sizes as a second line of defense.
+- Streaming context pointers returned by OpenSSL must be freed with the matching
+  `free-*` procedure.
+
+## Crypto Caveats
+
+- OpenSSL provides the primitive implementations. This repository should not add
+  custom cryptographic primitives.
+- MD5 and SHA-1 remain available for compatibility. They are not acceptable for
+  new integrity or signature designs.
+- AEAD APIs currently emit and require 16-byte tags.
+- The public API accepts arbitrary OpenSSL cipher and digest names; release
+  notes for consumers should document protocol-specific allowlists. The default
+  production policy is in `docs/algorithm-policy.md`.
+
+## Release Gates
+
+Before production release:
+
+- `make test` must pass from a clean checkout.
+- `make audit` must record OpenSSL version and dynamic linkage.
+- `make openssl-advisory-check` must accept the linked OpenSSL release line.
+- `make sbom` must record source, generated library, native shim, OpenSSL, and
+  linkage evidence.
+- `make reproducibility-report` must report `status=match`.
+- `make target-evidence` must record reviewed target OpenSSL, platform CVE,
+  external crypto/FFI, downstream integration, and cross-platform smoke proof
+  before any production support claim.
+- Jerboa security scanner and the high-confidence secret scan must be clean.
+- OpenSSL CVE status and platform support must be documented in release notes
+  or `docs/openssl-advisory-review.md`.
+- Algorithm-policy exceptions must be documented.
diff --git a/docs/openssl-advisory-review.md b/docs/openssl-advisory-review.md
new file mode 100644
index 0000000..53a7b45
--- /dev/null
+++ b/docs/openssl-advisory-review.md
@@ -0,0 +1,50 @@
+# OpenSSL Advisory Review
+
+Review date: 2026-06-21
+
+`jerboa-crypto` is a low-level `libcrypto` FFI boundary. 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-crypto` delegates digest, HMAC, cipher, AEAD, Ed25519, random, and KDF
+operations to OpenSSL. It 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 this repo exposes raw
+cryptographic primitives to downstream Jerboa libraries and applications.
+
+## 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..18c8925
--- /dev/null
+++ b/docs/release-evidence.md
@@ -0,0 +1,85 @@
+# Release Evidence
+
+`jerboa-crypto` is a low-level OpenSSL `libcrypto` boundary. It is not
+production-ready until each release candidate has current build/test, SBOM,
+OpenSSL linkage, advisory/CVE, reproducibility, algorithm-policy, and
+downstream integration evidence.
+
+Run from a clean checkout:
+
+```sh
+make clean
+make release-evidence JERBUILD=/path/to/jerbuild
+make target-evidence
+```
+
+The `release-evidence` target runs `make test`, `make audit`, the OpenSSL
+advisory gate, SBOM generation, 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 OS and Jerboa toolchain identity.
+- `cc-version.txt` and `openssl-version.txt`.
+- `native-linkage.txt` from `otool -L` or `ldd`.
+- `openssl-advisory-check.txt` with the accepted OpenSSL release-line result.
+- `sbom.txt` and `sbom/` with source, generated library, native shim, OpenSSL,
+  and linkage evidence.
+- `reproducibility/result.txt` plus first/second native shim and generated
+  library hashes.
+- `target-evidence/`, a local blocked-status record or a reviewed target proof
+  copied with a SHA-256 sidecar.
+- `source-inputs.txt` for source, docs, tests, and evidence scripts.
+- `shim-sha256.txt` for the C shim source and built native shim.
+
+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.
+
+Protocol integrations must either follow `docs/algorithm-policy.md` or record a
+reviewed exception in release notes.
+
+Reproducibility evidence is claimed only for two clean builds in the same
+toolchain environment. The automated report compares the generated Jerboa
+library and native shim artifact. It archives hashes, status, and diffs only;
+raw build logs and native artifacts stay in scratch space.
+
+Release evidence must remain host-neutral. Use status fields such as
+`jerboa_home_status=present` instead of private toolchain paths, and `uname
+-srm` instead of full host-identifying platform banners. Do not store keys,
+tokens, plaintext crypto material, private paths, hostnames, customer data, or
+raw target command output as proof.
+
+## Target Production Proof
+
+`scripts/target-evidence.sh` writes `dist/target-evidence/status.txt`.
+Without a proof file, local release evidence records:
+
+- `target_evidence_status: local-release-proof-recorded`
+- `target_crypto_proof_status: not-run`
+- `release_host_openssl_advisory_status: blocked-not-run`
+- `target_openssl_linkage_status: blocked-not-run`
+- `platform_cve_review_status: blocked-not-run`
+- `external_crypto_ffi_review_status: blocked-not-run`
+- `algorithm_policy_exception_review_status: blocked-not-run`
+- `downstream_protocol_integration_status: blocked-not-run`
+- `cross_platform_smoke_status: blocked-not-run`
+- `production_crypto_status: blocked-not-run`
+
+To attach reviewed target evidence, set `JCRYPTO_TARGET_PROOF_FILE` to a file
+that contains these exact markers:
+
+```text
+release_host_openssl_advisory_status=reviewed
+target_openssl_linkage_status=reviewed
+platform_cve_review_status=reviewed
+external_crypto_ffi_review_status=reviewed
+algorithm_policy_exception_review_status=reviewed
+downstream_protocol_integration_status=reviewed
+cross_platform_smoke_status=reviewed
+sensitive_artifact_policy=no-secrets-private-paths-host-data-or-plaintext-crypto-material
+```
+
+Set `JCRYPTO_REQUIRE_TARGET_PROOF=1` on production release hosts to fail closed
+when proof is missing or incomplete. Proof files must be marker summaries only.
diff --git a/jerboa_crypto_shim.c b/jerboa_crypto_shim.c
index 4db5b9a..31e90ca 100644
--- a/jerboa_crypto_shim.c
+++ b/jerboa_crypto_shim.c
@@ -11,9 +11,18 @@
 #include <stdlib.h>
 #include <string.h>
 
+static int bad_len(int len) {
+    return len < 0;
+}
+
+static int missing_input(const void *ptr, int len) {
+    return len > 0 && ptr == NULL;
+}
+
 /* ---- Error handling ---- */
 
 int jerboa_crypto_err_get(char *buf, int buflen) {
+    if (buf == NULL || buflen <= 0) return -1;
     unsigned long e = ERR_get_error();
     if (e == 0) {
         buf[0] = 0;
@@ -26,6 +35,7 @@ int jerboa_crypto_err_get(char *buf, int buflen) {
 /* ---- Random ---- */
 
 int jerboa_rand_bytes(unsigned char *buf, int n) {
+    if (buf == NULL || bad_len(n)) return -1;
     return RAND_bytes(buf, n);
 }
 
@@ -40,16 +50,19 @@ void jerboa_digest_ctx_free(void *ctx) {
 }
 
 int jerboa_digest_init(void *ctx, const char *algo) {
+    if (ctx == NULL || algo == NULL) return -10;
     const EVP_MD *md = EVP_get_digestbyname(algo);
     if (!md) return -1;
     return EVP_DigestInit_ex((EVP_MD_CTX *)ctx, md, NULL) == 1 ? 0 : -2;
 }
 
 int jerboa_digest_update(void *ctx, const unsigned char *data, int len) {
+    if (ctx == NULL || bad_len(len) || missing_input(data, len)) return -10;
     return EVP_DigestUpdate((EVP_MD_CTX *)ctx, data, len) == 1 ? 0 : -1;
 }
 
 int jerboa_digest_final(void *ctx, unsigned char *out, int *outlen) {
+    if (ctx == NULL || out == NULL || outlen == NULL) return -10;
     unsigned int len = 0;
     int rc = EVP_DigestFinal_ex((EVP_MD_CTX *)ctx, out, &len);
     *outlen = (int)len;
@@ -57,6 +70,7 @@ int jerboa_digest_final(void *ctx, unsigned char *out, int *outlen) {
 }
 
 int jerboa_digest_size(const char *algo) {
+    if (algo == NULL) return -10;
     const EVP_MD *md = EVP_get_digestbyname(algo);
     if (!md) return -1;
     return EVP_MD_size(md);
@@ -65,6 +79,10 @@ int jerboa_digest_size(const char *algo) {
 /* One-shot digest */
 int jerboa_digest(const char *algo, const unsigned char *data, int datalen,
                 unsigned char *out, int *outlen) {
+    if (algo == NULL || out == NULL || outlen == NULL ||
+        bad_len(datalen) || missing_input(data, datalen)) {
+        return -10;
+    }
     const EVP_MD *md = EVP_get_digestbyname(algo);
     if (!md) return -1;
     EVP_MD_CTX *ctx = EVP_MD_CTX_new();
@@ -86,6 +104,11 @@ int jerboa_digest(const char *algo, const unsigned char *data, int datalen,
 int jerboa_hmac(const char *algo, const unsigned char *key, int keylen,
               const unsigned char *data, int datalen,
               unsigned char *out, int *outlen) {
+    if (algo == NULL || out == NULL || outlen == NULL ||
+        bad_len(keylen) || bad_len(datalen) ||
+        missing_input(key, keylen) || missing_input(data, datalen)) {
+        return -10;
+    }
     const EVP_MD *md = EVP_get_digestbyname(algo);
     if (!md) return -1;
     unsigned int len = 0;
@@ -105,18 +128,21 @@ void jerboa_cipher_ctx_free(void *ctx) {
 }
 
 int jerboa_cipher_key_length(const char *algo) {
+    if (algo == NULL) return -10;
     const EVP_CIPHER *c = EVP_get_cipherbyname(algo);
     if (!c) return -1;
     return EVP_CIPHER_key_length(c);
 }
 
 int jerboa_cipher_iv_length(const char *algo) {
+    if (algo == NULL) return -10;
     const EVP_CIPHER *c = EVP_get_cipherbyname(algo);
     if (!c) return -1;
     return EVP_CIPHER_iv_length(c);
 }
 
 int jerboa_cipher_block_size(const char *algo) {
+    if (algo == NULL) return -10;
     const EVP_CIPHER *c = EVP_get_cipherbyname(algo);
     if (!c) return -1;
     return EVP_CIPHER_block_size(c);
@@ -124,6 +150,7 @@ int jerboa_cipher_block_size(const char *algo) {
 
 int jerboa_encrypt_init(void *ctx, const char *algo,
                       const unsigned char *key, const unsigned char *iv) {
+    if (ctx == NULL || algo == NULL || key == NULL || iv == NULL) return -10;
     const EVP_CIPHER *c = EVP_get_cipherbyname(algo);
     if (!c) return -1;
     return EVP_EncryptInit_ex((EVP_CIPHER_CTX *)ctx, c, NULL, key, iv) == 1 ? 0 : -2;
@@ -131,15 +158,21 @@ int jerboa_encrypt_init(void *ctx, const char *algo,
 
 int jerboa_encrypt_update(void *ctx, const unsigned char *in, int inlen,
                         unsigned char *out, int *outlen) {
+    if (ctx == NULL || out == NULL || outlen == NULL ||
+        bad_len(inlen) || missing_input(in, inlen)) {
+        return -10;
+    }
     return EVP_EncryptUpdate((EVP_CIPHER_CTX *)ctx, out, outlen, in, inlen) == 1 ? 0 : -1;
 }
 
 int jerboa_encrypt_final(void *ctx, unsigned char *out, int *outlen) {
+    if (ctx == NULL || out == NULL || outlen == NULL) return -10;
     return EVP_EncryptFinal_ex((EVP_CIPHER_CTX *)ctx, out, outlen) == 1 ? 0 : -1;
 }
 
 int jerboa_decrypt_init(void *ctx, const char *algo,
                       const unsigned char *key, const unsigned char *iv) {
+    if (ctx == NULL || algo == NULL || key == NULL || iv == NULL) return -10;
     const EVP_CIPHER *c = EVP_get_cipherbyname(algo);
     if (!c) return -1;