security: redact mcp log secrets
Jaime Fournier <jaimef@linbsd.org>
f12715f0d5a314ca395e500f508fb0d7e070e2da
diff --git a/docs/kimi3-security-recommmendations.md b/docs/kimi3-security-recommmendations.md
index edadff8..28d6456 100644
--- a/docs/kimi3-security-recommmendations.md
+++ b/docs/kimi3-security-recommmendations.md
@@ -958,6 +958,12 @@ Standardize on `(std security secret)` (auto-wipe lifetimes) +
command-line argv (`ps`-visible — Philosophy rejects this), secrets in
error messages/logs. Scanner rules from P2-05 enforce going forward.
+- **Status:** started for repository-local MCP logging. `mcp/server.ss` now
+ centrally redacts sensitive MCP request fields, env overrides, free-form
+ command strings with secret markers, and slow-process log entries before
+ writing `JERBOA_MCP_LOG_FILE`. A runtime smoke verified that a fake token in
+ `jerboa_eval` env/command plumbing reaches the log only as `<redacted>`.
+
### K3-P2-08 — SHSTK default decision for daemon binaries
**Serves:** G1. **Effort:** 2 days.
diff --git a/mcp/server.ss b/mcp/server.ss
index 33c428f..39612ad 100644
--- a/mcp/server.ss
+++ b/mcp/server.ss
@@ -211,28 +211,82 @@
ch))
(string->list s))))
+(def jmcp-sensitive-key-fragments
+ '("token" "secret" "password" "passwd" "api-key" "api_key"
+ "access-token" "access_token" "refresh-token" "refresh_token"
+ "authorization" "bearer" "credential" "private-key" "private_key"
+ "webhook-secret" "webhook_secret" "totp"))
+
+(def (jmcp-key->string key)
+ (cond
+ [(symbol? key) (symbol->string key)]
+ [(string? key) key]
+ [else (pp-to-string key)]))
+
+(def (jmcp-sensitive-key? key)
+ (let ([name (string-downcase (jmcp-key->string key))])
+ (any (lambda (frag) (string-contains name frag))
+ jmcp-sensitive-key-fragments)))
+
+(def (jmcp-secret-looking-string? s)
+ (let ([lower (string-downcase s)])
+ (or (string-contains lower "authorization")
+ (string-contains lower "bearer ")
+ (string-contains lower "--token") ; jerboa-security: suppress secret-via-argv-flag -- redaction marker, not argv parsing
+ (string-contains lower "--access-token")
+ (string-contains lower "--refresh-token")
+ (string-contains lower "--secret")
+ (string-contains lower "--password")
+ (string-contains lower "--passwd")
+ (string-contains lower "--api-key")
+ (string-contains lower "api_key=")
+ (string-contains lower "api-key=")
+ (string-contains lower "token=")
+ (string-contains lower "secret=")
+ (string-contains lower "password=")
+ (string-contains lower "passwd=")
+ (string-contains lower "private_key=")
+ (string-contains lower "private-key=")
+ (string-contains lower "webhook_secret=")
+ (string-contains lower "webhook-secret="))))
+
+(def (jmcp-redact-log-string s)
+ (if (and (string? s) (jmcp-secret-looking-string? s))
+ "<redacted>"
+ s))
+
+(def (jmcp-log-normalize-field key value depth)
+ (if (jmcp-sensitive-key? key)
+ "<redacted>"
+ (jmcp-log-normalize value depth)))
+
(def (jmcp-log-normalize v depth)
(cond
[(<= depth 0) '...]
[(hash-table? v)
(map (lambda (kv)
- (cons (car kv) (jmcp-log-normalize (cdr kv) (- depth 1))))
+ (cons (car kv)
+ (jmcp-log-normalize-field (car kv) (cdr kv) (- depth 1))))
(take-n (hash->list v) 40))]
[(list? v)
(map (lambda (x) (jmcp-log-normalize x (- depth 1)))
(take-n v 40))]
[(pair? v)
- (cons (jmcp-log-normalize (car v) (- depth 1))
- (jmcp-log-normalize (cdr v) (- depth 1)))]
+ (if (jmcp-sensitive-key? (car v))
+ (cons (jmcp-log-normalize (car v) (- depth 1)) "<redacted>")
+ (cons (jmcp-log-normalize (car v) (- depth 1))
+ (jmcp-log-normalize (cdr v) (- depth 1))))]
[(vector? v)
(list 'vector
(vector-length v)
(map (lambda (x) (jmcp-log-normalize x (- depth 1)))
(take-n (vector->list v) 40)))]
+ [(string? v) (jmcp-redact-log-string v)]
[else v]))
(def (jmcp-log-value v limit)
- (shorten (jmcp-clean-string (pp-to-string (jmcp-log-normalize v 4))) limit))
+ (jmcp-redact-log-string
+ (shorten (jmcp-clean-string (pp-to-string (jmcp-log-normalize v 4))) limit)))
(def (jmcp-file-length path)
(if (file-exists? path)
@@ -357,7 +411,8 @@
" printf 'ts_sec=%s event=process.slow elapsed_ms=" slow-ms
" slow_ms=" slow-ms " timeout_ms=" (number->string timeout-ms)
" pid=%s cmd=%s\\n' \"$(date +%s)\" \"$child\" "
- (shell-quote cmd) " >> " (shell-quote log-file) " 2>/dev/null || true\n"
+ (shell-quote (jmcp-redact-log-string cmd))
+ " >> " (shell-quote log-file) " 2>/dev/null || true\n"
" fi\n"
") &\n"
"slow_watcher=$!\n")