Security hardening and release readiness

ober

94c8d679c920c65591dd39f36246b00bdef01c92

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..d68c952
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,38 @@
+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
+
+      - name: Install Jerboa toolchain
+        run: sh support/ensure-jerboa.sh "$JERBOA_VERSION" .jerboa/bin
+
+      - name: Test
+        run: make test-linux
+
+      - name: Audit native linkage
+        run: make audit
+
+      - name: Release evidence
+        run: make release-evidence
diff --git a/.github/workflows/security-baseline.yml b/.github/workflows/security-baseline.yml
new file mode 100644
index 0000000..28a713e
--- /dev/null
+++ b/.github/workflows/security-baseline.yml
@@ -0,0 +1,35 @@
+name: Security Baseline
+
+on:
+  push:
+    branches: [main, master]
+  pull_request:
+  workflow_dispatch:
+
+permissions:
+  contents: read
+
+jobs:
+  baseline:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v4
+
+      - name: Required release files
+        run: |
+          set -eu
+          test -f LICENSE
+          test -f SECURITY.md
+          test -f .gitignore
+          find . -maxdepth 1 -iname "README*" -type f | grep -q .
+
+      - name: High-confidence secret scan
+        run: |
+          set -eu
+          pattern="(BEGIN (RSA|OPENSSH|EC|DSA|PRIVATE) KEY|ghp_[A-Za-z0-9_]{20,}|github_pat_[A-Za-z0-9_]{20,}|sk-(ant-api03|proj|svcacct)-[A-Za-z0-9_-]{30,}|AKIA[0-9A-Z]{16})"
+          matches="$(git grep -n -I -E "$pattern" -- . ":!*.png" ":!*.jpg" ":!*.jpeg" ":!*.gif" ":!*.so" ":!*.dylib" ":!*.o" ":!*.a" ":!*.boot" ":!*.tar.gz" || true)"
+          if [ -n "$matches" ]; then
+            echo "$matches"
+            echo "High-confidence secret pattern found."
+            exit 1
+          fi
diff --git a/.gitignore b/.gitignore
index 5381726..5258088 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,3 +4,8 @@ lib/
 *.wpo
 *.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..a158631
--- /dev/null
+++ b/.jerboa/security.json
@@ -0,0 +1,44 @@
+{
+  "repository": "jerboa-inotify",
+  "riskClass": "linux-native-ffi-filesystem-monitor",
+  "status": "experimental-linux-only",
+  "releaseEvidence": {
+    "requiredTargets": [
+      "security",
+      "test",
+      "audit",
+      "platform-evidence",
+      "sbom",
+      "reproducibility-report",
+      "release-evidence"
+    ],
+    "sensitiveArtifactPolicy": "no-private-paths-host-data-secrets-or-raw-production-watch-output"
+  },
+  "targetEvidence": {
+    "linuxProofFileEnv": "JERBOA_INOTIFY_TARGET_LINUX_PROOF_FILE",
+    "linuxRequireProofEnv": "JERBOA_INOTIFY_REQUIRE_TARGET_LINUX_PROOF",
+    "overflowProofFileEnv": "JERBOA_INOTIFY_TARGET_OVERFLOW_PROOF_FILE",
+    "overflowRequireProofEnv": "JERBOA_INOTIFY_REQUIRE_TARGET_OVERFLOW_PROOF",
+    "statusFile": "dist/platform-evidence/status.txt",
+    "linuxRequiredMarkers": [
+      "target_platform_status=linux-recorded",
+      "linux_inotify_status=target-evidence-recorded",
+      "test_linux_status=target-evidence-recorded",
+      "native_linkage_status=target-evidence-recorded",
+      "native_shim_reproducibility_status=target-evidence-recorded"
+    ],
+    "overflowRequiredMarkers": [
+      "overflow_runtime_status=target-evidence-recorded",
+      "overflow_event_status=rescan-required-tested",
+      "backpressure_status=target-evidence-recorded",
+      "recursive_watch_status=target-evidence-recorded",
+      "path_logging_status=redaction-tested"
+    ]
+  },
+  "ffiBoundary": {
+    "nativeLoading": "lazy-via-jerboa-ffi",
+    "collectSafeCalls": true,
+    "cShim": "jerboa_inotify_shim.c",
+    "linuxRuntimeRequired": true
+  }
+}
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..261eeb9
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,201 @@
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
diff --git a/Makefile b/Makefile
index 874852a..0398897 100644
--- a/Makefile
+++ b/Makefile
@@ -1,43 +1,131 @@
-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
-
-CC      ?= cc
-CFLAGS  ?= -shared -fPIC -O2
-LIBS    ?=
-SHIM     = jerboa_inotify_shim.so
+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
+DIST_DIR ?= $(CURDIR)/dist
+RELEASE_EVIDENCE_DIR ?= $(DIST_DIR)/release-evidence
+PLATFORM_EVIDENCE_DIR ?= $(DIST_DIR)/platform-evidence
 
+CC ?= cc
+WARN_CFLAGS ?= -Wall -Wextra -Werror -Wformat=2 -Wshadow -Wpointer-arith -Wcast-align -Wwrite-strings
+HARDEN_CFLAGS ?= -fPIC -O2 -fstack-protector-strong -D_FORTIFY_SOURCE=2
+CFLAGS ?= $(HARDEN_CFLAGS) $(WARN_CFLAGS)
+LDFLAGS ?= -shared -Wl,-z,relro,-z,now
+LIBS ?=
+SHIM = jerboa_inotify_shim.so
 UNAME_S := $(shell uname -s)
 
-.PHONY: all build transpile test clean shim platform-check
+.PHONY: all build transpile test test-linux clean shim ensure-jerboa-tools audit \
+	platform-check platform-evidence security sbom reproducibility-report 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
+
 platform-check:
 	@if [ "$(UNAME_S)" != "Linux" ]; then \
-	  echo "inotify is a Linux kernel API; shim build + tests require Linux ($(UNAME_S) detected)."; \
-	  exit 1; \
+		echo "inotify is a Linux kernel API; runtime shim build/tests require Linux ($(UNAME_S) detected)."; \
+		exit 1; \
 	fi
 
 shim: platform-check $(SHIM)
 
-$(SHIM): jerboa_inotify_shim.c
-	$(CC) $(CFLAGS) -o $@ $< $(LIBS)
+$(SHIM): jerboa_inotify_shim.c Makefile
+	$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $< $(LIBS)
 
-transpile:
-	$(JERBUILD) transpile src lib --force
+transpile: ensure-jerboa-tools
+	@$(JERBUILD) transpile src lib --force
 
-build: transpile shim
+build: transpile
+	@if [ "$(UNAME_S)" = "Linux" ]; then $(MAKE) shim; else echo "Skipping inotify shim build on $(UNAME_S)."; fi
 
-test: build
+test: transpile
+	@if [ "$(UNAME_S)" = "Linux" ]; then \
+		$(MAKE) test-linux; \
+	else \
+		echo "Skipping inotify runtime tests on $(UNAME_S); CI runs them on Linux."; \
+	fi
+
+test-linux: build
 	JERBOA_INOTIFY_LIB=$(CURDIR) \
 	LD_LIBRARY_PATH=$(CURDIR) \
 	$(JERBUILD) exec --libdirs "$(LIBDIRS)" tests/inotify-test.ss
 
+audit: build
+	@echo "==> Platform"
+	@uname -srm
+	@echo "==> Native linkage for $(SHIM)"
+	@if [ "$(UNAME_S)" = "Linux" ] && command -v ldd >/dev/null 2>&1; then \
+		ldd "$(SHIM)"; \
+	else \
+		echo "No Linux shim linkage audit available on $(UNAME_S)."; \
+	fi
+
+platform-evidence:
+	@REPO_ROOT="$(CURDIR)" SHIM="$(SHIM)" PLATFORM_EVIDENCE_DIR="$(PLATFORM_EVIDENCE_DIR)" sh scripts/platform-evidence.sh
+
+security: scripts/security-check.sh
+	@sh scripts/security-check.sh
+
+sbom: transpile
+	@REPO_ROOT="$(CURDIR)" \
+	DIST_DIR="$(DIST_DIR)" \
+	JERBUILD="$(JERBUILD)" \
+	CC="$(CC)" \
+	SHIM="$(SHIM)" \
+	sh scripts/sbom.sh
+
+reproducibility-report:
+	@REPO_ROOT="$(CURDIR)" \
+	DIST_DIR="$(DIST_DIR)" \
+	JERBUILD="$(JERBUILD)" \
+	MAKE="$(MAKE)" \
+	SHIM="$(SHIM)" \
+	sh scripts/reproducibility-report.sh
+
+verify: security test audit platform-evidence sbom reproducibility-report
+
+release-evidence: security
+	@rm -rf "$(RELEASE_EVIDENCE_DIR)"
+	@mkdir -p "$(RELEASE_EVIDENCE_DIR)"
+	@echo "==> Collecting git status"
+	@git status --short > "$(RELEASE_EVIDENCE_DIR)/git-status.txt"
+	@git rev-parse HEAD > "$(RELEASE_EVIDENCE_DIR)/git-head.txt" 2>/dev/null || true
+	@echo "==> Running tests"
+	@$(MAKE) test > "$(RELEASE_EVIDENCE_DIR)/test.log" 2>&1
+	@echo "==> Running native audit"
+	@$(MAKE) audit > "$(RELEASE_EVIDENCE_DIR)/audit.log" 2>&1
+	@echo "==> Recording platform evidence"
+	@$(MAKE) platform-evidence > "$(RELEASE_EVIDENCE_DIR)/platform-evidence.log" 2>&1
+	@rm -rf "$(RELEASE_EVIDENCE_DIR)/platform-evidence"
+	@cp -R "$(PLATFORM_EVIDENCE_DIR)" "$(RELEASE_EVIDENCE_DIR)/platform-evidence"
+	@echo "==> Generating SBOM"
+	@$(MAKE) sbom > "$(RELEASE_EVIDENCE_DIR)/sbom.log" 2>&1
+	@rm -rf "$(RELEASE_EVIDENCE_DIR)/sbom"
+	@cp -R "$(DIST_DIR)/sbom" "$(RELEASE_EVIDENCE_DIR)/sbom"
+	@echo "==> Running reproducibility report"
+	@$(MAKE) reproducibility-report > "$(RELEASE_EVIDENCE_DIR)/reproducibility.log" 2>&1
+	@rm -rf "$(RELEASE_EVIDENCE_DIR)/reproducibility"
+	@cp -R "$(DIST_DIR)/reproducibility" "$(RELEASE_EVIDENCE_DIR)/reproducibility"
+	@{ \
+		echo "overflow_policy_status=present"; \
+		grep -E 'overflow_policy_status: documented|overflow_event_status: rescan-required|consumer_cache_status: dirty-until-rescan|backpressure_status: caller-required|recursive_watch_status: caller-managed|path_logging_status: redaction-required-for-sensitive-roots|target_overflow_proof_status: documented|proof_failure_status: blocked-target-proof' docs/overflow-handling.md; \
+	} > "$(RELEASE_EVIDENCE_DIR)/overflow-policy.txt"
+	@grep -q 'overflow_policy_status: documented' "$(RELEASE_EVIDENCE_DIR)/overflow-policy.txt"
+	@grep -q 'overflow_event_status: rescan-required' "$(RELEASE_EVIDENCE_DIR)/overflow-policy.txt"
+	@grep -q 'backpressure_status: caller-required' "$(RELEASE_EVIDENCE_DIR)/overflow-policy.txt"
+	@find src lib docs scripts -type f -print | LC_ALL=C sort | xargs shasum -a 256 > "$(RELEASE_EVIDENCE_DIR)/source-hashes.sha256"
+	@sh scripts/sanitize-evidence.sh "$(RELEASE_EVIDENCE_DIR)"
+	@sh scripts/sanitize-evidence.sh "$(PLATFORM_EVIDENCE_DIR)"
+	@sh scripts/sanitize-evidence.sh "$(DIST_DIR)/sbom"
+	@sh scripts/sanitize-evidence.sh "$(DIST_DIR)/reproducibility"
+	@echo "Release evidence written to $(RELEASE_EVIDENCE_DIR)"
+
 clean:
 	rm -f $(SHIM) chez_inotify_shim.so
-	rm -rf lib
+	rm -rf lib .jerboa/bin tests/tmp dist
diff --git a/README.md b/README.md
index ce7c3bd..c29c558 100644
--- a/README.md
+++ b/README.md
@@ -1,29 +1,58 @@
-# chez-inotify
+# jerboa-inotify
 
-Linux inotify filesystem event monitoring for Chez Scheme.
+Linux inotify filesystem event monitoring for Jerboa.
+
+Status: experimental and Linux-only. Treat this repository as a native FFI
+filesystem component until the release gates in `SECURITY.md` and
+`docs/ffi-boundary.md` are complete.
 
 ## Requirements
 
-- Chez Scheme 10.x
-- Linux (inotify is Linux-only)
-- GCC
+- Linux with inotify support
+- Jerboa `jerbuild`
+- C compiler
 
-## Build
+## Build And Test
 
-```bash
-make
+```sh
+make test-linux
+make audit
+make security
+make platform-evidence
+make sbom
+make reproducibility-report
+make release-evidence
 ```
 
+On non-Linux hosts, `make test` transpiles the Jerboa source and skips runtime
+tests. CI runs the real kernel tests on Ubuntu.
+
+If `jerbuild` is not installed, the Makefile bootstraps the pinned Jerboa
+release into `.jerboa/bin`.
+
+`make release-evidence` records test output, platform evidence, native linkage
+when Linux is available, SBOM manifests, and reproducibility output under
+`dist/release-evidence/`. On non-Linux hosts it is expected to report partial
+evidence because inotify runtime checks require the Linux kernel.
+
+Production Linux support is blocked until `make platform-evidence` records
+Linux runtime/linkage proof and overflow/backpressure proof through
+`JERBOA_INOTIFY_TARGET_LINUX_PROOF_FILE` and
+`JERBOA_INOTIFY_TARGET_OVERFLOW_PROOF_FILE`, or a Linux release host runs the
+full evidence target directly. Production release hosts should set the matching
+`JERBOA_INOTIFY_REQUIRE_TARGET_*_PROOF=1` variables when proof files are
+required. Proof files must not contain private paths, hostnames, secrets, raw
+production watch output, or raw command output.
+
 ## Usage
 
 ```scheme
-(import (chez-inotify))
+(import (jerboa-inotify))
 
 (define fd (inotify-init))
 (define wd (inotify-add-watch fd "/path/to/watch"
              (bitwise-ior IN_CREATE IN_DELETE IN_MODIFY)))
 
-;; Poll with 5 second timeout
 (when (inotify-poll fd 5000)
   (for-each
     (lambda (ev)
@@ -37,31 +66,32 @@ make
 (inotify-close fd)
 ```
 
+Consumers must handle `IN_Q_OVERFLOW` by rescanning watched state. Inotify does
+not recursively watch directory trees. Production callers should follow
+`docs/overflow-handling.md` for cache invalidation, backpressure, and path-log
+redaction.
+
 ## API
 
 | Function | Description |
-|----------|-------------|
+| --- | --- |
 | `(inotify-init)` | Create inotify instance, returns fd |
 | `(inotify-close fd)` | Close inotify fd |
 | `(inotify-add-watch fd path mask)` | Add watch, returns wd |
 | `(inotify-rm-watch fd wd)` | Remove watch |
-| `(inotify-poll fd timeout-ms)` | Poll for events, returns #t/#f |
-| `(inotify-read-events fd)` | Read all pending events |
+| `(inotify-poll fd timeout-ms)` | Poll for events |
+| `(inotify-read-events fd)` | Read pending events |
 
 ## Event Record
 
 | Accessor | Description |
-|----------|-------------|
+| --- | --- |
 | `(inotify-event-wd ev)` | Watch descriptor |
 | `(inotify-event-mask ev)` | Event mask |
 | `(inotify-event-cookie ev)` | Cookie for rename tracking |
-| `(inotify-event-name ev)` | Filename or #f |
-
-## Constants
-
-Watch masks: `IN_ACCESS`, `IN_ATTRIB`, `IN_CLOSE_WRITE`, `IN_CLOSE_NOWRITE`, `IN_CREATE`, `IN_DELETE`, `IN_DELETE_SELF`, `IN_MODIFY`, `IN_MOVE_SELF`, `IN_MOVED_FROM`, `IN_MOVED_TO`, `IN_OPEN`, `IN_ALL_EVENTS`, `IN_MOVE`, `IN_CLOSE`
+| `(inotify-event-name ev)` | Filename or `#f` |
 
-Watch flags: `IN_DONT_FOLLOW`, `IN_EXCL_UNLINK`, `IN_MASK_ADD`, `IN_ONESHOT`, `IN_ONLYDIR`
+## Security
 
-Event flags: `IN_IGNORED`, `IN_ISDIR`, `IN_Q_OVERFLOW`, `IN_UNMOUNT`
-# chez-inotify
+See `SECURITY.md`, `docs/ffi-boundary.md`, `docs/overflow-handling.md`, and
+`docs/release-evidence.md`.
diff --git a/SECURITY.md b/SECURITY.md
new file mode 100644
index 0000000..ac42e13
--- /dev/null
+++ b/SECURITY.md
@@ -0,0 +1,67 @@
+# Security Policy
+
+`jerboa-inotify` is experimental and Linux-only. Do not make production claims
+for this repository until the release gates below are complete from a clean
+Linux checkout.
+
+## Supported Status
+
+No public production-support commitment exists yet. Before a security-sensitive
+release:
+
+- `git status --short` shows only intentional release changes.
+- `make test-linux` and `make audit` pass locally on Linux and in CI.
+- The high-confidence secret scan is clean for the working tree, and repository
+  history has been reviewed or intentionally reset before first public release.
+- Linux kernel/platform support, native linkage, event-overflow behavior, and
+  unsupported-platform behavior are documented in release notes.
+- Event-overflow/backpressure consumer policy is documented in
+  `docs/overflow-handling.md`.
+- `make security` passes, including release-file, native-boundary, generated
+  artifact, private-path, and target-proof marker checks.
+- `make release-evidence` has been archived from Linux, including SBOM,
+  platform, linkage, test, and reproducibility evidence.
+- Target Linux runtime/linkage and overflow evidence is captured through the
+  proof controls documented in `docs/release-evidence.md`; production release
+  hosts set `JERBOA_INOTIFY_REQUIRE_TARGET_LINUX_PROOF=1` or
+  `JERBOA_INOTIFY_REQUIRE_TARGET_OVERFLOW_PROOF=1` with
+  `JERBOA_INOTIFY_TARGET_LINUX_PROOF_FILE` or
+  `JERBOA_INOTIFY_TARGET_OVERFLOW_PROOF_FILE`. Required missing, incomplete, or
+  sensitive proof material fails closed with `status=blocked-target-proof`.
+
+## Filesystem Monitoring Risks
+
+- Inotify is notification, not authorization. It must not be treated as a
+  sandbox, policy engine, or complete audit log.
+- Event queues can overflow. Consumers must handle `IN_Q_OVERFLOW` and rescan
+  watched state. Required consumer cache and backpressure behavior is documented
+  in `docs/overflow-handling.md`.
+- Recursive watching is not automatic. Consumers watching directory trees must
+  add watches for new subdirectories deliberately.
+- Paths and filenames can reveal sensitive local filesystem layout. Avoid
+  logging watched paths from private or production environments unless intended.
+
+Target proof files are marker-only release attestations. Do not include private
+paths, hostnames, secrets, production watch output, SSH clone strings, or raw
+command output. The platform-evidence script rejects overlarge, marker-
+incomplete, host-private, or high-confidence secret proof material.
+
+## Native Boundary
+
+The FFI boundary is documented in `docs/ffi-boundary.md`.
+
+Security-sensitive expectations:
+
+- Dynamic native loading is lazy and must not run at library import time.
+- Watched paths are length-checked and reject embedded NUL bytes before C calls.
+- Event parsing validates buffer bounds before reading kernel event fields.
+- `read`, `poll`, and add-watch operations are collect-safe FFI calls.
+- File descriptors and watch descriptors are caller-owned resources.
+- `make release-evidence` records non-Linux partial status honestly and must be
+  rerun on Linux before production claims.
+
+## 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/ffi-boundary.md b/docs/ffi-boundary.md
new file mode 100644
index 0000000..ca0d6cc
--- /dev/null
+++ b/docs/ffi-boundary.md
@@ -0,0 +1,53 @@
+# jerboa-inotify FFI Boundary
+
+`jerboa-inotify` exposes Linux inotify through `jerboa_inotify_shim.c`.
+
+## Trust Boundaries
+
+- Watched paths are untrusted strings. They are converted to UTF-8 bytevectors,
+  length-checked, rejected if empty or containing NUL, and copied into a C string
+  before `inotify_add_watch`.
+- Event buffers are kernel-provided data. The C shim validates offsets and event
+  lengths before reading fields or names.
+- Native loading is lazy through `(jerboa ffi)` so importing the module does not
+  crash non-Linux feature probes or static binaries.
+- `read`, `poll`, and `inotify_add_watch` are collect-safe FFI calls.
+
+## Memory And Lifetime Rules
+
+- Scheme owns read buffers and path bytevectors.
+- C does not retain Scheme-owned pointers after returning.
+- The caller owns the inotify file descriptor returned by `inotify-init` and must
+  close it with `inotify-close`.
+- Watch descriptors returned by `inotify-add-watch` should be removed with
+  `inotify-rm-watch` when no longer needed.
+
+## Operational Caveats
+
+- This repository is Linux-only. macOS and other platforms can transpile the
+  Jerboa source but cannot run the kernel runtime tests.
+- Inotify observes local filesystem events only. It is not an authorization or
+  sandbox boundary.
+- Event queues can overflow. Consumers must handle `IN_Q_OVERFLOW` and rescan
+  watched state when necessary. See `docs/overflow-handling.md` for the required
+  consumer cache and backpressure policy.
+- Recursive watching is a caller responsibility; inotify does not recursively
+  watch directory trees.
+
+## Release Gates
+
+Before production release:
+
+- `make test-linux` must pass on Linux from a clean checkout.
+- `make audit` must record platform and native linkage.
+- `make release-evidence` must be archived from Linux, including SBOM,
+  generated-library reproducibility, and native-shim reproducibility output.
+- Jerboa security scanner and the high-confidence secret scan must be clean.
+- Linux kernel/platform support and event-overflow behavior must be documented
+  in release notes.
+- Overflow/backpressure policy evidence must be present in `make
+  release-evidence` output.
+- Target Linux runtime/linkage and overflow evidence can be attached through
+  `JERBOA_INOTIFY_TARGET_LINUX_PROOF_FILE` and
+  `JERBOA_INOTIFY_TARGET_OVERFLOW_PROOF_FILE`; required proof flags fail closed
+  with `status=blocked-target-proof`.
diff --git a/docs/overflow-handling.md b/docs/overflow-handling.md
new file mode 100644
index 0000000..e7e6e5c
--- /dev/null
+++ b/docs/overflow-handling.md
@@ -0,0 +1,76 @@
+# Queue Overflow And Backpressure
+
+`jerboa-inotify` exposes Linux inotify events. It does not make the kernel event
+queue lossless. Consumers must treat queue overflow as a correctness boundary
+and repair their own view of watched state.
+
+Required release markers:
+
+- `overflow_policy_status: documented`
+- `overflow_event_status: rescan-required`
+- `consumer_cache_status: dirty-until-rescan`
+- `backpressure_status: caller-required`
+- `recursive_watch_status: caller-managed`
+- `path_logging_status: redaction-required-for-sensitive-roots`
+- `target_overflow_proof_status: documented`
+- `proof_failure_status: blocked-target-proof`
+
+## Required Consumer Shape
+
+When a consumer sees `IN_Q_OVERFLOW`, it must:
+
+- mark all cached state for the affected watcher as dirty;
+- stop trusting incremental event ordering;
+- rescan watched directories from a known root;
+- rebuild any derived index before reporting the system as caught up; and
+- emit a bounded diagnostic that does not expose sensitive watched paths unless
+  explicitly configured.
+
+Applications that cannot tolerate missed changes must combine inotify with a
+durable source of truth, such as periodic full scans, content hashes, mtime/size
+checks, or a database reconciliation step.
+
+## Backpressure
+
+Consumers should avoid unbounded per-event work. A production watcher should use
+one of these patterns:
+
+- bounded work queues that coalesce repeated changes for the same path;
+- a debounce window for high-churn directories;
+- per-root concurrency limits for expensive indexing or upload work;
+- explicit overflow counters and health status; and
+- a manual or scheduled full reconciliation path.
+
+If the work queue fills, the caller should mark the relevant watched root dirty
+and schedule a rescan instead of dropping events silently.
+
+## Recursive Watchers
+
+Inotify does not watch directory trees recursively. A recursive watcher must add
+watches for existing subdirectories during initial scan, add watches for newly
+created directories, remove watches for deleted directories, and rescan after
+overflow before trusting its watch set.
+
+## Path Logging
+
+Watched paths may reveal private project names, user names, host layouts, and
+secret-bearing directories. Production diagnostics should log root IDs or
+redacted path labels by default, with full path logging as an explicit local
+debug setting.
+
+## Target Evidence
+
+`JERBOA_INOTIFY_TARGET_OVERFLOW_PROOF_FILE` records Linux release-host overflow
+and consumer backpressure evidence. When
+`JERBOA_INOTIFY_REQUIRE_TARGET_OVERFLOW_PROOF=1`, missing or incomplete proof
+fails closed with `status=blocked-target-proof`.
+Proof files must remain marker-only and must not include private watched paths,
+hostnames, secrets, raw watch output, or raw command output.
+
+Required proof markers are:
+
+- `overflow_runtime_status=target-evidence-recorded`
+- `overflow_event_status=rescan-required-tested`
+- `backpressure_status=target-evidence-recorded`
+- `recursive_watch_status=target-evidence-recorded`
+- `path_logging_status=redaction-tested`
diff --git a/docs/release-evidence.md b/docs/release-evidence.md
new file mode 100644
index 0000000..11a0d20
--- /dev/null
+++ b/docs/release-evidence.md
@@ -0,0 +1,66 @@
+# Release Evidence
+
+`make release-evidence` writes ignored local evidence under
+`dist/release-evidence/`.
+
+The evidence bundle contains:
+
+- build/test output;
+- native linkage audit output when running on Linux;
+- platform evidence showing whether Linux inotify runtime checks were possible;
+- `platform-evidence/status.txt` plus optional copied target proof files and
+  `.sha256` sidecars;
+- overflow/backpressure policy evidence in `overflow-policy.txt`;
+- SBOM-style source, generated-library, build-environment, and native-shim
+  manifests;
+- repeated transpile reproducibility output;
+- repeated native-shim reproducibility output on Linux.
+
+Release evidence uses host-neutral platform fields and is sanitized before
+publication. Proof files and release artifacts must not preserve private paths,
+hostnames, secrets, SSH clone strings, raw production watch output, or raw
+command output. Reproducibility evidence archives hashes/status only; scratch
+build logs are not retained.
+
+On non-Linux hosts, the evidence is intentionally partial: transpilation and
+generated-library reproducibility can pass, but runtime, linkage, and native
+shim reproducibility report `status=blocked` because inotify is a Linux kernel
+API. Production release evidence must include a Linux run of `make
+release-evidence`.
+
+The overflow policy evidence records that `IN_Q_OVERFLOW` requires rescan,
+consumer cache state remains dirty until rescan completes, backpressure is a
+caller responsibility, recursive watches are caller-managed, and sensitive path
+logging needs redaction.
+
+## Target Proof Files
+
+`scripts/platform-evidence.sh` accepts two 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_INOTIFY_TARGET_LINUX_PROOF_FILE` with
+  `JERBOA_INOTIFY_REQUIRE_TARGET_LINUX_PROOF=1` records Linux runtime, test,
+  linkage, and native-shim reproducibility evidence. Required markers:
+  `target_platform_status=linux-recorded`,
+  `linux_inotify_status=target-evidence-recorded`,
+  `test_linux_status=target-evidence-recorded`,
+  `native_linkage_status=target-evidence-recorded`, and
+  `native_shim_reproducibility_status=target-evidence-recorded`.
+- `JERBOA_INOTIFY_TARGET_OVERFLOW_PROOF_FILE` with
+  `JERBOA_INOTIFY_REQUIRE_TARGET_OVERFLOW_PROOF=1` records overflow and
+  consumer backpressure evidence. Required markers:
+  `overflow_runtime_status=target-evidence-recorded`,
+  `overflow_event_status=rescan-required-tested`,
+  `backpressure_status=target-evidence-recorded`,
+  `recursive_watch_status=target-evidence-recorded`, and
+  `path_logging_status=redaction-tested`.
+
+Proof files must be 64 KiB or smaller and marker-only. Files containing private
+paths, host details, SSH clone strings, or high-confidence secret material fail
+closed before they are copied into release evidence.
+
+target_linux_proof_status: documented
+target_overflow_proof_status: documented
+proof_failure_status: blocked-target-proof
diff --git a/jerboa_inotify_shim.c b/jerboa_inotify_shim.c
index 49ff090..18634fc 100644
--- a/jerboa_inotify_shim.c
+++ b/jerboa_inotify_shim.c
@@ -1,122 +1,143 @@
-/* jerboa_inotify_shim.c — Linux inotify wrapper for Chez Scheme FFI */
+/* jerboa_inotify_shim.c - Linux inotify wrapper for Jerboa FFI */
 
-#include <sys/inotify.h>
-#include <unistd.h>
 #include <errno.h>
+#include <limits.h>
+#include <poll.h>
 #include <stdlib.h>
 #include <string.h>
-#include <poll.h>
+#include <sys/inotify.h>
+#include <unistd.h>
+
+static int bytes_have_nul(const unsigned char *src, int len) {
+    if (!src || len < 0) return 1;
+    for (int i = 0; i < len; i++) {
+        if (src[i] == 0) return 1;
+    }
+    return 0;
+}
+
+static char *copy_cstring(const unsigned char *src, int len) {
+    char *dst = NULL;
+    if (!src || len <= 0 || len == INT_MAX || bytes_have_nul(src, len)) return NULL;
+    dst = (char *)malloc((size_t)len + 1);
+    if (!dst) return NULL;
+    memcpy(dst, src, (size_t)len);
+    dst[len] = '\0';
+    return dst;
+}
+
+static ssize_t (*inotify_sys_read)(int, void *, size_t) = read;
+
+static const struct inotify_event *event_at(const unsigned char *buf, int buflen, int offset) {
+    const struct inotify_event *ev = NULL;
+    size_t base = sizeof(struct inotify_event);
+    size_t total;
+
+    if (!buf || buflen < 0 || offset < 0) return NULL;
+    if ((size_t)offset > (size_t)buflen) return NULL;
+    if ((size_t)buflen - (size_t)offset < base) return NULL;
+
+    ev = (const struct inotify_event *)(const void *)(buf + offset);
+    total = base + (size_t)ev->len;
+    if (total < base) return NULL;
+    if (total > (size_t)buflen - (size_t)offset) return NULL;
+    return ev;
+}
 
-/* Create an inotify instance with NONBLOCK and CLOEXEC flags.
-   Returns fd or -errno. */
 int jerboa_inotify_init(void) {
     int fd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC);
     if (fd < 0) return -errno;
     return fd;
 }
 
-/* Add a watch. Returns watch descriptor or -errno. */
-int jerboa_inotify_add_watch(int fd, const char *path, unsigned int mask) {
-    int wd = inotify_add_watch(fd, path, mask);
+int jerboa_inotify_add_watch_bv(int fd, const unsigned char *path, int path_len, unsigned int mask) {
+    char *path_c = copy_cstring(path, path_len);
+    int wd;
+
+    if (fd < 0 || !path_c) return -EINVAL;
+    wd = inotify_add_watch(fd, path_c, mask);
+    free(path_c);
     if (wd < 0) return -errno;
     return wd;
 }
 
-/* Remove a watch. Returns 0 or -errno. */
 int jerboa_inotify_rm_watch(int fd, int wd) {
+    if (fd < 0 || wd < 0) return -EINVAL;
     if (inotify_rm_watch(fd, wd) < 0) return -errno;
     return 0;
 }
 
-/* Read events into buffer. Returns bytes read, 0 if nothing available,
-   or -errno on error. */
 int jerboa_inotify_read(int fd, unsigned char *buf, int buflen) {
-    int n = read(fd, buf, buflen);
+    ssize_t n;
+
+    if (fd < 0 || !buf || buflen <= 0) return -EINVAL;
+    do {
+        n = inotify_sys_read(fd, buf, (size_t)buflen);
+    } while (n < 0 && errno == EINTR);
+
     if (n < 0) {
         if (errno == EAGAIN || errno == EWOULDBLOCK) return 0;
         return -errno;
     }
-    return n;
+    if (n > INT_MAX) return -EOVERFLOW;
+    return (int)n;
 }
 
-/* Poll for readability with timeout_ms (-1 = block, 0 = poll).
-   Returns 1 if readable, 0 if timeout, -errno on error. */
 int jerboa_inotify_poll(int fd, int timeout_ms) {
     struct pollfd pfd;
+    int rc;
+
+    if (fd < 0 || timeout_ms < -1) return -EINVAL;
     pfd.fd = fd;
     pfd.events = POLLIN;
-    int rc = poll(&pfd, 1, timeout_ms);
+    pfd.revents = 0;
+
+    do {
+        rc = poll(&pfd, 1, timeout_ms);
+    } while (rc < 0 && errno == EINTR);
+
     if (rc < 0) return -errno;
-    return rc;
+    if (rc == 0) return 0;
+    return (pfd.revents & POLLIN) ? 1 : 0;
 }