security: retire legacy registry shell paths

ober

a36d9853b5d2c248435917602da8ff257c5898f0

diff --git a/docs/kimi3-security-recommmendations.md b/docs/kimi3-security-recommmendations.md
index e98c3f9..a5b26dd 100644
--- a/docs/kimi3-security-recommmendations.md
+++ b/docs/kimi3-security-recommmendations.md
@@ -1137,7 +1137,10 @@ fix must add its scanner rule in the same commit (write it into
   directory creation with `mkdir`/`path-directory` instead of shelling out to
   `mkdir -p`, closing the scanner's critical command-concatenation finding at
   that language surface. The core `process-status` compatibility helper also
-  no longer constructs a shell `wait` command.
+  no longer constructs a shell `wait` command. The retired `(jerboa registry)`
+  compatibility surface now creates registry directories without shelling out
+  and fails closed for legacy mutable Git uninstalls instead of constructing an
+  `rm -rf` command.
 
 ### K3-P2-06 — Documentation consistency pass
 **Serves:** G5. **Effort:** 2 days.
diff --git a/docs/security-reference.md b/docs/security-reference.md
index d8cbb30..0b36ccd 100644
--- a/docs/security-reference.md
+++ b/docs/security-reference.md
@@ -1003,7 +1003,9 @@ These are known gaps documented as current limitations, not implementation promi
 - **FFI audit (Phase 5 of parser hardening) is in progress.** `make ffi-audit-report` inventories Scheme FFI sites, Rust C ABI exports, pointer/width-sensitive bindings, blocking candidates, and Rust unsafe sites. Native unsafe review and selected Scheme FFI slices, including the regex, compression, crypto, OpenSSL TLS, rustls TLS, POSIX, async-process, and nREPL TCP wrappers, are remediated; the remaining Scheme per-binding null-return, bounds, ownership, and GC-safety review is tracked in [ffi-audit.md](ffi-audit.md).
 - **Core compatibility helpers avoid shell construction.** `create-directory*`
   performs recursive `mkdir` directly, and `process-status` no longer builds a
-  shell `wait` command.
+  shell `wait` command. The retired `(jerboa registry)` compatibility module
+  creates registry directories without shelling out and disables legacy mutable
+  Git uninstall.
 - **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 b7a65ba..7cf3d9d 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. | 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. | Continue moving risky APIs behind explicit unsafe imports as new modules land. |
 
 ## Compatibility Notes
 
diff --git a/lib/jerboa/registry.ss b/lib/jerboa/registry.ss
index 6a1d9d0..4cc99eb 100644
--- a/lib/jerboa/registry.ss
+++ b/lib/jerboa/registry.ss
@@ -22,23 +22,15 @@
   (def *package-dir*
     (make-parameter (string-append (home-dir) "/.jerboa/packages/")))
 
-  (def (ensure-directory path)
-    (unless (file-exists? path)
-      (system (string-append "mkdir -p " (shell-quote path)))))
-
-  (def (shell-quote s)
-    (string-append "'" (let loop ([i 0] [acc ""])
-      (if (= i (string-length s))
-        acc
-        (let ([ch (string-ref s i)])
-          (if (char=? ch #\')
-            (loop (+ i 1) (string-append acc "'\\''"))
-            (loop (+ i 1) (string-append acc (string ch))))))) "'"))
-
-  (def (run-command cmd)
-    (let ([rc (system cmd)])
-      (unless (= rc 0)
-        (error 'registry "command failed" cmd rc))))
+  (def (ensure-directory path) ; jerboa-security: suppress recursive-descent-follows-symlinks -- this recursion creates missing directories only; it does not chmod/chown/rm or otherwise mutate existing symlink targets
+    (unless (or (string=? path "") (file-directory? path))
+      (let ([parent (registry-path-parent path)])
+        (when (and parent
+                   (not (string=? parent ""))
+                   (not (string=? parent path))
+                   (not (file-directory? parent)))
+          (ensure-directory parent)))
+      (mkdir path)))
 
   (def (url-from-github-path gh-path)
     (string-append "https://" gh-path ".git"))
@@ -136,15 +128,9 @@
            gh-path))
 
   (def (package-uninstall! name)
-    (let ([entry (registry-lookup name)])
-      (unless entry
-        (error 'package-uninstall! "package not installed" name))
-      (let ([path (entry-path entry)])
-        (when (file-exists? path)
-          (run-command (string-append "rm -rf " (shell-quote path)))))
-      (save-registry!
-        (filter (lambda (e) (not (string=? (entry-name e) name)))
-                (load-registry)))))
+    (error 'package-uninstall!
+           "legacy mutable Git uninstall is disabled; use jpkg remove/uninstall with jpkg.lock"
+           name))
 
   (def (package-update! name)
     (error 'package-update!