Security hardening and release readiness

ober

8121ef45c9e1da402fc00df7d2395e3b17c22f7a

diff --git a/.build.yml b/.build.yml
new file mode 100644
index 0000000..2961a4f
--- /dev/null
+++ b/.build.yml
@@ -0,0 +1,26 @@
+image: debian/stable
+packages:
+  - build-essential
+  - ca-certificates
+  - cargo
+  - curl
+  - git
+  - pkg-config
+  - ripgrep
+  - rustc
+environment:
+  JERBOA_VERSION: v0.2.3
+sources:
+  - https://git.sr.ht/~lisp/jerboa-coreutils
+tasks:
+  - install-jerbuild: |
+      curl -fsSL "https://github.com/jerboa-lang/jerboa/releases/download/${JERBOA_VERSION}/jerbuild-linux-x86_64" -o "$HOME/jerbuild"
+      chmod +x "$HOME/jerbuild"
+  - install-cargo-audit: |
+      cargo install cargo-audit --locked
+  - verify: |
+      cd jerboa-coreutils
+      JERBUILD="$HOME/jerbuild" make verify
+  - release-evidence: |
+      cd jerboa-coreutils
+      JERBUILD="$HOME/jerbuild" make release-evidence
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..ac2dfef
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,39 @@
+name: CI
+
+on:
+  push:
+    branches: [main, master]
+  pull_request:
+  workflow_dispatch:
+
+permissions:
+  contents: read
+
+jobs:
+  verify:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v4
+
+      - name: Install build tools
+        run: sudo apt-get update && sudo apt-get install -y build-essential curl ca-certificates git ripgrep pkg-config
+
+      - name: Install Rust
+        uses: dtolnay/rust-toolchain@stable
+
+      - name: Install cargo-audit
+        run: cargo install cargo-audit --locked
+
+      - name: Install jerbuild
+        run: |
+          set -eux
+          curl -fsSL "https://github.com/jerboa-lang/jerboa/releases/download/${JERBOA_VERSION}/jerbuild-linux-x86_64" -o /usr/local/bin/jerbuild
+          chmod +x /usr/local/bin/jerbuild
+        env:
+          JERBOA_VERSION: v0.2.3
+
+      - name: Verify
+        run: JERBUILD=/usr/local/bin/jerbuild make verify
+
+      - name: Release evidence
+        run: JERBUILD=/usr/local/bin/jerbuild make release-evidence
diff --git a/.gitignore b/.gitignore
index 2e0fe0f..1df6e3b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,6 +6,7 @@
 
 # jerbuild whole-program build scratch
 .build/
+dist/
 
 # Native binary + per-applet symlinks (rebuilt with `make binary`)
 /jerboa-coreutils
diff --git a/.jerboa/security.json b/.jerboa/security.json
new file mode 100644
index 0000000..9d24a30
--- /dev/null
+++ b/.jerboa/security.json
@@ -0,0 +1,61 @@
+{
+  "version": 1,
+  "repo": "jerboa-coreutils",
+  "extends": ["jerboa:cli", "jerboa:ffi", "jerboa:filesystem", "jerboa:process-tools"],
+  "classification": "multi-call local coreutils implementation with filesystem, process, terminal, checksum, and optional grep regex surfaces",
+  "license": "Apache-2.0",
+  "paths": {
+    "production": ["main-binary.ss", "src/**/*.ss", "lib/**/*.sls", "support/*.c", "support/*.list", ".jerbuild", "Makefile"],
+    "tests": ["tests/**", "support/import-check.ss", "tools/**"],
+    "generated": ["dist/**", ".build/**", "bin/**", "jerboa-coreutils", "jerboa-coreutils-*", "*.so", "*.dylib", "*.wpo", "*.o", "*.a", "vendor/**/target/**"],
+    "vendor": ["vendor/jerboa-native-rs/**"],
+    "docs": ["README.md", "SECURITY.md", "docs/**", "*.md"]
+  },
+  "capabilities": {
+    "network": "none expected in project source",
+    "ffi": "coreutils C shim, libc compatibility calls, PCRE2 runtime loading for grep, and jerboa-native-rs crypto for checksum applets",
+    "filesystemRead": "coreutils applets intentionally read caller-selected paths",
+    "filesystemWrite": "file-modifying applets intentionally write/delete caller-selected paths",
+    "shell": "process applets intentionally execute requested commands; checked wrappers validate and audit caller-requested command strings; fixed host-tool fallbacks are compatibility boundaries",
+    "kernelSandbox": "not claimed; non-enforcing std seccomp/Landlock stubs are not used as production controls",
+    "hostileInput": "command-line arguments, file paths, file contents, environment, terminal state, regex patterns"
+  },
+  "policy": {
+    "failOn": ["critical", "high", "medium"],
+    "imports": { "directChezscheme": "deny in source, entry, support checks, and tools" },
+    "ffi": { "allowedOnlyIn": ["src/jerboa-coreutils/**/*.ss", "support/libcoreutils.c", ".jerbuild"] },
+    "process": { "allowedOnlyIn": ["src/jerboa-coreutils/**/*.ss", "Makefile", "tests/**"] },
+    "network": { "allowed": false },
+    "eval": { "stringEval": "deny", "bareRead": "deny", "allowReadEval": false }
+  },
+  "releaseGates": [
+    "make security",
+    "make test",
+    "make verify",
+    "make focused-security-tests",
+    "make sbom",
+    "make reproducibility-report",
+    "make target-evidence",
+    "make release-evidence"
+  ],
+  "targetEvidence": {
+    "script": "scripts/target-evidence.sh",
+    "optionalProofFileEnv": "JCOREUTILS_TARGET_PROOF_FILE",
+    "requiredProofEnv": "JCOREUTILS_REQUIRE_TARGET_PROOF",
+    "maxProofBytes": 65536,
+    "targetProofStatus": "target_coreutils_proof_status",
+    "productionStatus": "production_coreutils_status",
+    "sensitiveArtifactPolicy": "no-file-content-private-paths-host-data-or-command-output-captures",
+    "requiredMarkers": [
+      "release_host_sbom_reproducibility_status=reviewed",
+      "pre_publication_history_secret_scan_status=reviewed",
+      "native_crate_rustsec_status=reviewed",
+      "external_ffi_review_status=reviewed",
+      "filesystem_process_review_status=reviewed",
+      "pcre2_target_linkage_status=reviewed",
+      "cross_platform_smoke_status=reviewed",
+      "sensitive_artifact_policy=no-file-content-private-paths-host-data-or-command-output-captures"
+    ]
+  },
+  "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 02351cb..61e4a56 100644
--- a/Makefile
+++ b/Makefile
@@ -10,19 +10,38 @@
 # separately built Chez. Cross targets additionally need the per-target cross
 # Chez (built once in $(JERBOA_HOME)) and the matching C cross toolchain.
 
-JERBOA_HOME ?= $(HOME)/mine/jerboa
-JERBUILD    ?= jerbuild
+JERBOA_HOME ?= $(CURDIR)/../jerboa
+JERBOA_VERSION ?= v0.2.3
+JERBOA_TOOL_DIR ?= $(CURDIR)/../jerboa/dist
+ifeq ($(origin JERBUILD),undefined)
+ifneq ($(wildcard $(JERBOA_TOOL_DIR)/jerbuild),)
+JERBUILD := $(JERBOA_TOOL_DIR)/jerbuild
+else
+JERBUILD := jerbuild
+endif
+endif
 
 # Bundled stdlib (used by the host `exec`/test helpers).
 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
 
 BIN     := jerboa-coreutils
 BIN_DIR := $(HOME)/.local/bin
+EVIDENCE_DIR ?= dist/release-evidence
+SBOM_DIR ?= dist/sbom
+REPRO_DIR ?= dist/reproducibility
+TARGET_EVIDENCE_DIR ?= dist/target-evidence
+LIBDIRS := lib:$(JH)/lib
 
 # Vendored Rust crate (crypto FFI for the checksum applets), linked by jerbuild
-# per .jerbuild. Sparse-cloned from the jerboa monorepo by `make vendor-deps`.
-NATIVE_DIR    := vendor/jerboa-native-rs
+# per .jerbuild. `make vendor-deps` refreshes it from the adjacent patched
+# Jerboa checkout when present; otherwise it sparse-clones from the remote.
+LOCAL_JERBOA_NATIVE_DIR ?= $(CURDIR)/../jerboa/jerboa-native-rs
+NATIVE_DIR ?= vendor/jerboa-native-rs
 JERBOA_REMOTE ?= https://git.sr.ht/~lisp/jerboa
+CARGO_AUDIT ?= cargo audit
 
 # Multi-call applet names. The binary dispatches on argv[0]; these are the
 # symlink names created next to $(BIN) by `make symlinks`.
@@ -68,7 +87,7 @@ FREEBSD_CC_ENV := CC_x86_64_unknown_freebsd="$(FREEBSD_AMD64_CC)" \
                   AR_x86_64_unknown_freebsd="$(LLVM_AR)" \
                   CARGO_TARGET_X86_64_UNKNOWN_FREEBSD_LINKER="$(FREEBSD_AMD64_CC)"
 
-.PHONY: all help gen binary symlinks test clean vendor-deps vendor-clean \
+.PHONY: all help gen binary symlinks test import-check focused-security-tests security audit verify sbom reproducibility-report target-evidence release-evidence clean clean-generated vendor-deps vendor-clean \
         linux linux-amd64 linux-arm64 freebsd freebsd-amd64 \
         cross-check-amd64 cross-check-arm64 cross-check-freebsd
 
@@ -82,36 +101,53 @@ help:
 	@echo "  make gen           Transpile src/ .ss -> lib/ .sls (jerbuild transpile)"
 	@echo "  make binary        Build native ./$(BIN) for this host (default applet build)"
 	@echo "  make symlinks      Create per-applet symlinks (cat, ls, ...) next to ./$(BIN)"
+	@echo "  make security      Static release/security checks"
+	@echo "  make verify        Security, audit, import check, and smoke tests"
+	@echo "  make release-evidence Generate dist/release-evidence"
 	@echo "  make linux-amd64   Cross-build $(BIN)-linux-amd64  (static musl)"
 	@echo "  make linux-arm64   Cross-build $(BIN)-linux-arm64  (static musl)"
 	@echo "  make freebsd-amd64 Cross-build $(BIN)-freebsd-amd64"
 	@echo "  make test          Build + smoke-test core applets"
+	@echo "  make focused-security-tests Run symlink/process argument security smokes"
 	@echo "  make install       Install ./$(BIN) + applet symlinks to $(BIN_DIR)"
 	@echo "  make clean         Remove build artifacts"
 	@echo ""
 	@echo "  Applets ($(words $(UTILS))): dispatched by argv[0] basename."
 
 # Sparse-clone just the jerboa-native-rs crate from the jerboa monorepo so a
-# fresh checkout populates vendor/. No-op once vendor/jerboa-native-rs exists.
-vendor-deps: $(NATIVE_DIR)
-$(NATIVE_DIR):
-	@mkdir -p vendor
-	@echo "=== Sparse-cloning jerboa-native-rs from $(JERBOA_REMOTE) ==="
-	@git clone --depth 1 --filter=blob:none --sparse $(JERBOA_REMOTE) vendor/.jerboa-monorepo
-	@git -C vendor/.jerboa-monorepo sparse-checkout set jerboa-native-rs
-	@mv vendor/.jerboa-monorepo/jerboa-native-rs $(NATIVE_DIR)
-	@rm -rf vendor/.jerboa-monorepo
+# fresh checkout populates vendor/. In this monorepo workspace, prefer the
+# adjacent patched checkout so release audit and binary inputs use the same
+# RustSec-clean lockfile.
+vendor-deps:
+	@if [ "$(NATIVE_DIR)" != "vendor/jerboa-native-rs" ]; then \
+	  test -f "$(NATIVE_DIR)/Cargo.toml" || { echo "ERROR: NATIVE_DIR missing Cargo.toml: $(NATIVE_DIR)" >&2; exit 1; }; \
+	elif [ -f "$(LOCAL_JERBOA_NATIVE_DIR)/Cargo.toml" ]; then \
+	  mkdir -p vendor; \
+	  echo "=== Syncing patched jerboa-native-rs from $(LOCAL_JERBOA_NATIVE_DIR) ==="; \
+	  rm -rf "$(NATIVE_DIR)"; \
+	  mkdir -p "$(NATIVE_DIR)"; \
+	  rsync -a --delete --exclude target/ "$(LOCAL_JERBOA_NATIVE_DIR)/" "$(NATIVE_DIR)/"; \
+	elif [ ! -f "$(NATIVE_DIR)/Cargo.toml" ]; then \
+	  mkdir -p vendor; \
+	  echo "=== Sparse-cloning jerboa-native-rs from $(JERBOA_REMOTE) ==="; \
+	  rm -rf vendor/.jerboa-monorepo; \
+	  git clone --depth 1 --filter=blob:none --sparse $(JERBOA_REMOTE) vendor/.jerboa-monorepo; \
+	  git -C vendor/.jerboa-monorepo sparse-checkout set jerboa-native-rs; \
+	  mv vendor/.jerboa-monorepo/jerboa-native-rs "$(NATIVE_DIR)"; \
+	  rm -rf vendor/.jerboa-monorepo; \
+	fi
+	@test -f "$(NATIVE_DIR)/Cargo.toml"
 
 vendor-clean:
 	rm -rf vendor/.jerboa-monorepo
 
 # Transpile Jerboa source to R6RS libraries jerbuild can whole-program compile.
 gen: vendor-deps
-	$(JERBUILD) transpile src lib
+	@$(JERBUILD) transpile src lib
 
 # Native multi-call binary for the host OS/arch. Reads .jerbuild.
 binary: gen
-	$(JERBUILD) build
+	@$(JERBUILD) build
 	@$(MAKE) --no-print-directory symlinks
 
 # Per-applet symlinks in bin/ pointing at the host ./$(BIN). Invoke applets as
@@ -198,7 +234,84 @@ test: binary
 	@printf 'apple\nbanana\n' | bin/grep an | grep -q banana && echo "PASS: grep"
 	@echo "=== ok ==="
 
+focused-security-tests: binary scripts/focused-security-tests.sh
+	@mkdir -p dist/focused-security-tests
+	@REPO_ROOT="$(CURDIR)" sh scripts/focused-security-tests.sh > dist/focused-security-tests/result.txt
+	@cat dist/focused-security-tests/result.txt
+
+import-check: gen
+	@$(JERBUILD) exec --libdirs "$(LIBDIRS)" support/import-check.ss
+
+security: clean-generated scripts/security-check.sh
+	scripts/security-check.sh
+
+audit: security vendor-deps
+	@echo "==> jerbuild"
+	@$(JERBUILD) --version
+	@echo "==> C compiler"
+	@cc --version 2>/dev/null | head -1 || cc -v 2>&1 | head -1
+	@echo "==> Rust crypto crate"
+	@cargo metadata --format-version 1 --manifest-path "$(NATIVE_DIR)/Cargo.toml" >/dev/null
+	@if ! $(CARGO_AUDIT) --version >/dev/null 2>&1; then \
+		echo "ERROR: cargo-audit is required for release evidence. Install with: cargo install cargo-audit --locked" >&2; \
+		exit 1; \
+	fi
+	@if [ -f "$(NATIVE_DIR)/Cargo.lock" ]; then \
+		$(CARGO_AUDIT) --file "$(NATIVE_DIR)/Cargo.lock" -D warnings; \
+	else \
+		(cd "$(NATIVE_DIR)" && $(CARGO_AUDIT) -D warnings); \
+	fi
+
+verify: security audit import-check test focused-security-tests target-evidence
+
+sbom: vendor-deps scripts/sbom.sh
+	@REPO_ROOT="$(CURDIR)" SBOM_DIR="$(SBOM_DIR)" JERBUILD="$(JERBUILD)" JERBOA_VERSION="$(JERBOA_VERSION)" sh scripts/sbom.sh
+
+reproducibility-report: vendor-deps scripts/reproducibility-report.sh
+	@REPO_ROOT="$(CURDIR)" REPRO_DIR="$(REPRO_DIR)" MAKE="$(MAKE)" sh scripts/reproducibility-report.sh
+
+target-evidence: scripts/target-evidence.sh
+	@REPO_ROOT="$(CURDIR)" TARGET_EVIDENCE_DIR="$(TARGET_EVIDENCE_DIR)" sh scripts/target-evidence.sh
+
+release-evidence: verify
+	$(MAKE) reproducibility-report
+	$(MAKE) sbom
+	rm -rf $(EVIDENCE_DIR)
+	mkdir -p $(EVIDENCE_DIR)
+	{ git rev-parse HEAD 2>/dev/null || true; } > $(EVIDENCE_DIR)/git-commit.txt
+	git status --short > $(EVIDENCE_DIR)/git-status.txt
+	git diff --stat > $(EVIDENCE_DIR)/diff-stat.txt
+	{ printf 'JERBOA_VERSION=%s\n' '$(JERBOA_VERSION)'; printf 'jerbuild_version='; $(JERBUILD) --version; if [ -n '$(JH)' ]; then printf 'jerboa_home_status=present\n'; else printf 'jerboa_home_status=missing\n'; fi; printf 'platform='; uname -srm; } > $(EVIDENCE_DIR)/build-env.txt
+	$(MAKE) security > $(EVIDENCE_DIR)/security.log 2>&1
+	$(MAKE) audit > $(EVIDENCE_DIR)/audit.log 2>&1
+	$(MAKE) import-check > $(EVIDENCE_DIR)/import-check.log 2>&1
+	$(MAKE) focused-security-tests > $(EVIDENCE_DIR)/focused-security-tests.log 2>&1
+	$(MAKE) test > $(EVIDENCE_DIR)/test.log 2>&1
+	@if [ -f "$(NATIVE_DIR)/Cargo.toml" ]; then cargo metadata --format-version 1 --manifest-path "$(NATIVE_DIR)/Cargo.toml" > $(EVIDENCE_DIR)/cargo-metadata.json; fi
+	@if command -v otool >/dev/null 2>&1 && [ -f "$(BIN)" ]; then otool -L "$(BIN)" > $(EVIDENCE_DIR)/binary-linkage.txt; elif command -v ldd >/dev/null 2>&1 && [ -f "$(BIN)" ]; then ldd "$(BIN)" > $(EVIDENCE_DIR)/binary-linkage.txt; fi
+	@if [ -f "$(BIN)" ]; then shasum -a 256 "$(BIN)" > $(EVIDENCE_DIR)/binary-sha256.txt; fi
+	{ find . \( -path './.git' -o -path './dist' -o -path './.build' -o -path './bin' -o -path './vendor/*/target' \) -prune -o -type f -not -name '$(BIN)' -not -name '$(BIN)-linux-amd64' -not -name '$(BIN)-linux-arm64' -not -name '$(BIN)-freebsd-amd64' -not -name '*.so' -not -name '*.dylib' -not -name '*.wpo' -not -name '*.o' -not -name '*.a' -print | LC_ALL=C sort | while IFS= read -r f; do shasum -a 256 "$$f"; done; } > $(EVIDENCE_DIR)/source-sha256.txt
+	rm -rf $(EVIDENCE_DIR)/sbom $(EVIDENCE_DIR)/reproducibility
+	cp -R $(SBOM_DIR) $(EVIDENCE_DIR)/sbom
+	cp -R $(REPRO_DIR) $(EVIDENCE_DIR)/reproducibility
+	cp -R $(TARGET_EVIDENCE_DIR) $(EVIDENCE_DIR)/target-evidence
+	cp -R dist/focused-security-tests $(EVIDENCE_DIR)/focused-security-tests
+	sh scripts/sanitize-evidence.sh "$(EVIDENCE_DIR)"
+	sh scripts/sanitize-evidence.sh "$(SBOM_DIR)"
+	sh scripts/sanitize-evidence.sh "$(REPRO_DIR)"
+	sh scripts/sanitize-evidence.sh "$(TARGET_EVIDENCE_DIR)"
+	test "$$(grep '^status=' "$(EVIDENCE_DIR)/reproducibility/report.txt" | cut -d= -f2)" = "match"
+
 clean:
 	rm -f $(BIN) $(BIN)-linux-amd64 $(BIN)-linux-arm64 $(BIN)-freebsd-amd64
+	rm -rf bin .build dist
+	find . -maxdepth 1 -type l -exec sh -c 'for p do [ "$$(readlink "$$p")" = "$(BIN)" ] && rm -f "$$p"; done' sh {} +
+	rm -f support/libcoreutils.so
+	find lib -name '*.so' -o -name '*.wpo' -o -name '*.hash' | xargs rm -f 2>/dev/null || true
+
+clean-generated:
+	rm -f $(BIN) $(BIN)-linux-amd64 $(BIN)-linux-arm64 $(BIN)-freebsd-amd64
 	rm -rf bin .build
+	find . -maxdepth 1 -type l -exec sh -c 'for p do [ "$$(readlink "$$p")" = "$(BIN)" ] && rm -f "$$p"; done' sh {} +
+	rm -f support/libcoreutils.so
 	find lib -name '*.so' -o -name '*.wpo' -o -name '*.hash' | xargs rm -f 2>/dev/null || true
diff --git a/README.md b/README.md
index 9fd4447..be32ac9 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,6 @@
 # jerboa-coreutils
 
-GNU coreutils implemented in [Jerboa](https://github.com/jafourni/jerboa) — Gerbil Scheme syntax and APIs running on stock Chez Scheme.
-
-This is a port of [gerbil-coreutils](https://github.com/jafourni/gerbil-coreutils) to the Jerboa platform, demonstrating that the same utility code works on an entirely different Scheme backend.
+GNU coreutils implemented in [Jerboa](https://github.com/jafourni/jerboa) on stock Chez Scheme.
 
 ## Architecture
 
@@ -56,7 +54,7 @@ jerboa-coreutils/
 ├── src/jerboa-coreutils/       # 113 utilities — Jerboa SOURCE (.ss)
 │   ├── common.ss               # Shared error handling, exit codes
 │   ├── common/io.ss            # File/line processing utilities
-│   ├── common/security.ss      # seccomp/landlock/audit (graceful degrade)
+│   ├── common/security.ss      # Checked sinks, audit, taint, FFI helpers
 │   ├── grep/pcre2.ss           # PCRE2 FFI bindings (lazy dlopen)
 │   └── true.ss ... grep.ss     # Individual utilities
 ├── lib/jerboa-coreutils/       # GENERATED R6RS libraries (jerbuild transpile)
@@ -97,16 +95,44 @@ jerboa-coreutils/
 
 **Phase 19-24 - Advanced:** who, split, dircolors, install, shred, pinky, basenc, b2sum, sum, od, csplit, pr, chcon, runcon, ptx, cp, mv, rm, ls, dd, stty, stdbuf, dir, vdir
 
-## How It Differs from gerbil-coreutils
+## Release Gates
+
+```bash
+make security
+make verify
+make sbom
+make reproducibility-report
+make target-evidence
+make release-evidence
+```
 
-| Aspect | gerbil-coreutils | jerboa-coreutils |
-|--------|-----------------|------------------|
-| Backend | Gerbil compiler + Gambit VM | Chez Scheme 10.x |
-| FFI | `begin-ffi` with inline C | C shim library + `foreign-procedure` |
-| Build | `gerbil build` | `make binary` (jerbuild: transpile + whole-program) |
-| Format | `.ss` Gerbil modules | `.ss` Jerboa source → `.sls` (generated) |
-| Getopt | 1-arg callback | 2-arg callback (cmd, hash) |
+`make verify` runs static security checks, dependency/native audit metadata,
+module import checks, focused symlink/process-argument security smokes, and the
+multicall smoke test suite. `make release-evidence` writes
+`dist/release-evidence`, including SBOM/toolchain data and repeated-build
+reproducibility evidence.
+
+Production support is still blocked until `make target-evidence` records a
+marker-complete proof through `JCOREUTILS_TARGET_PROOF_FILE`; production
+release hosts should set `JCOREUTILS_REQUIRE_TARGET_PROOF=1` so missing or
+incomplete proof fails closed. The proof is a summary of release-host SBOM and
+reproducibility review, history secret review, native RustSec review, external
+FFI review, filesystem/process review, PCRE2 target linkage, and cross-platform
+smoke status. It must not contain raw command output, file contents, private
+paths, hostnames, customer data, tokens, or keys.
+
+The current source scanner posture is clean at medium-or-higher severity.
+Kernel sandboxing is not claimed; deployments that need seccomp or Landlock
+must wrap the binary externally or add verified native enforcement.
+Rust advisory status is a fail-closed release gate: `make audit` runs
+`cargo audit --file <Cargo.lock> -D warnings` for the native crate, and missing
+`cargo-audit` or any advisory finding fails release evidence.
+
+This repository intentionally has filesystem, process, terminal, and FFI
+surfaces because it implements coreutils applets. The current production policy
+is documented in `docs/threat-model.md`, `docs/ffi-boundary.md`, and
+`docs/process-and-filesystem-policy.md`.
 
 ## License
 
-Same as gerbil-coreutils.
+Apache-2.0. See `LICENSE`.
diff --git a/SECURITY.md b/SECURITY.md
new file mode 100644
index 0000000..55ffdd3
--- /dev/null
+++ b/SECURITY.md
@@ -0,0 +1,53 @@
+# Security Policy
+
+`jerboa-coreutils` is a local multicall implementation of coreutils. It
+intentionally reads, writes, deletes, links, stats, and transforms
+caller-selected files, and several applets intentionally execute
+caller-requested processes.
+
+Production claims require the release gates tracked in `~/Release-plan.md` and
+the repository production-readiness tracker 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:
+
+- `git status --short` shows only intentional release changes.
+- `make security`, `make verify`, and `make release-evidence` pass.
+- The working tree secret scan is clean, and history has been reviewed or
+  intentionally reset before first public release.
+- Native and Rust dependency audit evidence is recorded.
+- `jerboa_security_scan` reports no medium-or-higher findings for the source
+  set.
+- SBOM/toolchain provenance, clean-build reproducibility comparison, and
+  external review are recorded or explicitly waived.
+- `make target-evidence` records a reviewed target proof through
+  `JCOREUTILS_TARGET_PROOF_FILE`, or records the local blocked status. Set
+  `JCOREUTILS_REQUIRE_TARGET_PROOF=1` on production release hosts.
+
+## Security Expectations
+
+- No network capability in project source.
+- No broad direct `(chezscheme)` imports in runtime, entry, support checks, or
+  tools.
+- No Scheme reader evaluation on untrusted inputs.
+- Native build artifacts must not be committed as source.
+- PCRE2 is loaded only for `grep`.
+- Process execution is limited to applets whose purpose requires it.
+- Production claims must not rely on non-enforcing seccomp/Landlock stubs; use
+  an external sandbox or add verified native enforcement for deployments that
+  require kernel sandboxing.
+- Finding or command output can include file contents, paths, users, process
+  data, and environment values; treat logs and release evidence accordingly.
+- Target proof files must be marker-only summaries. Do not store raw command
+  output, file contents, private host paths, hostnames, customer data, tokens,
+  or keys in release evidence.
+- Do not commit secrets, private keys, tokens, production `.env` files,
+  operational hostnames, customer/user data, or private infrastructure details.
+
+## 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..fd3ae6f
--- /dev/null
+++ b/docs/ffi-boundary.md
@@ -0,0 +1,28 @@
+# FFI Boundary
+
+The host binary links `support/libcoreutils.c` through `.jerbuild` and registers
+the project shim symbols listed in `support/coreutils-ffi-symbols.list`.
+
+The checksum applets use the vendored `jerboa-native-rs` crate with the
+`crypto` feature and no default features.
+
+`grep` loads PCRE2 lazily when regex matching is requested. `grep` is the only
+applet expected to load PCRE2.
+
+Coreutils FFI definitions use `define-coreutils-foreign` from
+`common/security.ss`. That wrapper resolves `foreign-procedure` bindings lazily
+on first use and calls Jerboa's static-aware loader helper instead of invoking
+`load-shared-object` during module initialization.
+
+Several compatibility applets still bind libc directly for portability, but
+path-heavy or stateful project helpers live in `support/libcoreutils.c`. New
+path FFI crossings must validate through `checked-path-value` and should prefer
+NUL-free bytevector/length C shim entry points. Blocking native calls should be
+marked `__collect_safe` where Chez permits it.
+
+Compatibility buffers in `support/libcoreutils.c` are caller-owned or
+thread-local; process-global mutable result buffers are not part of the
+supported release boundary.
+
+Native artifacts such as `support/libcoreutils.so`, `.so`, `.wpo`, `.a`, and
+the built `jerboa-coreutils` binary are release outputs, not source files.
diff --git a/docs/process-and-filesystem-policy.md b/docs/process-and-filesystem-policy.md
new file mode 100644
index 0000000..398340d
--- /dev/null
+++ b/docs/process-and-filesystem-policy.md
@@ -0,0 +1,43 @@
+# Process And Filesystem Policy
+
+Coreutils applets intentionally expose filesystem and process operations. The
+release posture is therefore deny-new-surface rather than deny-all-sinks.
+
+Allowed filesystem behavior:
+
+- read caller-selected files,
+- write or delete caller-selected files for modifying applets,
+- use `/dev/urandom` for random bytes,
+- read `/proc`, `/sys`, and terminal metadata for system-information applets.
+
+Allowed process behavior:
+
+- process applets such as `env`, `chroot`, `nice`, `nohup`, and `timeout`
+  execute caller-requested commands,
+- process-inspection applets may call fixed host tools as compatibility
+  fallbacks,
+- process-signaling applets may send caller-requested signals.
+
+Current hardened posture:
+
+- file open/write/delete sinks use checked wrappers with path validation,
+  embedded-NUL rejection, taint unwrapping, and audit hooks,
+- caller-requested process execution uses checked command wrappers before
+  crossing into the host process API,
+- compatibility host-tool fallbacks are fixed-command boundaries, not
+  attacker-selected executable paths,
+- known path-heavy FFI crossings validate paths before crossing into C, and
+  symlink reads use a bounded bytevector C shim.
+
+Kernel sandboxing is not claimed here. The repository no longer calls
+non-enforcing std seccomp/Landlock stubs as production controls; deployments
+that require kernel policy must use an external sandbox or a verified native
+enforcement layer.
+
+Remaining release evidence work:
+
+- run marker-complete target proof on the release host for native audit,
+  reproducibility, PCRE2 linkage, filesystem/process review, cross-platform
+  smoke, history secret review, and external FFI review,
+- keep target proof marker-only; do not preserve raw command output, file
+  contents, private paths, hostnames, customer data, or secrets.
diff --git a/docs/release-evidence.md b/docs/release-evidence.md
new file mode 100644
index 0000000..3858556
--- /dev/null
+++ b/docs/release-evidence.md
@@ -0,0 +1,71 @@
+# Release Evidence
+
+`make release-evidence` writes release evidence to `dist/release-evidence`.
+The coreutils reproducibility report uses `make clean-generated` between
+measured builds. That removes generated binaries, symlinks, native shims, and
+compiled artifacts while leaving ignored `dist/` evidence in place; `dist/` is
+excluded from source manifests.
+
+The bundle includes:
+
+- git commit, status, and diff summary,
+- Jerboa toolchain metadata,
+- security, audit, import-check, and test logs,
+- focused symlink deletion and process-argument security smoke output,
+- Cargo metadata for the vendored native crypto crate when present,
+- `sbom/`, with source/release-input hashes, Jerboa/C/Rust/PCRE2 toolchain
+  metadata, Cargo metadata, and vendored native crate hashes,
+- `reproducibility/report.txt`, comparing repeated binary, generated-library,
+  generated-input, normalized Jerbuild metadata, source-manifest, and applet
+  symlink manifests,
+- `target-evidence/`, a local blocked-status record or a reviewed target proof
+  copied with a SHA-256 sidecar,
+- binary linkage and SHA-256 digest,
+- source-file SHA-256 digests.
+
+Review `git-status.txt` and `diff-stat.txt` before publishing. Native build
+outputs and vendored `target/` directories are excluded from source hashes.
+The audit log must include a fail-closed RustSec run for the native crate using
+`cargo audit --file <Cargo.lock> -D warnings`. Missing `cargo-audit` or an
+advisory finding fails release evidence.
+
+Release evidence must remain host-neutral. Use status fields such as
+`jerboa_home_status=present` instead of private toolchain paths, and `uname
+-srm` instead of full host-identifying platform banners. Do not archive raw
+file contents, command output captures from target hosts, private paths,
+hostnames, customer data, or secrets as production proof.
+
+## Target Production Proof
+
+`scripts/target-evidence.sh` writes `dist/target-evidence/status.txt`.
+Without a proof file, local release evidence records:
+
+- `target_evidence_status: local-release-proof-recorded`
+- `target_coreutils_proof_status: not-run`
+- `release_host_sbom_reproducibility_status: blocked-not-run`
+- `pre_publication_history_secret_scan_status: blocked-not-run`
+- `native_crate_rustsec_status: blocked-not-run`
+- `external_ffi_review_status: blocked-not-run`
+- `filesystem_process_review_status: blocked-not-run`
+- `pcre2_target_linkage_status: blocked-not-run`
+- `cross_platform_smoke_status: blocked-not-run`
+- `production_coreutils_status: blocked-not-run`
+
+To attach reviewed target evidence, set `JCOREUTILS_TARGET_PROOF_FILE` to a
+file that contains these exact markers:
+
+```text
+release_host_sbom_reproducibility_status=reviewed
+pre_publication_history_secret_scan_status=reviewed
+native_crate_rustsec_status=reviewed
+external_ffi_review_status=reviewed
+filesystem_process_review_status=reviewed
+pcre2_target_linkage_status=reviewed
+cross_platform_smoke_status=reviewed
+sensitive_artifact_policy=no-file-content-private-paths-host-data-or-command-output-captures
+```
+
+Set `JCOREUTILS_REQUIRE_TARGET_PROOF=1` on production release hosts to fail
+closed when the proof file is missing or incomplete. Proof files must be marker
+summaries only; do not include file contents, command output captures, private
+paths, hostnames, customer data, tokens, or keys.
diff --git a/docs/threat-model.md b/docs/threat-model.md
new file mode 100644
index 0000000..d4333dc
--- /dev/null
+++ b/docs/threat-model.md
@@ -0,0 +1,39 @@
+# Threat Model
+
+`jerboa-coreutils` is a local multi-call command-line binary. It intentionally
+operates on caller-selected files, directories, processes, terminals, and
+environment variables.
+
+## Assets
+
+- Caller files and directory trees.
+- Process IDs and process-control operations.
+- Terminal state.
+- File contents printed to stdout/stderr.
+- Checksum inputs and outputs.
+
+## Trust Boundaries
+
+- Command-line arguments and environment variables are untrusted.
+- File contents and paths are untrusted.
+- Regex patterns for `grep` are untrusted.
+- FFI crosses into `support/libcoreutils.c`, libc compatibility calls, PCRE2,
+  and the vendored Jerboa native crypto crate.
+- Process applets intentionally cross into child-process execution.
+
+## Security Requirements
+
+- No network capability in this repository.
+- No broad direct `(chezscheme)` imports in source or tooling.
+- No Scheme reader evaluation on untrusted inputs.
+- C launcher writes must handle EINTR and short writes.
+- Native build artifacts must not be committed as source.
+- File open/write/delete and caller-requested process sinks route through
+  checked wrappers that reject embedded NULs and keep audit hooks visible.
+- FFI bindings are resolved lazily instead of loading native objects during
+  module initialization; compatibility C buffers are thread-local.
+- Kernel sandboxing is not claimed by this repository. Deployments requiring
+  seccomp or Landlock enforcement must wrap the binary externally or add and
+  verify a native enforcement layer.
+- Focused traversal, symlink, process-argument, reproducibility, and external
+  review evidence remain first-public-release requirements.
diff --git a/gaps.md b/gaps.md
index 4aa4679..963a087 100644
--- a/gaps.md
+++ b/gaps.md
@@ -28,10 +28,11 @@ splitting it into `-l` and `-a`. Similarly `-n3` is not parsed as `-n 3`.
 
 Only ~18 utilities register `--version`. The rest do not respond to it.
 
-### ~~1.4 Compile warning from `std/sugar`~~ **FIXED**
+### 1.4 Compile warning from bundled `std/sugar`
 
-The `with-catch` arity warning is suppressed via the `%apply1` indirection in
-`~/mine/jerboa/lib/std/sugar.sls`. No warning appears at runtime.
+`jerbuild` currently emits a compile-time warning from the bundled
+`std/sugar.sls` cache during whole-program builds. This warning is outside the
+coreutils source tree and has not affected the local smoke tests.
 
 ---
 
@@ -59,14 +60,16 @@ without the broken `(bytevector->hex digest)` wrapper.
 
 ## 3. FFI / Foreign Procedure Issues — ALL FIXED
 
-### ~~3.1 Missing `load-shared-object` in `id` and `groups`~~ **FIXED**
+### ~~3.1 Missing native symbol loading in `id` and `groups`~~ **FIXED**
 
-Both now have `(define _load-ffi (begin (load-shared-object #f) (void)))`.
+Coreutils FFI definitions now resolve lazily through
+`define-coreutils-foreign`; applet modules no longer call `load-shared-object`
+during initialization.
 
 ### ~~3.2 Broken `open-process-ports` usage in `readlink`~~ **FIXED**
 
-`readlink` and `realpath` now use proper `let-values` destructuring for
-`open-process-ports`' 4-value return.
+`readlink` now uses the bounded `coreutils_readlink_bv` C shim. `realpath`
+uses the direct project C shim instead of a subprocess parser.
 
 ---
 
diff --git a/huihui.md b/huihui.md
new file mode 100644
index 0000000..24f6f69
--- /dev/null
+++ b/huihui.md
@@ -0,0 +1,260 @@
+# Security Audit Report — jerboa-coreutils
+
+## Summary
+
+Audited 111 `.ss` source files in `src/jerboa-coreutils/`. 
+
+**Key finding**: No critical vulnerabilities (code execution, arbitrary file writes) found. The codebase shows good security hygiene with CSPRNGs, audit logging infrastructure, and seccomp/landlock support.
+
+---
+
+## I. Path Traversal & NUL Injection (HIGH)
+
+### Issue 1.1: NUL validation not universal
+**Files**: `common.ss`, `io.ss`, `cp.ss`, `ls.ss`, `grep.ss`
+**Severity**: Medium
+
+NUL byte checks are present in helpers (`path-within-base?`, `safe-path-join`) but not consistently applied to all FFI path arguments.
+
+```ss
+;; common.ss:86-93 — good validation, but only used by helpers
+(when (string-contains? path (string #\nul))
+  (error 'path-within-base? "path contains NUL byte"))
+```
+
+**Risk**: Commands that directly pass paths to FFI (`chmod`, `chown`, etc.) may accept untrusted input with NUL bytes.
+
+**Fix**: Wrap FFI path arguments with a helper that checks for `#\nul`.
+
+---
+
+### Issue 1.2: String concatenation without validation
+**Files**: `cp.ss:54`, `ls.ss:202-209`
+
+```ss
+;; cp.ss:54
+(string-append dst "/" (path-basename src))
+
+;; ls.ss:202-209
+(string-append path "/" name)
+```
+
+**Risk**: If paths contain NUL bytes, they may bypass checks in FFI calls or escape validation.
+
+---
+
+## II. Symlink & TOCTOU Vulnerabilities (MEDIUM)
+
+### Issue 2.1: Interactive overwrite creates TOCTOU window
+**File**: `cp.ss:97-103`
+
+```ss
+(when interactive
+  (unless (confirm-overwrite dst)
+    ...))
+```
+User input delay allows an attacker to replace the destination with a symlink.
+
+---
+
+### Issue 2.2: Symlink escapes during recursive copy
+**File**: `cp.ss:147-156`
+
+```ss
+(when (and (eq? s-type 'symlink)
+           (let ((target (read-symlink s)))
+             (and target
+                  (> (string-length target) 0)
+                  (eqv? (string-ref target 0) #\/)
+                  (not (path-within-base? target src)))))
+```
+✅ Good check that warns when symlink points outside source tree.
+
+---
+
+### Issue 2.3: Path stripping in ln may skip validation
+**File**: `ln.ss`
+
+No explicit symlink escape check for hardlinks, though hardlinks cannot cross filesystems by default.
+
+---
+
+## III. FFI/foreign-procedure Risks (MEDIUM)
+
+### Issue 3.1: Missing bounds checks in path arguments
+**Files**: `cp.ss`, `rm.ss`, `mv.ss`, `ln.ss`, `chown.ss`
+
+FFI procs like:
+```ss
+(define ffi-unlink (foreign-procedure "unlink" (string) int))
+```
+have no explicit length limits on path strings; Chez passes C pointers directly.
+
+---
+
+### Issue 3.2: Inconsistent FFI error propagation