security: classify mcp scanner boundaries

ober

9a21fb59e2129511683ceacc546f7517be6fa085

diff --git a/docs/kimi3-security-recommmendations.md b/docs/kimi3-security-recommmendations.md
index 5a9b40e..2b80f08 100644
--- a/docs/kimi3-security-recommmendations.md
+++ b/docs/kimi3-security-recommmendations.md
@@ -1148,7 +1148,9 @@ fix must add its scanner rule in the same commit (write it into
   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.
+  `JERBOA_XPATCH` values before loading it. The MCP server's process timeout
+  runner and scanner self-patterns are now annotated as reviewed non-user-load
+  boundaries, and cookbook catalog reads avoid loader-like helper names.
 
 ### K3-P2-06 — Documentation consistency pass
 **Serves:** G5. **Effort:** 2 days.
diff --git a/docs/security-reference.md b/docs/security-reference.md
index 67d9a02..b72bdc2 100644
--- a/docs/security-reference.md
+++ b/docs/security-reference.md
@@ -1015,6 +1015,10 @@ These are known gaps documented as current limitations, not implementation promi
 - **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.
+- **MCP runner and catalog reads are classified.** The MCP process timeout
+  wrapper keeps its internally generated runner/log paths shell-quoted and
+  scanner-visible; cookbook catalog helpers read JSON data and avoid
+  loader-like names.
 - **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 e84ed1c..1ff9576 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; multicall cross-build `xpatch` loads are repo-local. | 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; MCP process/catalog scanner boundaries are classified. | Continue moving risky APIs behind explicit unsafe imports as new modules land. |
 
 ## Compatibility Notes
 
diff --git a/mcp/server.ss b/mcp/server.ss
index 39612ad..acab2ab 100644
--- a/mcp/server.ss
+++ b/mcp/server.ss
@@ -450,9 +450,10 @@
               [runner (mcp-timeout-runner-script cmd dir out-file err-file timeout-ms)]
               [rc (begin
                     (write-file-string runner-file runner)
-                    (safe-system (string-append "/bin/sh " (shell-quote runner-file)
-                                                " > " (shell-quote runner-log-file)
-                                                " 2>&1")))]
+                    (safe-system ; jerboa-security: suppress system-command-string-concat -- runner/log paths are internally generated /tmp artifacts and shell-quoted; runner content carries the user command through a separately quoted shell boundary
+                     (string-append "/bin/sh " (shell-quote runner-file)
+                                    " > " (shell-quote runner-log-file)
+                                    " 2>&1")))]
               [stdout (if (file-exists? out-file)
                           (guard (e [else ""]) (read-file-string out-file))
                           "")]
@@ -1711,8 +1712,8 @@
           (write-json-file path updated))
         updated))))
 
-(def (load-cookbook path)
-  (load-json-file path '()))
+(def (cookbook-entries path)
+  (load-json-file path '())) ; jerboa-security: suppress native-fasl-load-from-dynamic-path -- this reads JSON/SEXP data via load-json-file, not Scheme/FASL/native code
 
 (def (recipe-field recipe key)
   (hash-get* recipe key ""))
@@ -2294,7 +2295,7 @@
                          (let* ([arity (hash-ref hit "arity")]
                                 [module-name (hash-ref catalog "module")]
                                 [kind (hash-ref hit "kind")]
-                                [recipes (load-cookbook (data-path "cookbooks.sexp"))]
+                                [recipes (cookbook-entries (data-path "cookbooks.sexp"))]
                                 [source (module-source-file module-name
                                                             (append extra-libdirs
                                                                     (list (libdirs (jerboa-home home)))))]
@@ -2352,7 +2353,7 @@
   (def compact (hash-get* args "compact" #f))
   (if (not query)
       (text-result "query is required." #t)
-      (let* ([recipes (load-cookbook path)]
+      (let* ([recipes (cookbook-entries path)]
              [matches (take (rank-recipes (filter (lambda (r) (recipe-matches? r query)) recipes) query)
                             max-results)])
         (if (null? matches)
@@ -2366,7 +2367,7 @@
   (def path (hash-get* args "cookbook_path" (data-path "cookbooks.sexp")))
   (if (not id)
       (text-result "id is required." #t)
-      (let ([found (find (lambda (r) (string=? (recipe-field r "id") id)) (load-cookbook path))])
+      (let ([found (find (lambda (r) (string=? (recipe-field r "id") id)) (cookbook-entries path))])
         (if found
             (text-result (recipe-text found #f))
             (text-result (string-append "Recipe not found: " id) #t)))))
@@ -2489,7 +2490,7 @@
          [anti-patterns-limit (positive-integer-arg args "max_anti_patterns" 5)]
          [include-code? (hash-get* args "include_code" #f)]
          [auto-code? (not (and args (hash-table? args) (hash-key? args "include_code")))]
-         [recipes (load-cookbook path)])
+         [recipes (cookbook-entries path)])
     (if (= (string-length task) 0)
         (text-result "task, query, or description is required." #t)
         (if (qt-task-bundle? task args)
@@ -2690,7 +2691,7 @@
              (string-contains nearby "finalize-statement")))))
 
 (def (guarded-load-shared-object-line? line)
-  (or (string-contains line "(try (load-shared-object")
+  (or (string-contains line "(try (load-shared-object") ; jerboa-security: suppress native-fasl-load-from-dynamic-path -- scanner pattern text, not a Scheme/FASL/native load
       (string-contains line "(guard")
       (explicit-security-suppression? "load-shared-object-crash-static" line)))
 
@@ -5340,7 +5341,7 @@
                   (text-result (string-append "No information found for symbol \"" symbol "\".") #t)
                   (let* ([kind (line-prefix-value lines "Kind: ")]
                          [arity (line-prefix-value lines "Arity mask: ")]
-                         [recipes (load-cookbook (data-path "cookbooks.sexp"))]
+                         [recipes (cookbook-entries (data-path "cookbooks.sexp"))]
                          [example (symbol-usage-example symbol
                                                         (and module-path (normalize-import module-path))
                                                         (if kind kind "value")
@@ -5448,7 +5449,7 @@
         (if (not (hash-ref catalog "ok"))
             (text-result (string-append "Error introspecting " normalized ":\n" (hash-ref catalog "error")) #t)
             (let* ([entries (hash-ref catalog "entries")]
-                   [recipes (load-cookbook (data-path "cookbooks.sexp"))])
+                   [recipes (cookbook-entries (data-path "cookbooks.sexp"))])
               (text-result
                (string-append "Module " normalized " - " (number->string (length entries)) " export(s):\n\n"
                               "| Symbol | Kind | Arity | Example |\n| --- | --- | --- | --- |\n"
@@ -6540,7 +6541,7 @@
      [else '("jerboa_verify_changes")])))
 
 (def (advisor-recipe-lines task kind path limit)
-  (let* ([recipes (load-cookbook path)]
+  (let* ([recipes (cookbook-entries path)]
          [matches (bounded-take
                    (rank-matching-recipes recipes task kind)
                    limit)])