Security hardening and release readiness

ober

ce2fdaa32667c84a4090962c1b6f6093b01b2473

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..a66c0c2
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,40 @@
+name: CI
+
+on:
+  push:
+    branches: [main, master]
+  pull_request:
+  workflow_dispatch:
+
+permissions:
+  contents: read
+
+env:
+  JERBOA_VERSION: v0.2.3
+  JERBUILD: ${{ github.workspace }}/.jerboa/bin/jerbuild
+
+jobs:
+  verify:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v4
+
+      - name: Install system tools
+        run: |
+          set -eu
+          sudo apt-get update
+          sudo apt-get install -y --no-install-recommends ca-certificates curl git ripgrep
+
+      - name: Bootstrap Jerboa
+        run: |
+          set -eu
+          sh support/ensure-jerboa.sh "$JERBOA_VERSION" .jerboa/bin
+          "$JERBUILD" --jerboa-home
+
+      - name: Install cargo-audit
+        run: |
+          set -eu
+          cargo install cargo-audit --locked
+
+      - name: Verify
+        run: make verify
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 69ffb72..a0d8035 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,6 +3,7 @@
 *.o
 target/
 vendor/
+dist/
 /*-main.c
 keys/
 secmon-agent
diff --git a/.jerboa/security.json b/.jerboa/security.json
new file mode 100644
index 0000000..bb6df06
--- /dev/null
+++ b/.jerboa/security.json
@@ -0,0 +1,86 @@
+{
+  "version": 1,
+  "repo": "jerboa-secmonlib",
+  "extends": [
+    "jerboa:daemon",
+    "jerboa:security-monitor",
+    "jerboa:network-service",
+    "jerboa:static-binary",
+    "jerboa:ffi-boundary",
+    "jerboa:crypto-boundary"
+  ],
+  "paths": {
+    "production": [
+      ".jerbuild",
+      "Makefile",
+      "bin/**/*.ss",
+      "lib/**/*.sls",
+      "support/*.c",
+      "support/*.sh",
+      "patches/jerboa-native-rs/**/*.rs",
+      "patches/jerboa-native-rs/Cargo.toml",
+      "patches/jerboa-native-rs/Cargo.lock"
+    ],
+    "tests": ["tests/**"],
+    "generated": ["secmon-agent", "support/ffi-symbols.gen", "target/**", "build/**", "dist/**"],
+    "vendor": ["vendor/**"],
+    "docs": ["README.md", "SECURITY.md", "docs/**", "*.md"]
+  },
+  "policy": {
+    "failOn": ["critical", "high"],
+    "crypto": {
+      "customCrypto": "deny",
+      "requireAuditedDependencies": true,
+      "requireKeySeparation": true
+    },
+    "network": {
+      "requireTimeouts": true,
+      "requireConnectionLimits": true,
+      "requireAuthenticatedTransport": true
+    },
+    "daemon": {
+      "requirePrivilegeDrop": true,
+      "requireSandboxPlan": true,
+      "requireReleaseEvidence": true
+    },
+    "releaseEvidence": {
+      "targetLoadProof": {
+        "requiredForProduction": true,
+        "proofFileEnv": "JSECMONLIB_TARGET_LOAD_PROOF_FILE",
+        "failClosedEnv": "JSECMONLIB_REQUIRE_TARGET_LOAD_PROOF",
+        "requiredMarkers": [
+          "production_load_status=current-run-recorded",
+          "agent_runtime_status=release-host-runtime-recorded",
+          "collector_load_status=release-host-sustained-recorded",
+          "production_min_seconds_met=1"
+        ]
+      },
+      "targetSandboxProof": {
+        "requiredForProduction": true,
+        "proofFileEnv": "JSECMONLIB_TARGET_SANDBOX_PROOF_FILE",
+        "failClosedEnv": "JSECMONLIB_REQUIRE_TARGET_SANDBOX_PROOF",
+        "requiredMarkers": [
+          "sandbox_status=target-evidence-recorded",
+          "privilege_drop_status=target-evidence-recorded",
+          "negative_access_test_status=target-denied-recorded"
+        ]
+      },
+      "targetBinarySmokeProof": {
+        "requiredForProduction": true,
+        "proofFileEnv": "JSECMONLIB_TARGET_BINARY_SMOKE_PROOF_FILE",
+        "failClosedEnv": "JSECMONLIB_REQUIRE_TARGET_BINARY_SMOKE_PROOF",
+        "requiredMarkers": [
+          "target_binary_smoke_status=target-evidence-recorded",
+          "agent_no_secret_startup_status=pass",
+          "target_platform_status=linux-or-freebsd-recorded"
+        ]
+      }
+    },
+    "ffi": {
+      "requireCollectSafeBlockingCalls": true,
+      "requireRegisteredStaticSymbols": true,
+      "requireOwnershipDocumentation": true
+    }
+  },
+  "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 c2701d2..9017b4d 100644
--- a/Makefile
+++ b/Makefile
@@ -16,8 +16,14 @@ JSQLITE_URL  ?= https://git.sr.ht/~lisp/jsqlite
 JSQLITE_SRC  ?= $(JSQLITE_REPO)/src
 LIBDIRS := --libdirs lib:$(JSQLITE_SRC):$(JH)/lib
 JEXEC   := $(JERBUILD) exec $(LIBDIRS)
+CARGO_AUDIT ?= $(shell command -v cargo-audit 2>/dev/null || printf '%s/.cargo/bin/cargo-audit' "$$HOME")
+DIST_DIR ?= dist/release-evidence
+SBOM_DIR ?= dist/sbom
+REPRO_DIR ?= dist/reproducibility
+SOAK_DIR ?= dist/soak
+BINARY_SMOKE_DIR ?= dist/binary-smoke
 
-.PHONY: all help build binary keygen agent collector analyze test install clean ensure-jsqlite
+.PHONY: all help build binary keygen agent collector analyze test security audit verify sbom reproducibility-report soak-evidence binary-smoke release-evidence install clean ensure-jsqlite
 .DEFAULT_GOAL := help
 
 all: binary
@@ -35,6 +41,12 @@ help:
 	@echo "  make collector ARGS=.. Run the event collector"
 	@echo "  make analyze ARGS=..   Run the offline analyzer"
 	@echo "  make test              Run the test suite"
+	@echo "  make verify            Run release/security gates"
+	@echo "  make sbom              Write SBOM/provenance evidence"
+	@echo "  make reproducibility-report"
+	@echo "                         Compare two clean local binary builds"
+	@echo "  make soak-evidence     Record load/soak evidence status"
+	@echo "  make release-evidence  Write release evidence under dist/"
 
 # Standalone native binary via .jerbuild (entry bin/agent.ss -> secmon-agent).
 # Two passes: the first cargo-builds jerboa-native into
@@ -48,10 +60,13 @@ ensure-jsqlite:
 	fi
 	@test -f "$(JSQLITE_SRC)/jsqlite/api.ss"
 
+# The final pass stabilizes the WPO payload after the generated FFI symbol set
+# exists, so repeated clean release builds produce the same secmon-agent bytes.
 binary: ensure-jsqlite
 	@: > support/ffi-symbols.gen
 	$(JERBUILD) build
-	sh support/gen-ffi-symbols.sh
+	JERBUILD="$(JERBUILD)" sh support/gen-ffi-symbols.sh
+	$(JERBUILD) build
 	$(JERBUILD) build
 
 build: binary
@@ -76,6 +91,65 @@ test: ensure-jsqlite
 	fi
 	@echo "All tests passed."
 
+security:
+	scripts/security-check.sh
+
+audit:
+	@if ! [ -x "$(CARGO_AUDIT)" ]; then \
+		echo "cargo-audit is required. Install with: cargo install cargo-audit --locked"; \
+		exit 1; \
+	fi
+	cd patches/jerboa-native-rs && "$(CARGO_AUDIT)" audit
+
+verify: security test audit binary
+
+sbom:
+	JERBUILD="$(JERBUILD)" JSECMONLIB_SBOM_DIR="$(SBOM_DIR)" scripts/sbom.sh
+
+reproducibility-report:
+	JERBUILD="$(JERBUILD)" JSECMONLIB_REPRO_DIR="$(REPRO_DIR)" scripts/reproducibility-report.sh
+
+soak-evidence:
+	JSECMONLIB_SOAK_DIR="$(SOAK_DIR)" scripts/soak-evidence.sh
+
+binary-smoke:
+	JSECMONLIB_BINARY_SMOKE_DIR="$(BINARY_SMOKE_DIR)" scripts/binary-smoke.sh
+
+release-evidence: verify sbom reproducibility-report soak-evidence binary-smoke
+	rm -rf "$(DIST_DIR)"
+	mkdir -p "$(DIST_DIR)"
+	git rev-parse HEAD > "$(DIST_DIR)/git-commit.txt"
+	git status --short > "$(DIST_DIR)/git-status.txt"
+	uname -a > "$(DIST_DIR)/build-environment.txt"
+	$(JERBUILD) --version >> "$(DIST_DIR)/build-environment.txt"
+	$(JERBUILD) --jerboa-home >> "$(DIST_DIR)/build-environment.txt"
+	cargo metadata --manifest-path patches/jerboa-native-rs/Cargo.toml --locked --format-version 1 > "$(DIST_DIR)/cargo-metadata-native.json"
+	cd patches/jerboa-native-rs && "$(CARGO_AUDIT)" audit > "$(CURDIR)/$(DIST_DIR)/rustsec-native.txt"
+	shasum -a 256 secmon-agent > "$(DIST_DIR)/secmon-agent-sha256.txt"
+	shasum -a 256 \
+		.jerbuild \
+		Makefile \
+		SECURITY.md \
+		.jerboa/security.json \
+		docs/deployment-security.md \
+		docs/release-evidence.md \
+		docs/threat-model.md \
+		scripts/binary-smoke.sh \
+		scripts/reproducibility-report.sh \
+		scripts/sbom.sh \
+		scripts/security-check.sh \
+		scripts/soak-evidence.sh \
+		support/static-main.c \
+		support/gen-ffi-symbols.sh \
+		support/ensure-jerboa.sh \
+		> "$(DIST_DIR)/source-inputs-sha256.txt"
+	cp support/ffi-symbols.gen "$(DIST_DIR)/ffi-symbols.gen"
+	{ if command -v otool >/dev/null 2>&1; then otool -L secmon-agent; elif command -v ldd >/dev/null 2>&1; then ldd secmon-agent; else echo "no dynamic-linkage inspector found"; fi; } > "$(DIST_DIR)/native-linkage.txt"
+	cp -R "$(SBOM_DIR)" "$(DIST_DIR)/sbom"
+	cp -R "$(REPRO_DIR)" "$(DIST_DIR)/reproducibility"
+	cp -R "$(SOAK_DIR)" "$(DIST_DIR)/soak"
+	cp -R "$(BINARY_SMOKE_DIR)" "$(DIST_DIR)/binary-smoke"
+
 install: binary
 	mkdir -p $(BIN_DIR)
 	install -m 0755 $(BIN) $(BIN_DIR)/$(BIN)
diff --git a/README.md b/README.md
index 6dc1db2..3de2ec3 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,8 @@
-# jerboa-secmon
+# jerboa-secmonlib
+
+Static Jerboa security-monitor agent, collector/analyzer source, and native FFI
+registration support.
+
+This repo is not public-production-ready until `make release-evidence` is
+captured on the target Linux or FreeBSD release host and the blockers in
+`docs/release-evidence.md` are closed.
diff --git a/SECURITY.md b/SECURITY.md
new file mode 100644
index 0000000..34b5f20
--- /dev/null
+++ b/SECURITY.md
@@ -0,0 +1,37 @@
+# Security Policy
+
+This repository is part of the Jerboa ecosystem. Treat it as experimental unless the README and release notes explicitly state a stronger support level.
+
+Production claims require the release gates tracked in `~/Release-plan.md` and `~/mine/jerboa-production-readiness.md` to be complete for this repository.
+
+## Supported Status
+
+No public production-support commitment exists yet. Security-sensitive releases must be cut from a clean checkout after:
+
+- `make verify` passes with the pinned Jerboa toolchain.
+- `make release-evidence` has been archived for the release candidate.
+- `git status --short` shows only intentional release changes.
+- A secret scan is clean for the working tree, and history has been reviewed or intentionally reset before first public release.
+- The documented build and test commands pass.
+- Dependency and native-code audits are clean, or accepted risks are documented.
+- Any FFI, filesystem, network, shell, or credential-handling behavior is documented.
+- `docs/threat-model.md` and `docs/deployment-security.md` have been reviewed
+  for the target release platform.
+
+## Security Expectations
+
+- Do not commit secrets, private keys, tokens, production `.env` files, operational hostnames, customer/user data, or private infrastructure details.
+- Do not implement cryptography directly. Use audited libraries or delegated components.
+- Prefer Rust for byte-level parsing, FFI boundaries, crypto-adjacent integration, and sandbox setup.
+- Prefer Jerboa for policy, orchestration, configuration, routing, tests, and high-level behavior.
+- Document file I/O, shell execution, network access, FFI ownership/lifetime rules, and credential storage before publication.
+- Daemons and hostile-input parsers require threat models, fuzz harnesses, regression corpora, resource limits, sandboxing documentation, and review before public production use.
+- Static binaries must register only the FFI symbols provided by the exact Jerboa toolchain and native feature set used for the build.
+- Do not claim target-host sandbox enforcement unless the release evidence
+  includes the service profile and a negative access test for that platform.
+- Release CI for production candidates must set the fail-closed target proof
+  controls for load, sandbox/privilege-drop, and target binary smoke 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/bin/agent.ss b/bin/agent.ss
index db4ba4c..ccd6235 100644
--- a/bin/agent.ss
+++ b/bin/agent.ss
@@ -1,7 +1,7 @@
 #!chezscheme
 ;; secmon-agent: Security monitoring agent
 (import
-  (chezscheme)
+  (scheme)
   (jerboa prelude clean)
   (secmon config)
   (secmon crypto ecies)
diff --git a/bin/analyze.ss b/bin/analyze.ss
index a3740c0..f8ac27d 100644
--- a/bin/analyze.ss
+++ b/bin/analyze.ss
@@ -1,7 +1,7 @@
 #!chezscheme
 ;; secmon-analyze: Offline event analysis and detection
 (import
-  (chezscheme)
+  (scheme)
   (jerboa prelude clean)
   (secmon storage store)
   (std text json))
diff --git a/bin/collector.ss b/bin/collector.ss
index e0c514c..4305f97 100644
--- a/bin/collector.ss
+++ b/bin/collector.ss
@@ -1,7 +1,7 @@
 #!chezscheme
 ;; secmon-collector: Collect and decrypt events from remote agents
 (import
-  (chezscheme)
+  (scheme)
   (jerboa prelude clean)
   (secmon config)
   (secmon crypto ecies)
@@ -30,7 +30,7 @@
 
 (define (connect-to-agent host port)
   (let ([c-socket (foreign-procedure "socket" (int int int) int)]
-        [c-connect (foreign-procedure "connect" (int u8* int) int)])
+        [c-connect (foreign-procedure __collect_safe "connect" (int u8* int) int)])
     (let ([fd (c-socket 2 1 0)])
       (when (< fd 0)
         (error 'connect "socket() failed"))
@@ -63,12 +63,15 @@
     (flush-output-port port)))
 
 (define (recv-msg! port psk-auth)
-  (guard (e [#t #f])
-    (let* ([len-bv (get-bytevector-n port 4)]
-           [len (unpack-u32-le len-bv 0)])
+  (let* ([len-bv (get-bytevector-n port 4)])
+    (unless (and (bytevector? len-bv) (= (bytevector-length len-bv) 4))
+      (error 'recv-msg! "connection closed before frame length"))
+    (let ([len (unpack-u32-le len-bv 0)])
       (when (> len 10485760)
         (error 'recv-msg! "message too large"))
       (let ([encrypted (get-bytevector-n port len)])
+        (unless (and (bytevector? encrypted) (= (bytevector-length encrypted) len))
+          (error 'recv-msg! "connection closed before frame body"))
         (psk-decrypt-transport psk-auth encrypted)))))
 
 ;; --- Authenticate to agent ---
@@ -123,11 +126,10 @@
 ;; --- Event decryption ---
 
 (define (decrypt-event decryptor stored-ev)
-  (guard (e [#t #f])
-    (let* ([enc-bv (stored-event-encrypted-data stored-ev)]
-           [enc-payload (bytevector->encrypted-payload enc-bv)]
-           [plaintext (ecies-decrypt decryptor enc-payload)])
-      (bytevector->security-event plaintext))))
+  (let* ([enc-bv (stored-event-encrypted-data stored-ev)]
+         [enc-payload (bytevector->encrypted-payload enc-bv)]
+         [plaintext (ecies-decrypt decryptor enc-payload)])
+    (bytevector->security-event plaintext)))
 
 ;; --- Print event ---
 
@@ -369,22 +371,21 @@
               (poll-loop))))))))
 
 (define (store-decrypted-event! store stored-ev ev host-str)
-  (guard (e [#t (void)])
-    (let ([data (security-event-data ev)])
-      (store-event! store
-        (security-event-hostname ev)
-        host-str
-        (stored-event-seq stored-ev)
-        (security-event-timestamp-ms ev)
-        (symbol->string (security-event-event-type ev))
-        (symbol->string (security-event-severity ev))
-        (if (hashtable? data) (hashtable-ref data "pid" #f) #f)
-        (if (hashtable? data) (hashtable-ref data "process_name" #f) #f)
-        (format-event-summary ev)
-        (json-object->string (security-event->json ev)))
-      (store-set-last-seq! store host-str
-        (stored-event-seq stored-ev)
-        (security-event-hostname ev)))))
+  (let ([data (security-event-data ev)])
+    (store-event! store
+      (security-event-hostname ev)
+      host-str
+      (stored-event-seq stored-ev)
+      (security-event-timestamp-ms ev)
+      (symbol->string (security-event-event-type ev))
+      (symbol->string (security-event-severity ev))
+      (if (hashtable? data) (hashtable-ref data "pid" #f) #f)
+      (if (hashtable? data) (hashtable-ref data "process_name" #f) #f)
+      (format-event-summary ev)
+      (json-object->string (security-event->json ev)))
+    (store-set-last-seq! store host-str
+      (stored-event-seq stored-ev)
+      (security-event-hostname ev))))
 
 (define (format-event-summary ev)
   (let ([type (security-event-event-type ev)]
diff --git a/bin/keygen.ss b/bin/keygen.ss
index 9acdd74..ecfbb35 100644
--- a/bin/keygen.ss
+++ b/bin/keygen.ss
@@ -1,7 +1,7 @@
 #!chezscheme
 ;; secmon-keygen: Generate ECIES keypair and PSK
 (import
-  (chezscheme)
+  (scheme)
   (jerboa prelude clean)
   (secmon crypto keys)
   (std text hex))
diff --git a/docs/deployment-security.md b/docs/deployment-security.md
new file mode 100644
index 0000000..5d758ad
--- /dev/null
+++ b/docs/deployment-security.md
@@ -0,0 +1,68 @@
+# Deployment Security
+
+`secmon-agent` is not production-ready until this deployment posture is tested
+on the target Linux or FreeBSD host and archived with the release evidence.
+
+## Accounts And Files
+
+- Run the agent as a dedicated `secmon` user when the selected monitors do not
+  require root-only host telemetry.
+- When root is required, start with the minimum service-manager capability set
+  and drop privileges before accepting collector connections whenever the
+  target monitor mix allows it.
+- Store `keys/public.key` and `keys/psk.key` readable only by the service user.
+  Keep `keys/private.key` only on the collector host.
+- Keep event databases and release evidence outside web roots and shared temp
+  directories.
+
+## Network
+
+- Bind the agent listener to a management interface or loopback tunnel, not a
+  public interface.
+- Firewall the listener to collector hosts.
+- Rotate PSKs when a collector host or monitored host is rebuilt, reassigned, or
+  suspected compromised.
+
+## Sandbox Plan
+
+- Linux releases should prefer a systemd unit with `NoNewPrivileges=true`,
+  `PrivateTmp=true`, strict `ReadWritePaths` for the event buffer/log location,
+  and a reviewed `SystemCallFilter` or Landlock/seccomp profile.
+- FreeBSD releases should prefer Capsicum/service-jail isolation where the
+  selected monitors can operate with pre-opened paths and sockets.
+- Do not claim kernel-enforced sandboxing from Jerboa std stubs alone. Archive
+  the service unit, jail profile, Landlock/seccomp/Capsicum evidence, and a
+  negative test showing denied access to an out-of-policy path.
+
+## Release Evidence Required
+
+- `make release-evidence` from a clean checkout with a pinned `JERBUILD`.
+- `dist/release-evidence/reproducibility/result.txt` showing
+  `prewarm_status=present` and matching repeated binary, FFI symbol,
+  release-input, and cargo metadata status.
+- `dist/release-evidence/soak/status.txt` from a release host showing sustained
+  agent/collector load. Local Darwin evidence is expected to remain blocked.
+- `dist/release-evidence/binary-smoke/status.txt` showing target-platform
+  startup smoke, or an explicit platform block for non-target local evidence.
+- Current RustSec, source scanner, secret scan, and external review notes.
+
+For release candidates, run target proof capture with fail-closed controls:
+
+```sh
+JSECMONLIB_REQUIRE_TARGET_LOAD_PROOF=1 \
+JSECMONLIB_TARGET_LOAD_PROOF_FILE=/path/to/load-proof.txt \
+JSECMONLIB_REQUIRE_TARGET_SANDBOX_PROOF=1 \
+JSECMONLIB_TARGET_SANDBOX_PROOF_FILE=/path/to/sandbox-proof.txt \
+make soak-evidence
+
+JSECMONLIB_REQUIRE_TARGET_BINARY_SMOKE_PROOF=1 \
+JSECMONLIB_TARGET_BINARY_SMOKE_PROOF_FILE=/path/to/binary-smoke-proof.txt \
+make binary-smoke
+```
+
+The load proof must identify the target host, command line, binary hash, start
+and stop timestamps, duration, collector identity, and resource-limit outcome.
+The sandbox proof must include the service profile, privilege-drop result, and a
+negative access test against an out-of-policy path. The binary smoke proof must
+come from Linux or FreeBSD and show the no-secret startup validation path for
+the exact release binary.
diff --git a/docs/release-evidence.md b/docs/release-evidence.md
new file mode 100644
index 0000000..e7cbed5
--- /dev/null
+++ b/docs/release-evidence.md
@@ -0,0 +1,75 @@
+# Release Evidence
+
+`jerboa-secmonlib` contains the static Jerboa security-monitor agent, collector,
+analyzer, ECIES/PSK transport boundary, and native FFI registration path. It is
+not production-ready until a release candidate has current evidence for the
+full security gate, static binary linkage, dependency audit, and operational
+soak/load behavior.
+
+Run from a clean checkout:
+
+```sh
+make clean
+make release-evidence JERBUILD=/path/to/jerbuild
+```
+
+The `release-evidence` target runs `make verify` first, then writes untracked
+artifacts under `dist/release-evidence/`:
+
+- `git-commit.txt` and `git-status.txt`.
+- `build-environment.txt` with OS and Jerboa toolchain identity.
+- Cargo dependency metadata and RustSec audit output for the patched native
+  Rust crate.
+- SHA-256 hash for `secmon-agent`.
+- Static-binary source input hashes, generated FFI symbol list, and dynamic
+  linkage output from `otool` or `ldd`.
+- `sbom/` with a source manifest, source hashes, native Cargo metadata, and
+  release input hashes.
+- `reproducibility/` with a recorded WPO prewarm build, two measured local
+  binary builds, release-input hashes for policy/evidence scripts, and
+  `result.txt`.
+- `binary-smoke/` with target-platform startup status. On non-target platforms,
+  this records an explicit platform block instead of implying runtime success.
+- `soak/` with agent/collector load and sandbox status. Local default evidence
+  records blocked statuses until a Linux or FreeBSD release-host soak is run.
+
+Reproducibility is claimed only when `reproducibility/result.txt` records
+`prewarm_status=present`, matching `binary_status`, `ffi_symbols_status`,
+`release_inputs_status`, `cargo_metadata_status`, and overall `status`.
+
+Production readiness still requires target-host runtime evidence. The external
+review packet must include this file, `SECURITY.md`, `docs/threat-model.md`,
+`docs/deployment-security.md`, `.jerboa/security.json`, the generated evidence
+directory, scanner logs, sustained agent/collector load evidence, and any
+accepted RustSec, crypto, platform sandbox, or native-linkage risks.
+
+## Target Proof Controls
+
+Target-host evidence is recorded through explicit proof files. Set the matching
+`JSECMONLIB_REQUIRE_*_PROOF=1` variable in release CI to fail closed when the
+proof is absent or incomplete.
+
+- `JSECMONLIB_TARGET_LOAD_PROOF_FILE` with
+  `JSECMONLIB_REQUIRE_TARGET_LOAD_PROOF=1` records Linux/FreeBSD
+  agent/collector soak evidence. The proof must contain
+  `production_load_status=current-run-recorded`,
+  `agent_runtime_status=release-host-runtime-recorded`,
+  `collector_load_status=release-host-sustained-recorded`, and
+  `production_min_seconds_met=1`.
+- `JSECMONLIB_TARGET_SANDBOX_PROOF_FILE` with
+  `JSECMONLIB_REQUIRE_TARGET_SANDBOX_PROOF=1` records target sandbox and
+  privilege-drop evidence. The proof must contain
+  `sandbox_status=target-evidence-recorded`,
+  `privilege_drop_status=target-evidence-recorded`, and
+  `negative_access_test_status=target-denied-recorded`.
+- `JSECMONLIB_TARGET_BINARY_SMOKE_PROOF_FILE` with
+  `JSECMONLIB_REQUIRE_TARGET_BINARY_SMOKE_PROOF=1` records target binary
+  startup evidence. The proof must contain
+  `target_binary_smoke_status=target-evidence-recorded`,
+  `agent_no_secret_startup_status=pass`, and
+  `target_platform_status=linux-or-freebsd-recorded`.
+
+When proof is valid, the scripts copy it into `dist/release-evidence/soak/` or
+`dist/release-evidence/binary-smoke/` and write a `.sha256` sidecar. When proof
+is required but missing or incomplete, the status is `blocked-target-proof` and
+the evidence target fails.
diff --git a/docs/threat-model.md b/docs/threat-model.md
new file mode 100644
index 0000000..de8cca3
--- /dev/null
+++ b/docs/threat-model.md
@@ -0,0 +1,60 @@
+# Threat Model
+
+`jerboa-secmonlib` is a host security-monitoring agent plus collector/analyzer
+tooling. The production boundary is a monitored Linux or FreeBSD host running
+`secmon-agent`, a collector that authenticates with a PSK and decrypts events
+with the collector private key, and release tooling that builds a static Jerboa
+binary with a fixed native FFI symbol set.
+
+## Assets
+
+- Collector private key and PSK.
+- Agent public key, PSK, and local event buffer.
+- Collected event databases, IOC inputs, and analysis output.
+- Static binary build inputs, native Rust crate lockfile, FFI symbol list, and
+  release evidence.
+
+## Adversaries
+
+- Local users or malware on a monitored host trying to blind the agent, read
+  buffered events, inject fake telemetry, or pivot through the agent listener.
+- Network attackers trying to poll events without the PSK or replay frames.
+- Malformed host artifacts such as logs, process tables, DNS data, and file
+  metadata intended to crash parsers or exhaust memory.
+- Build-chain attackers attempting to change native FFI symbols, cargo
+  dependencies, generated artifacts, or release evidence after review.
+
+## Required Controls
+
+- The agent never receives the collector private key. It can encrypt events but
+  must not decrypt historical telemetry.
+- Collector connections must complete PSK authentication before event access.
+- Frame parsing must enforce size limits and exact reads.
+- Static binaries must use the exact FFI symbol list generated from the pinned
+  Jerboa/native toolchain.
+- Production deployments must run under a dedicated service account with the
+  smallest file and network access compatible with the selected monitors.
+- Release candidates must include SBOM, repeated-build reproducibility,
+  binary-smoke, and soak/load status evidence.
+
+## Current Non-Production Gaps
+
+- Production-length agent/collector load evidence is still required on a
+  Linux/FreeBSD release host.
+- Kernel sandbox enforcement is not claimed until Landlock, Capsicum, seccomp,
+  or an equivalent service manager profile is captured in release evidence.
+- Privileged telemetry monitors require target-OS review because some checks
+  need elevated read access to host security state.
+- External review is still required for the crypto boundary, native FFI symbol
+  registration, parser resource limits, and deployment policy.
+
+## Release Evidence Markers
+
+target_load_proof_status: documented
+target_sandbox_proof_status: documented
+target_binary_smoke_proof_status: documented
+
+Release automation must fail with `blocked-target-proof` when target
+agent/collector load, sandbox/privilege-drop, or target binary startup proof is
+required but absent or missing the markers documented in
+`docs/release-evidence.md`.
diff --git a/patches/jerboa-native-rs/Cargo.lock b/patches/jerboa-native-rs/Cargo.lock
index 712da09..bfcc461 100644
--- a/patches/jerboa-native-rs/Cargo.lock
+++ b/patches/jerboa-native-rs/Cargo.lock
@@ -486,7 +486,6 @@ dependencies = [
  "regex",
  "ring",
  "rustls",
- "rustls-pemfile",
  "rustls-pki-types",
  "scrypt",
  "sha2 0.10.9",
@@ -893,15 +892,6 @@ dependencies = [
 ]
 
 [[package]]
-name = "rustls-pemfile"
-version = "2.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50"
-dependencies = [
- "rustls-pki-types",
-]
-
-[[package]]
 name = "rustls-pki-types"
 version = "1.14.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/patches/jerboa-native-rs/Cargo.toml b/patches/jerboa-native-rs/Cargo.toml
index 8297d76..eb5f572 100644
--- a/patches/jerboa-native-rs/Cargo.toml
+++ b/patches/jerboa-native-rs/Cargo.toml
@@ -20,7 +20,6 @@ rcgen = { version = "0.13", features = ["ring", "pem"] }
 time = "0.3"
 rustls = { version = "0.23", default-features = false, features = ["ring", "logging", "std", "tls12"] }
 rustls-pki-types = "1"
-rustls-pemfile = "2"
 webpki-roots = "0.26"
 
 [target.'cfg(target_os = "linux")'.dependencies]
diff --git a/scripts/binary-smoke.sh b/scripts/binary-smoke.sh
new file mode 100755
index 0000000..3ce0748
--- /dev/null
+++ b/scripts/binary-smoke.sh
@@ -0,0 +1,122 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+ROOT=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
+cd "$ROOT"
+
+OUT=${JSECMONLIB_BINARY_SMOKE_DIR:-dist/binary-smoke}
+REQUIRE=${JSECMONLIB_REQUIRE_BINARY_SMOKE:-0}
+TARGET_BINARY_SMOKE_PROOF=${JSECMONLIB_TARGET_BINARY_SMOKE_PROOF_FILE:-}
+REQUIRE_TARGET_BINARY_SMOKE_PROOF=${JSECMONLIB_REQUIRE_TARGET_BINARY_SMOKE_PROOF:-0}
+PLATFORM=$(uname -s)
+
+rm -rf "$OUT"
+mkdir -p "$OUT"
+
+target_binary_smoke_proof_status=not-run
+
+write_status() {
+  local status=$1
+  local startup_status=$2
+  local rc=$3
+  local proof_status=$4
+  local reason=$5
+
+  {
+    echo "repo=jerboa-secmonlib"
+    echo "platform=$PLATFORM"
+    echo "status=$status"
+    echo "agent_no_secret_startup_status=$startup_status"
+    echo "agent_exit_code=$rc"
+    echo "target_binary_smoke_proof_status=$proof_status"
+    echo "reason=$reason"
+  } > "$OUT/status.txt"
+}
+
+has_marker() {
+  local file=$1
+  local marker=$2
+
+  grep -F -q "$marker" "$file"
+}
+
+require_marker() {
+  local file=$1
+  local marker=$2
+
+  if ! has_marker "$file" "$marker"; then
+    printf '[secmonlib-binary-smoke] target binary smoke proof is missing marker: %s\n' "$marker" >&2
+    return 1
+  fi
+}
+
+record_proof() {
+  local proof=$1
+  local destination=$2
+
+  cp "$proof" "$destination"
+  shasum -a 256 "$destination" > "$destination.sha256"
+}
+
+proof_requested=0
+if [ -n "$TARGET_BINARY_SMOKE_PROOF" ] || [ "$REQUIRE_TARGET_BINARY_SMOKE_PROOF" = "1" ]; then
+  proof_requested=1
+fi
+
+if [ "$proof_requested" = "1" ]; then
+  if [ -z "$TARGET_BINARY_SMOKE_PROOF" ] || [ ! -s "$TARGET_BINARY_SMOKE_PROOF" ]; then
+    write_status \
+      "blocked-target-proof" \
+      "blocked-target-proof" \
+      "not-run" \
+      "missing" \
+      "set JSECMONLIB_TARGET_BINARY_SMOKE_PROOF_FILE to a non-empty target-host binary smoke proof"
+    exit 1
+  fi
+  if ! require_marker "$TARGET_BINARY_SMOKE_PROOF" "target_binary_smoke_status=target-evidence-recorded" ||
+     ! require_marker "$TARGET_BINARY_SMOKE_PROOF" "agent_no_secret_startup_status=pass" ||
+     ! require_marker "$TARGET_BINARY_SMOKE_PROOF" "target_platform_status=linux-or-freebsd-recorded"; then
+    write_status \
+      "blocked-target-proof" \
+      "blocked-target-proof" \
+      "not-run" \
+      "incomplete" \
+      "target binary smoke proof is present but missing required target-platform markers"
+    exit 1
+  fi
+  record_proof "$TARGET_BINARY_SMOKE_PROOF" "$OUT/target-binary-smoke-proof.txt"
+  target_binary_smoke_proof_status=present
+  write_status \
+    "target-proof-recorded-review-required" \
+    "target-proof-recorded-review-required" \
+    "not-run" \