security: classify mcp scanner boundaries
ober
9a21fb59e2129511683ceacc546f7517be6fa085
--- 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. --- 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 --- 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 --- 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)])