Update core hardening safety docs

ober

3c49c0920d98ba749e874866ee097bb04fdb43cc

diff --git a/docs/chez-hardening.md b/docs/chez-hardening.md
index 1a52643..1825558 100644
--- a/docs/chez-hardening.md
+++ b/docs/chez-hardening.md
@@ -95,10 +95,10 @@ status and which Chez subsystem they touch.
 | Stack canaries    | universal               | `-fstack-protector-strong` | None                           | In `--enable-harden`                  |
 | Stack-clash probe | universal               | `-fstack-clash-protection` | None                           | In `--enable-harden`                  |
 | FORTIFY_SOURCE    | universal (libc)        | `-D_FORTIFY_SOURCE=2`      | None                           | In `--enable-harden`                  |
-| Register clearing | universal               | `-fzero-call-used-regs=…`  | None                           | **Not in `--enable-harden`** (Phase 2)|
-| Auto-var-init     | universal               | `-ftrivial-auto-var-init=zero` | None                       | **Not in `--enable-harden`** (Phase 2)|
+| Register clearing | universal               | `-fzero-call-used-regs=…`  | None                           | In `--enable-harden` when compiler accepts it |
+| Auto-var-init     | universal               | `-ftrivial-auto-var-init=zero` | None                       | In `--enable-harden` when compiler accepts it |
 | Full RELRO + Now  | universal               | `-Wl,-z,relro,-z,now`      | None                           | In `--enable-harden`                  |
-| PIE + ASLR        | universal               | `-fPIE -pie`               | None                           | In `--enable-harden` (non-static)     |
+| PIE + ASLR        | universal               | `-fPIE -pie`               | None                           | In `--enable-harden` (`-fPIC`/`-pie` non-static, probed `-fPIE` static) |
 | MPK / PKU         | Intel Skylake+, Zen 3+  | runtime `pkey_*` syscalls  | JIT page management            | **Not started** (Phase 4)             |
 | LAM               | Intel Sapphire Rapids+  | runtime                    | sanitizer-style, niche         | Out of scope                          |
 
@@ -187,12 +187,13 @@ the canary/FORTIFY paths exercise.
 
 ---
 
-## 5. Phase 2 — Extra CFLAGS Not Yet in `--enable-harden`
+## 5. Phase 2 — Extra CFLAGS in `--enable-harden`
 
-**Effort:** ~1 day (mostly testing). **Risk:** low.
+**Status:** implemented in `vendor/ChezScheme/configure`. **Risk:** low.
 
-`--enable-harden` covers the standard mitigations but omits four
-worthwhile ones:
+`--enable-harden` covers the standard mitigations and now probes four
+additional flags. Unsupported flags are skipped rather than breaking older
+toolchains or cross compilers.
 
 ### 5.1 Register Clearing — `-fzero-call-used-regs=used-gpr`
 
@@ -220,38 +221,27 @@ of bugs that don't exist in audited code but are cheap to enforce.
 
 ### 5.4 Static-PIE Compatibility
 
-The current `--enable-harden` adds `-pie` only when not `--static`.
-The Jerboa Docker build uses `--static`, so libkernel ends up with no
-PIE flag — but the final Jerboa binary is `-static-pie` (per
-`secure.md` §"Static PIE"). For static-PIE to actually randomize
-libkernel's address, libkernel's object files need `-fPIE` (not
-`-fPIC`, but `-fPIE` is what static-PIE wants).
+`--enable-harden` adds `-pie` only when not `--static`. For static builds it now
+probes and adds `-fPIE` to libkernel object compilation without adding
+executable-only `-pie` to intermediate link flags. The final Jerboa binary link
+remains responsible for static-PIE.
 
-Add to `configure`: when `--static` AND `--enable-harden`, emit
-`-fPIE` without `-pie`. The `-pie` belongs on the final link, not on
-intermediate `.o` files.
+### 5.5 Configure Implementation
 
-### 5.5 Proposed `--enable-harden` Patch (Fork-Side)
-
-These flags should be added to `vendor/ChezScheme/configure` under
-the `if [ "$enableharden" = "yes" ]` block. This becomes commit N+1
-in the fork's "Security hardening" theme.
+`vendor/ChezScheme/configure` uses a small compile-only probe before appending
+each optional flag:
 
 ```sh
-CFLAGS="${CFLAGS} -fzero-call-used-regs=used-gpr"
-CFLAGS="${CFLAGS} -ftrivial-auto-var-init=zero"
-CFLAGS="${CFLAGS} -Wformat -Wformat-security -Werror=format-security"
+try_harden_cflags "-fzero-call-used-regs=used-gpr"
+try_harden_cflags "-ftrivial-auto-var-init=zero"
+try_harden_cflags "-Wformat -Wformat-security -Werror=format-security"
 if [ "$staticbuild" = "yes" ] ; then
-    CFLAGS="${CFLAGS} -fPIE"
+    try_harden_cflags "-fPIE"
 fi
 ```
 
-Compiler-version guards: `-fzero-call-used-regs` and
-`-ftrivial-auto-var-init` are GCC 11+/12+ respectively. The
-configure script should detect compiler version and skip on older
-toolchains rather than fail. (musl-cross-make ships GCC 11+ for all
-supported architectures, so this is non-issue for the production
-pipeline.)
+Compiler-version guards are therefore capability probes rather than brittle
+version parsing.
 
 ---
 
@@ -476,18 +466,15 @@ regressed (e.g., someone added a third `./configure` call without
 
 Recommended sequence to land:
 
-1. **Phase 1** (1 hour) — wire `--enable-harden` into the three call
-   sites. Unlocks every already-shipped mitigation. Lowest-risk,
-   highest-impact single change.
-2. **Phase 2.4** (1 hour) — fix `-fPIE` on `--static` builds so
-   libkernel actually participates in static-PIE ASLR.
-3. **Phase 2.1–2.3** (1 day) — extra CFLAGS as a fork-side patch
-   to `--enable-harden`.
-4. **Phase 3.3** (done for generated binaries) — runtime SHSTK enablement for
+1. **Phase 1** (done) — `--enable-harden` is wired into native, cross, Docker,
+   and musl helper build paths.
+2. **Phase 2.1–2.4** (done) — extra compiler-probed CFLAGS and static `-fPIE`
+   are folded into the vendored Chez configure path.
+3. **Phase 3.3** (done for generated binaries) — runtime SHSTK enablement for
    non-`call/cc` programs.
-5. **Phase 3.4** (weeks) — kernel continuation work to make
+4. **Phase 3.4** (weeks) — kernel continuation work to make
    SHSTK / PAC safe under `call/cc`.
-6. **Phase 4** — MTE, MPK, GCS as separate proposals.
+5. **Phase 4** — MTE, MPK, GCS as separate proposals.
 
 Phases 1–2 alone bring the libkernel hardening level up to par
 with the Jerboa shim, closing the gap `secure.md` describes as
diff --git a/docs/safety-guide.md b/docs/safety-guide.md
index 5600afa..06e94af 100644
--- a/docs/safety-guide.md
+++ b/docs/safety-guide.md
@@ -52,6 +52,7 @@ protections, runs your code, and returns the result to the parent.
 ;; - 30 second timeout
 ;; - compute-only seccomp filter (blocks network, filesystem writes)
 ;; - No Landlock rules (add your own for filesystem restriction)
+;; - No memory cap unless max-memory-size is configured
 (run-safe (lambda () (+ 1 2)))  ;; => 3
 
 ;; Evaluate a string in a restricted environment (113-binding allowlist):
@@ -65,6 +66,7 @@ protections, runs your code, and returns the result to the parent.
 (run-safe (lambda () (process-data input))
   (make-sandbox-config
     'timeout 10
+    'max-memory-size (* 256 1024 1024)
     'seccomp 'io-only))
 
 ;; Filesystem sandbox: read-only access to specific directories:
@@ -87,6 +89,8 @@ protections, runs your code, and returns the result to the parent.
 | `seccomp` | `'compute-only`, `'io-only`, `'network-server`, filter, `#f` | `'compute-only` | Syscall filter (irreversible) |
 | `landlock` | ruleset or `#f` | `#f` | Filesystem access restriction (irreversible) |
 | `capabilities` | list or `'()` | `'()` | Runtime capability tokens |
+| `max-output-size` | bytes | 1 MiB | Max child result bytes read from the pipe |
+| `max-memory-size` | bytes or `#f` | `#f` | Optional child address-space cap via `setrlimit` |
 
 ### Default Parameters
 
@@ -108,20 +112,21 @@ You can set global defaults instead of passing config every time:
 - **Syscall abuse**: Seccomp BPF filters are kernel-enforced. Even arbitrary
   code execution inside the sandbox cannot bypass them.
 - **Infinite loops**: Engine-based timeout preempts runaway Scheme code.
+- **Memory exhaustion**: `max-memory-size` installs a child-process memory cap
+  before the untrusted thunk runs.
 - **Parent compromise**: All protections are applied in a forked child. The
   parent process remains unrestricted.
 
 ### What It Does NOT Protect Against
 
-- **Memory exhaustion**: A `(make-bytevector 1000000000)` inside the sandbox
-  can OOM the child before the timeout fires. There is no `setrlimit` yet.
-  For DoS protection, run sandboxed code in a resource-limited container.
+- **Default memory exhaustion**: `max-memory-size` is opt-in because platforms
+  disagree on address-space limits. Set it explicitly for untrusted work, and
+  use cgroups or containers when you need process-tree limits.
 - **Blocked FFI calls**: The engine timeout preempts Scheme code but cannot
   interrupt a blocking C/Rust FFI call. Use socket-level timeouts
   (`SO_RCVTIMEO`) for network operations.
-- **Temp file race**: The child communicates results via `/tmp/jerboa-sandbox-*`
-  with a random suffix. A local attacker with `/tmp` access could potentially
-  interfere. This is a known issue.
+- **Stdout/stderr capture**: `max-output-size` caps the result channel read
+  from the child pipe. It is not a general stdout/stderr capture mechanism.
 
 ---
 
@@ -711,12 +716,13 @@ to catch common issues before they become bugs.
 
 Be honest about what Jerboa does NOT protect against:
 
-### No Memory Limits in Sandbox
+### Memory Limits Are Opt-In
 
-`run-safe` does not set `setrlimit(RLIMIT_AS)` in the child process. A
-malicious thunk can exhaust memory before the timeout fires. If you run
-untrusted code, use OS-level memory limits (cgroups, containers) as a
-complementary measure.
+`run-safe` supports `max-memory-size`, which installs `setrlimit(RLIMIT_AS)` in
+the child process. The default remains `#f` because Chez heap layout and kernel
+limit semantics vary by platform. If you run untrusted code, set
+`max-memory-size` and use OS-level memory limits (cgroups, containers) when you
+need process-tree enforcement.
 
 ### Kernel Protections Are Linux x86_64 Only
 
@@ -725,11 +731,11 @@ numbers. On other platforms (macOS, ARM Linux, BSDs), these protections
 silently degrade — the code runs without kernel sandboxing. The Scheme-level
 allowlist and timeout still apply.
 
-### Temp File Race in `run-safe`
+### `run-safe` Result Channel
 
-`run-safe` communicates results via `/tmp/jerboa-sandbox-<random>`. A local
-attacker with `/tmp` access could create symlinks to redirect the result
-file. Current implementations should avoid this path for adversarial local multi-user contexts until it uses `mkstemp` or `O_TMPFILE`.
+`run-safe` communicates results through `pipe(2)`, not a predictable temp file.
+The old `/tmp/jerboa-sandbox-*` symlink/TOCTOU concern is no longer part of the
+current implementation.
 
 ### Engine Timeout Cannot Interrupt FFI
 
@@ -769,11 +775,12 @@ produce a "multiple definitions" warning. This is cosmetic but means symbol
 resolution order could theoretically surprise you in edge cases. In practice,
 the safe version always wins because it is bound last.
 
-### Build Reproducibility Is Partial
+### Reproducibility Evidence Is Local
 
-`(std build reproducible)` provides content-addressed artifacts and SHA-256
-hashing, but bit-identical reproducible builds end-to-end are still being verified. The SBOM detects Scheme, C, and Rust dependencies but full build
-orchestration is not complete.
+`make reproducibility-report` performs a local two-build comparison of the
+multicall binary, object cache, bundle, links, and source manifest, and exits
+nonzero on mismatch. Cross-machine reproducibility still requires comparing
+those reports from independent builders.
 
 ---