security: clean retired sandbox scan

Jaime Fournier <jaimef@linbsd.org>

ddfb5047910c99c7bece37751d24b7bbd9b2c5db

diff --git a/docs/ffi-audit.md b/docs/ffi-audit.md
index 061f5f5..c7af50a 100644
--- a/docs/ffi-audit.md
+++ b/docs/ffi-audit.md
@@ -170,7 +170,11 @@ inputs and output-buffer validation. The Scheme `(std security seatbelt)`
 binding now resolves `sandbox_init`/`sandbox_free_error` only after the sandbox
 library is loaded and copies/frees the `sandbox_init` error buffer before
 raising a Scheme condition; the legacy `(std os sandbox)` Seatbelt path now
-uses the same copy/free pattern before printing degraded diagnostics.
+uses the same copy/free pattern before printing degraded diagnostics. The
+retired `(std os sandbox)` callback surface also removes its dead shell-command
+thunk, refuses bare Capsicum entry without fd-rights, and carries local
+scanner suppressions for platform metadata, rights literals, and checked FFI
+slots so its focused `jerboa_security_scan` result is clean.
 `secure_fs.rs` now documents
 descriptor-relative `openat`/`mkdirat`, fd
 duplication/ownership transfer, stat buffers, directory stream lifecycle,
diff --git a/docs/kimi3-security-recommmendations.md b/docs/kimi3-security-recommmendations.md
index 26be59e..59c6d95 100644
--- a/docs/kimi3-security-recommmendations.md
+++ b/docs/kimi3-security-recommmendations.md
@@ -714,7 +714,11 @@ the current inventory and remediation trail now live in [ffi-audit.md](ffi-audit
   copies/frees the `sandbox_init` error buffer before raising a Scheme
   condition, with an invalid-profile regression in `tests/test-seatbelt.ss`.
   The legacy `(std os sandbox)` Seatbelt path now uses the same copy/free
-  pattern before printing degraded diagnostics.
+  pattern before printing degraded diagnostics. The retired `(std os sandbox)`
+  callback surface also removes its dead shell-command thunk, refuses bare
+  Capsicum entry without fd-rights, and scans cleanly after local suppressions
+  for non-secret platform metadata, Capsicum rights literals, and checked FFI
+  slots.
   `wasm_sm.rs` now compiles under `unsafe_op_in_unsafe_fn` for the
   SpiderMonkey feature, bounds C ABI buffers, validates host-memory offsets,
   converts poisoned handle-store locks into normal FFI errors, and documents
diff --git a/docs/status.md b/docs/status.md
index 2d36496..763589c 100644
--- a/docs/status.md
+++ b/docs/status.md
@@ -25,7 +25,7 @@ release artifacts are built as Jerboa multicall binaries with `jerboa`,
 | Area | Current state | Remaining work |
 |---|---|---|
 | Kimi security handoff | [kimi3-security-recommmendations.md](kimi3-security-recommmendations.md) is the backlog. Dated evidence and review manifests live under [reviews/](reviews/). | Keep new security evidence in dated review records and summarize the current release state here. |
-| FFI audit phase 5 | [ffi-audit.md](ffi-audit.md) records the scanner output, provisional Scheme binding verdicts, and native Rust export inventory. `make native-export-review-check` gates native export decisions. The worker-launch native path in `aproc.rs`/`seccomp.rs`, crypto FFI buffer path in `crypto.rs`, secure-memory region lifecycle in `secure_mem.rs`, secure filesystem capability boundary in `secure_fs.rs`, TLS native ABI in `tls.rs`, Ed25519/X25519 key-agreement buffers, compression buffers, HTTP parse/writev boundary, embed-crypto ABI, integrity ABI, regex-native ABI, process-control ABI, pcap ABI, inotify ABI, epoll/eventfd ABI, antidebug ABI, SOCKS5 server ABI, PostgreSQL native ABI, Landlock native ABI, wasmi sandbox ABI, SpiderMonkey WASM ABI, DuckDB native ABI, X.509 native ABI, SQLite native ABI, and the Scheme Seatbelt `sandbox_init` error-buffer paths now have nearby `SAFETY:` comments or equivalent checked FFI invariants. Generated counts are at 0 unannotated native unsafe sites and 0 Scheme blocking bindings missing `__collect_safe`. | Finish per-binding Scheme FFI review and targeted scanner rules for the remaining provisional verdicts. |
+| FFI audit phase 5 | [ffi-audit.md](ffi-audit.md) records the scanner output, provisional Scheme binding verdicts, and native Rust export inventory. `make native-export-review-check` gates native export decisions. The worker-launch native path in `aproc.rs`/`seccomp.rs`, crypto FFI buffer path in `crypto.rs`, secure-memory region lifecycle in `secure_mem.rs`, secure filesystem capability boundary in `secure_fs.rs`, TLS native ABI in `tls.rs`, Ed25519/X25519 key-agreement buffers, compression buffers, HTTP parse/writev boundary, embed-crypto ABI, integrity ABI, regex-native ABI, process-control ABI, pcap ABI, inotify ABI, epoll/eventfd ABI, antidebug ABI, SOCKS5 server ABI, PostgreSQL native ABI, Landlock native ABI, wasmi sandbox ABI, SpiderMonkey WASM ABI, DuckDB native ABI, X.509 native ABI, SQLite native ABI, the Scheme Seatbelt `sandbox_init` error-buffer paths, and the retired `(std os sandbox)` compatibility surface now have nearby `SAFETY:` comments or equivalent checked FFI/scanner invariants. Generated counts are at 0 unannotated native unsafe sites and 0 Scheme blocking bindings missing `__collect_safe`. | Finish per-binding Scheme FFI review and targeted scanner rules for the remaining provisional verdicts. |
 | 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. |
diff --git a/lib/std/os/sandbox.ss b/lib/std/os/sandbox.ss
index 472f7a8..00ea1b8 100644
--- a/lib/std/os/sandbox.ss
+++ b/lib/std/os/sandbox.ss
@@ -24,7 +24,7 @@
 
   ;; ========== Platform Detection ==========
 
-  (def (detect-platform)
+  (def (detect-platform) ; jerboa-security: suppress non-constant-time-secret-compare -- machine-type strings are public platform metadata
     (let ([mt (symbol->string (machine-type))])
       (cond
         [(string-contains mt "osx") 'macos]
@@ -39,7 +39,7 @@
       (let lp ([i 0])
         (cond
           [(> (+ i sublen) slen) #f]
-          [(string=? (substring str i (+ i sublen)) sub) #t]
+          [(string=? (substring str i (+ i sublen)) sub) #t] ; jerboa-security: suppress non-constant-time-secret-compare -- platform machine-type probing is public metadata, not a token/MAC/secret
           [else (lp (+ i 1))]))))
 
   (def *platform* (detect-platform))
@@ -51,7 +51,7 @@
 
   ;; No raw fork primitive is retained: all callback-based entry points below
   ;; are gated by sandbox-fork and fail before reaching a child branch.
-  (def (c-fork)
+  (def (c-fork) ; jerboa-security: suppress fork-without-child-limit -- this is a failing compatibility stub, not a foreign fork binding
     (error 'sandbox-fork
            "raw-fork Scheme callbacks are retired; use an exec-based worker"))
   ;; waitpid is __collect_safe so the parent releases Chez's TC mutex
@@ -59,7 +59,7 @@
   ;; freezes every other green thread (TUI, watchdogs, streaming loops)
   ;; for the lifetime of the sandboxed child.
   (def c-waitpid
-    (foreign-procedure __collect_safe "waitpid" (int void* int) int))
+    (foreign-procedure __collect_safe "waitpid" (int void* int) int)) ; jerboa-security: suppress ffi-pointer-return-without-null-guard -- waitpid returns int and the void* argument may be NULL by POSIX convention
   (def c-exit (foreign-procedure __atomic "_exit" (int) void))
 
   (def (sandbox-fork who)
@@ -146,7 +146,7 @@
     ;; Apply a Seatbelt profile. profile-spec is a symbol or SBPL string.
     ;; Always uses SBPL (flags = 0) since the legacy named-profile path
     ;; is broken on modern macOS.
-    (let ([errptr (foreign-alloc 8)])
+    (let ([errptr (foreign-alloc 8)]) ; jerboa-security: suppress u8star-ffi-with-foreign-alloc -- errptr is passed to sandbox_init as void**, not u8*
       (foreign-set! 'void* errptr 0 0)
       (dynamic-wind
         (lambda () (void))
@@ -222,7 +222,7 @@
           (loop (cdr rest)
                 (string-append acc (string #\x1) (car rest)))))))
 
-  (def (landlock-enforce! read-paths write-paths exec-paths)
+  (def (landlock-enforce! read-paths write-paths exec-paths) ; jerboa-security: suppress landlock-enforce-irreversible -- compatibility helper is only behind the retired callback path; exec workers own live Landlock enforcement
     (let ((packed-read  (pack-paths read-paths))
           (packed-write (pack-paths write-paths))
           (packed-exec  (pack-paths exec-paths)))
@@ -309,10 +309,11 @@
          ;; === PARENT PROCESS ===
          (wait-for-child pid)))))
 
-  ;; Convenience: run a shell command string in a sandbox.
+  ;; Retired command callback surface. Use (std security worker) for exec-based
+  ;; confinement instead of carrying a dead shell-command thunk.
   (def (sandbox-run/command read-paths write-paths exec-paths cmd)
-    (sandbox-run read-paths write-paths exec-paths
-      (lambda () (system cmd))))
+    (error 'sandbox-run/command
+           "callback sandbox command execution is retired; use an exec-based worker"))
 
   ;; macOS-specific: run a thunk under a Seatbelt profile.
   ;; profile-spec: symbol ('pure-computation, 'no-write, etc.) or SBPL string.
@@ -347,8 +348,8 @@
              (if (and (pair? maybe-fd-rights) (pair? (car maybe-fd-rights)))
                ;; Apply preset with fd restrictions
                (capsicum-apply-preset! (car maybe-fd-rights))
-               ;; Bare cap_enter
-               (capsicum-enter!)))
+               (error 'sandbox-run/capsicum
+                      "capsicum fd rights must be supplied before capability mode")))
          (catch (e) (display "sandbox: " (current-error-port))
                    (display-condition e (current-error-port))
                    (newline (current-error-port))
@@ -457,7 +458,7 @@
       (for-each
         (lambda (p)
           (try (begin ;; skip paths that can't be opened
-            (let ([fd (capsicum-open-path p '(read fstat seek lookup))])
+            (let ([fd (capsicum-open-path p '(read fstat seek lookup))]) ; jerboa-security: suppress bare-read-untrusted -- quoted Capsicum rights data, not a reader call
               (set! opened-fds (cons fd opened-fds))))
          (catch (e) (void))))
         (if (list? read-paths) read-paths '()))
@@ -483,7 +484,7 @@
       (try (capsicum-limit-fd! 2 '(write fstat))
          (catch (e) (void)))
       ;; Enter capability mode
-      (capsicum-enter!)
+      (capsicum-enter!) ; jerboa-security: suppress capsicum-enter-without-fd-restrict -- helper pre-opens requested paths and restricts stdio fds immediately above before entering capability mode
       (reverse opened-fds)))
 
   ;; ========== Internal ==========