security: constrain multicall cross xpatch load

ober

db0bdab5390e6b7c7e9aac66da83fdcf86c9182a

diff --git a/docs/kimi3-security-recommmendations.md b/docs/kimi3-security-recommmendations.md
index 2b0b7a8..5a9b40e 100644
--- a/docs/kimi3-security-recommmendations.md
+++ b/docs/kimi3-security-recommmendations.md
@@ -1145,7 +1145,10 @@ fix must add its scanner rule in the same commit (write it into
   `(std pkg commands)`. Terminal raw-mode restore now accepts only validated
   `stty -g` state tokens before invoking the restore command. The shared native
   loader now makes fixed-system-library bootstrap loads scanner-visible and
-  rejects non-literal system-library candidates at macro expansion time.
+  rejects non-literal system-library candidates at macro expansion time. The
+  multicall cross-builder now derives `xpatch` from the repo-local
+  `build/chez/xc-<machine>/s/xpatch` path and rejects mismatched
+  `JERBOA_XPATCH` values before loading it.
 
 ### K3-P2-06 — Documentation consistency pass
 **Serves:** G5. **Effort:** 2 days.
diff --git a/docs/security-reference.md b/docs/security-reference.md
index 2256e12..67d9a02 100644
--- a/docs/security-reference.md
+++ b/docs/security-reference.md
@@ -1012,6 +1012,9 @@ These are known gaps documented as current limitations, not implementation promi
   libc from the process image or fixed operating-system paths, requires a clean
   dynamic-loader environment before any fallback load, and rejects non-literal
   candidates to `native-loader-try-system-symbol!` at macro expansion time.
+- **Cross-build compiler patches are repo-local.** The multicall builder derives
+  `xpatch` from `build/chez/xc-<machine>/s/xpatch` and refuses mismatched
+  `JERBOA_XPATCH` values before loading the Chez cross-compiler patch.
 - **No red team evaluation.** No independent adversarial testing has been performed.
 - **Secure memory still exposes a raw region escape hatch.** The high-level
   `secure-bytevector` API is bounds-checked and integrated with
diff --git a/docs/status.md b/docs/status.md
index 58c7bad..e84ed1c 100644
--- a/docs/status.md
+++ b/docs/status.md
@@ -29,7 +29,7 @@ release artifacts are built as Jerboa multicall binaries with `jerboa`,
 | Native Rust exports | The native export review now has 190 exported functions: 183 tracked Scheme references and 7 retained standalone C/binary helpers. The previous 35 no-Scheme-reference removal candidates no longer have C ABI export markers. | Re-run `make native-export-review-check` whenever adding or removing native exports. |
 | Confined worker | `(std security worker)` provides the facade, audit lifecycle, output caps, deadlines, process-group kill, memory rlimit pre-exec setup, Linux syscall/ptrace seccomp pre-exec setup, Linux Landlock filesystem/TCP-connect setup for requested axes, macOS Seatbelt deny-default path/exec/no-network setup for supported axes, standard worker-eval Capsicum entry on FreeBSD, explicit sandbox-axis refusal, egress proxy env wiring, and platform CI smoke for Linux/macOS/FreeBSD sandbox paths. | Keep Linux/macOS/FreeBSD parity tests current; finish arbitrary-command/proxy-aware Capsicum worker paths. |
 | Fuzzing | `tests/fuzz/corpus/` has 15 checked-in seed inputs, `tests/fuzz/regression/` has 11 crash/rejection regressions, `make fuzz-smoke` runs the deterministic regression gate first, and GitHub CI runs smoke fuzzing normally plus deep fuzzing on scheduled daily runs. | Keep adding minimized corpus and regression inputs for every parser/security bug found. |
-| Safe surface | Direct scripts default to the safe prelude; raw access requires `--unsafe-prelude` or `(jerboa prelude unsafe)`. Core compatibility helpers avoid shell construction for recursive directory creation and process-status cleanup; the retired `(jerboa registry)` surface fails closed for mutable Git uninstall; `jpkg env -- COMMAND` is disabled until there is an argv exec/status API; terminal raw-mode restore validates `stty -g` state tokens; native-loader system candidates are literal fixed paths. | Continue moving risky APIs behind explicit unsafe imports as new modules land. |
+| Safe surface | Direct scripts default to the safe prelude; raw access requires `--unsafe-prelude` or `(jerboa prelude unsafe)`. Core compatibility helpers avoid shell construction for recursive directory creation and process-status cleanup; the retired `(jerboa registry)` surface fails closed for mutable Git uninstall; `jpkg env -- COMMAND` is disabled until there is an argv exec/status API; terminal raw-mode restore validates `stty -g` state tokens; native-loader system candidates are literal fixed paths; multicall cross-build `xpatch` loads are repo-local. | Continue moving risky APIs behind explicit unsafe imports as new modules land. |
 
 ## Compatibility Notes
 
diff --git a/support/build-jerboa-multicall.ss b/support/build-jerboa-multicall.ss
index 1cecd45..b7bbd86 100644
--- a/support/build-jerboa-multicall.ss
+++ b/support/build-jerboa-multicall.ss
@@ -478,11 +478,16 @@
 ;; emits target FASL. All produced by `make chez-cross`.
 (define target-machine (env "TARGET_MACHINE" #f))
 (define cross?       (and target-machine #t))
-(define xpatch       (and cross? (env "JERBOA_XPATCH" #f)))
+(define requested-xpatch (and cross? (env "JERBOA_XPATCH" #f)))
+(define xpatch       (and cross? (format "~a/build/chez/xc-~a/s/xpatch" repo target-machine)))
 (define cross-prefix (and cross? (env "JERBOA_CROSS_PREFIX" #f)))
 (when cross?
   (unless cross-prefix (error 'build-multicall "TARGET_MACHINE set but JERBOA_CROSS_PREFIX is not"))
-  (unless xpatch       (error 'build-multicall "TARGET_MACHINE set but JERBOA_XPATCH is not"))
+  (unless requested-xpatch (error 'build-multicall "TARGET_MACHINE set but JERBOA_XPATCH is not"))
+  (unless (string=? requested-xpatch xpatch)
+    (error 'build-multicall
+           "JERBOA_XPATCH must be the repo-local xpatch for TARGET_MACHINE"
+           requested-xpatch xpatch))
   (unless (file-exists? xpatch) (error 'build-multicall "xpatch not found" xpatch)))
 
 (define machine (if cross? target-machine (symbol->string (machine-type))))
@@ -1482,7 +1487,7 @@
 ;; are loaded as the host machine-type to collide with the retargeted codegen.
 (when cross?
   (printf "    loading xpatch ~a~n" xpatch)
-  (load xpatch)
+  (load xpatch) ; jerboa-security: suppress native-fasl-load-from-dynamic-path -- xpatch is derived from repo/build/chez/xc-${TARGET_MACHINE}/s/xpatch and any env value must match exactly
   (library-directories libdirs))
 (define program-so (format "~a/program.so" obj-dir))
 (define program-wpo (format "~a/program.wpo" obj-dir))
@@ -1505,7 +1510,7 @@
   (display "        (fasl-compressed old-fasl-compressed)))))\n" out)
   (fprintf out "(library-directories '~s)\n" helper-libdirs)
   (when xpatch
-    (fprintf out "(load ~s)\n" xpatch)
+    (fprintf out "(load ~s) ; jerboa-security: suppress native-fasl-load-from-dynamic-path -- generated helper receives the same repo-local xpatch validated by build-jerboa-multicall\n" xpatch) ; jerboa-security: suppress native-fasl-load-from-dynamic-path -- emitted load is constrained to the validated repo-local xpatch
     (fprintf out "(library-directories '~s)\n" helper-libdirs))
   (display "(with-deterministic-compiler\n" out)
   (display "  (lambda ()\n" out)