Add Secmon SBOM release evidence

ober

90e73bd3095852f2f884308c711486ccde2827fb

diff --git a/.jerboa/security.json b/.jerboa/security.json
index 3a50968..449f92a 100644
--- a/.jerboa/security.json
+++ b/.jerboa/security.json
@@ -72,6 +72,11 @@
       "requireTempCleanup": true
     },
     "releaseEvidence": {
+      "sbom": {
+        "status": "required",
+        "marker": "sbom_status=present",
+        "scope": "source-typed-rust-binaries-target-proof-posture"
+      },
       "targetLoadProof": {
         "proofFileEnv": "JSECMON_TARGET_LOAD_PROOF_FILE",
         "requireEnv": "JSECMON_REQUIRE_TARGET_LOAD_PROOF",
diff --git a/Makefile b/Makefile
index 20becfe..b9225fa 100644
--- a/Makefile
+++ b/Makefile
@@ -15,13 +15,14 @@ BUILD  ?= build/rust
 TYPED  := $(wildcard typed/*.ss)
 DIST_DIR ?= dist/release-evidence
 REPRO_DIR ?= dist/reproducibility
+SBOM_DIR ?= dist/sbom
 SOAK_DIR ?= dist/soak
 SOAK_TIMEOUT_SECONDS ?= 300
 SOAK_ITERATIONS ?= 8
 BINARY_SMOKE_TIMEOUT_SECONDS ?= 10
 
 .PHONY: vendor-deps ensure-jerboa ensure-jsqlite rust test ffi-demo kernels-check triage-check triage-store-check analytics-check detect-check storage-check entity-check threats-check geoip-check sigma-check yaml-rules-check buffer-check dns-sniffer-check suspicious-check netconn-check kernmod-check selinux-check container-check dns-servers-check sensitive-path-check dtrace-parse-check dtrace-runtime-check stealth-check ebpf-events-check ebpf-runtime-check proc-linux-check freebsd-parse-check event-meta-check config-check privdrop-check event-danger-check persistence-check file-change-check webshell-check platform-mounts-check analyze-cli-check collector-cli-check event-summary-check ioc-check frame-check correlate-check revshell-check cron-check logtamper-check detection-rules-check daemon-telemetry-check mux-telemetry-check ipaddr-check auth-check lolbin-check dga-check calendar-check monitor-process-check monitor-network-check monitor-files-check monitor-auth-check monitor-kernel-check monitor-cron-check monitor-container-check monitor-rootkit-check monitor-podman-check monitor-selinux-check monitor-lateral-check monitor-webshell-check monitor-revshell-check monitor-persistence-check monitor-logtamper-check monitor-dns-check monitor-manager-check event-json-check collector-check protocol-check event-codec-check local-store-check collector-pull-check agent-server-check checks native-runtime keygen analyze collector agent telemetry binaries clean
-.PHONY: security audit verify reproducibility-report release-evidence soak-check soak-evidence binary-smoke
+.PHONY: security audit verify sbom reproducibility-report release-evidence soak-check soak-evidence binary-smoke
 # Combined libdir path so sibling libraries `(jsecmon ...)` resolve to ./jsecmon
 # (a second --libdirs would replace, not append, the jerboa one).
 LIBDIRS := "$(CURDIR):$(JSQLITE_SRC):$(JERBOA)/lib"
@@ -660,13 +661,16 @@ soak-evidence:
 
 verify: security test checks audit
 
+sbom: rust
+	REPO_ROOT="$(CURDIR)" SBOM_DIR="$(SBOM_DIR)" BUILD="$(BUILD)" SCHEME="$(SCHEME)" sh scripts/sbom.sh
+
 reproducibility-report:
 	REPO_ROOT="$(CURDIR)" REPRO_DIR="$(REPRO_DIR)" MAKE="$(MAKE)" JERBOA="$(JERBOA)" JSQLITE_REPO="$(JSQLITE_REPO)" JSQLITE_SRC="$(JSQLITE_SRC)" SCHEME="$(SCHEME)" sh scripts/reproducibility-report.sh
 
 binary-smoke:
 	REPO_ROOT="$(CURDIR)" BINARY_SMOKE_TIMEOUT_SECONDS="$(BINARY_SMOKE_TIMEOUT_SECONDS)" sh scripts/binary-smoke.sh
 
-release-evidence: verify reproducibility-report
+release-evidence: verify reproducibility-report sbom
 	rm -rf "$(DIST_DIR)"
 	mkdir -p "$(DIST_DIR)"
 	$(MAKE) --no-print-directory soak-evidence
@@ -676,7 +680,7 @@ release-evidence: verify reproducibility-report
 	cp "$(SOAK_DIR)/soak-check.txt" "$(DIST_DIR)/soak-check.txt"
 	git rev-parse HEAD > "$(DIST_DIR)/git-commit.txt"
 	git status --short > "$(DIST_DIR)/git-status.txt"
-	uname -a > "$(DIST_DIR)/build-environment.txt"
+	{ echo "repo=jerboa-secmon"; echo "platform=$$(uname -srm)"; } > "$(DIST_DIR)/build-environment.txt"
 	"$(SCHEME)" --version >> "$(DIST_DIR)/build-environment.txt"
 	cargo metadata --manifest-path "$(BUILD)/Cargo.toml" --locked --format-version 1 > "$(DIST_DIR)/cargo-metadata-typed.json"
 	cargo audit --file "$(BUILD)/Cargo.lock" -D warnings > "$(DIST_DIR)/rustsec-typed.txt"
@@ -685,7 +689,10 @@ release-evidence: verify reproducibility-report
 	{ for bin in jsecmon-keygen jsecmon-analyze jsecmon-collector jsecmon-agent jsecmon-telemetry; do echo "== $$bin =="; if command -v otool >/dev/null 2>&1; then otool -L "$$bin"; elif command -v ldd >/dev/null 2>&1; then ldd "$$bin"; else echo "no dynamic-linkage inspector found"; fi; done; } > "$(DIST_DIR)/native-linkage.txt"
 	rm -rf "$(DIST_DIR)/reproducibility"
 	cp -R "$(REPRO_DIR)" "$(DIST_DIR)/reproducibility"
+	rm -rf "$(DIST_DIR)/sbom"
+	cp -R "$(SBOM_DIR)" "$(DIST_DIR)/sbom"
 	test "$$(grep '^status=' "$(DIST_DIR)/reproducibility/report.txt" | cut -d= -f2)" = "match"
+	sh scripts/sanitize-evidence.sh "$(DIST_DIR)"
 
 # ── Native binaries ───────────────────────────────────────────────────────────
 # The shippable artifacts are compiled, self-contained executables — never
diff --git a/docs/release-evidence.md b/docs/release-evidence.md
index fc61f47..85228e4 100644
--- a/docs/release-evidence.md
+++ b/docs/release-evidence.md
@@ -36,6 +36,9 @@ The evidence directory contains:
 - `build-environment.txt` with OS and Scheme toolchain identity.
 - Cargo dependency metadata and RustSec audit output for the generated typed
   Rust crate.
+- `sbom/`, a host-neutral SBOM-style manifest covering source, examples, docs,
+  release controls, typed Rust metadata, generated binary hashes when present,
+  and target proof posture. The SBOM status marker is `sbom_status=present`.
 - SHA-256 hashes for `jsecmon-keygen`, `jsecmon-analyze`, `jsecmon-collector`,
   `jsecmon-agent`, and `jsecmon-telemetry`.
 - Release input hashes and dynamic-linkage output from `otool` or `ldd`.
@@ -57,6 +60,7 @@ Required release markers:
 
 - `local_loopback_iterations_default: 8`
 - `production_load_status=blocked-not-run`
+- `sbom_status=present`
 - `target_load_proof_status: documented`
 - `target_telemetry_proof_status: documented`
 
diff --git a/scripts/sanitize-evidence.sh b/scripts/sanitize-evidence.sh
new file mode 100755
index 0000000..ff0b481
--- /dev/null
+++ b/scripts/sanitize-evidence.sh
@@ -0,0 +1,29 @@
+#!/bin/sh
+set -eu
+
+if [ "$#" -eq 0 ]; then
+    set -- dist/release-evidence
+fi
+
+for evidence_dir in "$@"; do
+    if [ ! -d "$evidence_dir" ]; then
+        continue
+    fi
+
+    find "$evidence_dir" -type f -print | while IFS= read -r file; do
+        if ! LC_ALL=C grep -Iq . "$file" 2>/dev/null; then
+            continue
+        fi
+        perl -0pi -e '
+          s#/Users/[^[:space:]\x22\x27()]+#<local-path>#g;
+          if (defined $ENV{HOME} && length $ENV{HOME}) {
+            my $home = quotemeta($ENV{HOME});
+            s#$home#<home>#g;
+          }
+          s#~/mine#<local-path>#g;
+          s#\$\((HOME)\)/mine#<local-path>#g;
+          s#users-MacBook-Pro#<host>#g;
+          s#git\@#git-at-#g;
+        ' "$file"
+    done
+done
diff --git a/scripts/sbom.sh b/scripts/sbom.sh
new file mode 100755
index 0000000..4c22c2f
--- /dev/null
+++ b/scripts/sbom.sh
@@ -0,0 +1,118 @@
+#!/bin/sh
+set -eu
+
+repo_root=${REPO_ROOT:-$(pwd)}
+out_dir=${SBOM_DIR:-"$repo_root/dist/sbom"}
+build_dir=${BUILD:-build/rust}
+scheme=${SCHEME:-scheme}
+sanitizer=${JSECMON_EVIDENCE_SANITIZER:-scripts/sanitize-evidence.sh}
+
+case "$out_dir" in
+  /*) ;;
+  *) out_dir="$repo_root/$out_dir" ;;
+esac
+
+rm -rf "$out_dir"
+mkdir -p "$out_dir"
+cd "$repo_root"
+
+if [ -x "$sanitizer" ]; then
+  trap '"$sanitizer" "$out_dir"' EXIT
+fi
+
+sha256_file() {
+  if command -v sha256sum >/dev/null 2>&1; then
+    sha256sum "$1"
+  else
+    shasum -a 256 "$1"
+  fi
+}
+
+hash_manifest() {
+  dir=$1
+  out=$2
+  if [ -d "$dir" ]; then
+    (
+      cd "$dir"
+      find . -type f \
+        ! -path './.git/*' \
+        ! -path './dist/*' \
+        ! -path './build/*' \
+        ! -path './vendor/*' \
+        -print | LC_ALL=C sort | while IFS= read -r file; do
+          sha256_file "$file"
+        done
+    ) > "$out"
+  else
+    : > "$out"
+  fi
+}
+
+{
+  echo "repo=jerboa-secmon"
+  echo "generated_at_utc=$(date -u '+%Y-%m-%dT%H:%M:%SZ')"
+  echo "git_head=$(git rev-parse HEAD 2>/dev/null || true)"
+  echo "platform=$(uname -srm)"
+  echo "scheme_status=selected"
+  "$scheme" --version 2>/dev/null || true
+  echo "cargo=$(cargo --version 2>/dev/null || true)"
+  echo "rustc=$(rustc --version 2>/dev/null || true)"
+} > "$out_dir/build-environment.txt"
+
+if [ -f "$build_dir/Cargo.toml" ]; then
+  cp "$build_dir/Cargo.toml" "$out_dir/typed-Cargo.toml"
+  [ -f "$build_dir/Cargo.lock" ] && cp "$build_dir/Cargo.lock" "$out_dir/typed-Cargo.lock"
+  cargo metadata --manifest-path "$build_dir/Cargo.toml" --locked --format-version 1 > "$out_dir/cargo-metadata-typed.json" 2>&1 || true
+  if command -v cargo >/dev/null 2>&1 && cargo audit --version >/dev/null 2>&1 && [ -f "$build_dir/Cargo.lock" ]; then
+    cargo audit --file "$build_dir/Cargo.lock" -D warnings > "$out_dir/rustsec-typed.txt" 2>&1 \
+      && echo "cargo_audit_status=pass" > "$out_dir/cargo-audit.status" \
+      || echo "cargo_audit_status=fail" > "$out_dir/cargo-audit.status"
+  else
+    echo "cargo_audit_status=blocked" > "$out_dir/cargo-audit.status"
+    echo "cargo audit is not installed or Cargo.lock is missing." > "$out_dir/rustsec-typed.txt"
+  fi
+else
+  echo "cargo_metadata_status=blocked-build-rust-missing" > "$out_dir/cargo-audit.status"
+fi
+
+hash_manifest bin "$out_dir/bin.sha256"
+hash_manifest jsecmon "$out_dir/jsecmon.sha256"
+hash_manifest typed "$out_dir/typed.sha256"
+hash_manifest tests "$out_dir/tests.sha256"
+hash_manifest examples "$out_dir/examples.sha256"
+hash_manifest docs "$out_dir/docs.sha256"
+hash_manifest scripts "$out_dir/scripts.sha256"
+hash_manifest .github "$out_dir/github-workflows.sha256"
+
+if [ -f .jerboa/security.json ]; then
+  sha256_file .jerboa/security.json > "$out_dir/jerboa-security.sha256"
+else
+  : > "$out_dir/jerboa-security.sha256"
+fi
+
+find . -maxdepth 1 -type f \
+  \( -name 'Makefile' -o -name 'README.md' -o -name 'SECURITY.md' -o -name 'LICENSE' -o -name '*.ss' -o -name '*.c' \) \
+  -print | LC_ALL=C sort | while IFS= read -r file; do
+    sha256_file "$file"
+  done > "$out_dir/top-level.sha256"
+
+bins="jsecmon-keygen jsecmon-analyze jsecmon-collector jsecmon-agent jsecmon-telemetry"
+: > "$out_dir/binaries.sha256"
+for bin in $bins; do
+  if [ -f "$bin" ]; then
+    sha256_file "$bin" >> "$out_dir/binaries.sha256"
+  fi
+done
+
+{
+  echo "repo=jerboa-secmon"
+  echo "sbom_status=present"
+  echo "typed_rust_crate_status=$([ -f "$build_dir/Cargo.toml" ] && echo present || echo blocked-build-rust-missing)"
+  echo "binary_hash_status=$([ -s "$out_dir/binaries.sha256" ] && echo present || echo not-built)"
+  echo "crypto_provider=generated-typed-rust-and-jerboa-native"
+  echo "sqlite_provider=jsqlite"
+  echo "target_load_proof=fail-closed"
+  echo "target_telemetry_proof=fail-closed"
+} > "$out_dir/dependency-posture.txt"
+
+echo "status=generated"
diff --git a/scripts/security-check.sh b/scripts/security-check.sh
index 6a49340..f722fd7 100755
--- a/scripts/security-check.sh
+++ b/scripts/security-check.sh
@@ -41,6 +41,8 @@ require_file ".jerboa/security.json"
 require_file "scripts/build-binaries.sh"
 require_file "scripts/binary-smoke.sh"
 require_file "scripts/security-check.sh"
+require_file "scripts/sanitize-evidence.sh"
+require_file "scripts/sbom.sh"
 require_file "scripts/reproducibility-report.sh"
 require_file "scripts/soak-evidence.sh"
 require_file "jsecmon/tcp-runtime.ss"
@@ -52,6 +54,7 @@ find . -maxdepth 1 -iname "README*" -type f | grep -q . || {
 for release_marker in \
   'local_loopback_iterations_default: 8' \
   'production_load_status=blocked-not-run' \
+  'sbom_status=present' \
   'target_load_proof_status: documented' \
   'target_telemetry_proof_status: documented'
 do
@@ -75,6 +78,8 @@ require_text 'JSECMON_REQUIRE_TARGET_LOAD_PROOF' scripts/soak-evidence.sh
 require_text 'JSECMON_TARGET_TELEMETRY_PROOF_FILE' scripts/soak-evidence.sh
 require_text 'JSECMON_REQUIRE_TARGET_TELEMETRY_PROOF' scripts/soak-evidence.sh
 require_text 'blocked-target-proof' scripts/soak-evidence.sh
+require_text 'sbom_status=present' scripts/sbom.sh
+require_text 'scripts/sanitize-evidence.sh' Makefile
 
 if command -v rg >/dev/null 2>&1; then
   say "running high-confidence secret scan"