P3.1: replace shell-string exec + predictable /tmp paths

ober

489c6e4d6308e278d3d154ed8ec32dfccab8076e

diff --git a/src/jcode/core/sandbox.ss b/src/jcode/core/sandbox.ss
index 3adbce7..e84e7ec 100644
--- a/src/jcode/core/sandbox.ss
+++ b/src/jcode/core/sandbox.ss
@@ -22,6 +22,7 @@
 
 (import :std/misc/string
         :std/os/path
+        :std/os/aproc     ;; P3.1: argv-style spawn replaces (system "cmd > tmp 2> tmp")
         :jcode/core/config
         :jcode/core/log)
 
@@ -121,25 +122,10 @@
 (def (process-run-capture command)
   ;; Minimal shell capture so we don't take a dep on the bash tool.
   ;; Returns (values stdout stderr exit-code).
-  (let ((tmp-out (format "/tmp/jcode-uname-~a-out" (real-time)))
-        (tmp-err (format "/tmp/jcode-uname-~a-err" (real-time))))
-    (let ((rc (system (format "~a > ~a 2> ~a" command tmp-out tmp-err))))
-      (let ((out (try (call-with-input-file tmp-out
-                        (lambda (p)
-                          (let loop ((acc ""))
-                            (let ((line (get-line p)))
-                              (if (eof-object? line)
-                                acc
-                                (loop (string-append acc line "\n")))))))
-                      (catch (e) "")))
-            (err (try (call-with-input-file tmp-err
-                        (lambda (p)
-                          (let loop ((acc ""))
-                            (let ((line (get-line p)))
-                              (if (eof-object? line)
-                                acc
-                                (loop (string-append acc line "\n")))))))
-                      (catch (e) ""))))
-        (when (file-exists? tmp-out) (delete-file tmp-out))
-        (when (file-exists? tmp-err) (delete-file tmp-err))
-        (values out err rc)))))
+  ;;
+  ;; P3.1: was (system "~a > /tmp/jcode-uname-<real-time> 2> ..."), which
+  ;; (a) wrote to predictable, racy paths in /tmp (symlink attack window),
+  ;; (b) shelled out via (system) — fine for trusted callers, but the
+  ;; tmp-file pattern was the security-relevant part. aproc-run/status
+  ;; pipes stdout/stderr in memory, no temp files, no path race.
+  (aproc-run/status command))
diff --git a/src/jcode/core/secrets.ss b/src/jcode/core/secrets.ss
index 2d7d6a2..4d2f3b4 100644
--- a/src/jcode/core/secrets.ss
+++ b/src/jcode/core/secrets.ss
@@ -163,15 +163,21 @@
       (else
        (display prompt (current-error-port))
        (flush-output-port (current-error-port))
+       ;; P3.1: restore terminal echo even if get-line raises out of the
+       ;; guard (e.g. Ctrl-C in some shells). Without unwind-protect a
+       ;; thrown signal between stty -echo and stty echo leaves the user's
+       ;; terminal silently invisible.
        (system "stty -echo 2>/dev/null")
-       (let ((line (guard (e (#t #f))
-                     (get-line (current-input-port)))))
-         (system "stty echo 2>/dev/null")
-         (newline (current-error-port))
-         (cond
-           ((or (not line) (eof-object? line))
-            (error 'secret-prompt-passphrase "no passphrase provided"))
-           (else (string-trim line))))))))
+       ;; try/finally guarantees stty echo restoration; (jerboa core) already imported.
+       (try
+         (let ((line (guard (e (#t #f))
+                       (get-line (current-input-port)))))
+           (newline (current-error-port))
+           (cond
+             ((or (not line) (eof-object? line))
+              (error 'secret-prompt-passphrase "no passphrase provided"))
+             (else (string-trim line))))
+         (finally (system "stty echo 2>/dev/null")))))))
 
 ;;; ---- on-disk format ----