security: hide lsp handler errors
Jaime Fournier
07c5c15ef864d94c7b5d529c6263742321ce7d77
--- a/docs/kimi3-security-recommmendations.md +++ b/docs/kimi3-security-recommmendations.md @@ -789,8 +789,10 @@ they are build-time. Close the runtime loop. eval-region, malformed request, and request-dispatch failures while logging internal detail server-side. `jmcp` now returns opaque references for unexpected tool-handler exceptions and framed/unframed JSON parse failures - while logging internal detail server-side. Remaining work: router integrations - plus LSP surfaces need opaque client error refs. + while logging internal detail server-side. `jlsp` now returns opaque + references for request-handler failures and logs detailed read/handler + failures server-side under the same reference. Remaining work: router + integrations need opaque client error refs. ### K3-P1-11 — Parameterized-only SQL in the safe surface **Serves:** G1. **Effort:** 2–3 days. --- a/docs/security-reference.md +++ b/docs/security-reference.md @@ -821,7 +821,7 @@ These modules are implemented but not covered in depth above. | Module | Purpose | |--------|---------| | `(std security sanitize)` | Context-aware sanitization: `sanitize-html`, `sanitize-html-attribute`, `sanitize-url-attribute`, `sql-escape`, `sanitize-path`, `safe-path-join`, `sanitize-header-value`, `sanitize-url`. Raises `&path-traversal`, `&header-injection`, `&url-scheme-violation`. | -| `(std security errors)` | Error classification (internal vs client-safe). Generates opaque error references for correlation. Prevents leaking internal details in error responses; `(std net grpc)`, `(std net thread-httpd)`, `(std repl server)`, and `jmcp` use it for protocol/handler failures. | +| `(std security errors)` | Error classification (internal vs client-safe). Generates opaque error references for correlation. Prevents leaking internal details in error responses; `(std net grpc)`, `(std net thread-httpd)`, `(std repl server)`, `jmcp`, and `jlsp` use it for protocol/handler failures. | | `(std security audit)` | Append-only JSONL audit log with SHA-256 hash chain. `audit-log!`, `verify-audit-chain`, `check-capability!/audit`. | | `(std security audit-log)` | Per-run structured audit records with JSONL/summary rendering, per-log redactors, and `audit-log-use-env-policy!` to scrub `(std security env)` registered secret values before emission. | | `(std security auth)` | API key stores, session tokens with expiry, auth middleware pattern, rate limiting for auth attempts. | --- a/lsp/server.ss +++ b/lsp/server.ss @@ -37,7 +37,8 @@ (lsp handlers configuration) (lsp handlers will-rename) (lsp handlers apply-edit) - (lsp validation)) + (lsp validation) + (std security errors)) ;; Handler registries (define *request-handlers* (make-hashtable string-hash string=?)) @@ -47,6 +48,26 @@ (define *output-port* #f) (define *output-mutex* (make-mutex)) + (define (lsp-error-detail exn) + (guard (render-exn [#t "unrenderable condition"]) + (if (message-condition? exn) + (condition-message exn) + "non-message condition"))) + + (define lsp-safe-error-handler + (make-safe-error-handler + (lambda (reference class-name exn) + (lsp-error "safe error ref=" reference + "class:" class-name + "detail:" (lsp-error-detail exn))))) + + (define (lsp-safe-error-message class-name exn) + (let ([safe (lsp-safe-error-handler class-name exn)]) + (string-append (safe-error-response-message safe) + " (Reference: " + (safe-error-response-reference safe) + ")"))) + (define (register-request-handler! method handler) (hashtable-set! *request-handlers* method handler)) @@ -177,7 +198,7 @@ (let loop () (let ([msg (guard (e [#t - (lsp-error "Read error:" (condition-message e)) + (lsp-error "Read error:" (lsp-safe-error-message 'bad-request e)) #f]) (read-lsp-message in-port))]) (cond @@ -209,10 +230,10 @@ (let ([handler (hashtable-ref *request-handlers* method #f)]) (if handler (guard (e [#t - (lsp-error "Handler error for" method ":" (condition-message e)) - (send-response! id - (make-jsonrpc-error id INTERNAL-ERROR - (string-append "Internal error: " (condition-message e))))]) + (let ([message (lsp-safe-error-message 'internal-error e)]) + (lsp-error "Handler error for" method ":" message) + (send-response! id + (make-jsonrpc-error id INTERNAL-ERROR message)))]) (let ([result (handler state params)]) (when (*validation-enabled*) (validate-response method result)) @@ -236,7 +257,7 @@ (when handler (guard (e [#t (lsp-error "Notification handler error for" method ":" - (condition-message e))]) + (lsp-safe-error-message 'internal-error e))]) (handler state params))))]) (loop)] --- a/lsp/test/unit-test.ss +++ b/lsp/test/unit-test.ss @@ -13,6 +13,8 @@ (lsp util position) (lsp util log) (lsp jsonrpc) + (lsp transport) + (lsp server) (lsp types) (lsp state) (lsp analysis document) @@ -358,6 +360,48 @@ (string=? (json-get notif "method") "textDocument/publishDiagnostics")) (check "notification has params" (hashtable? (json-get notif "params")))) +(test-section "LSP server: opaque handler errors") + +(define (lsp-frame obj) + (call-with-string-output-port + (lambda (port) + (write-lsp-message port obj)))) + +(define (run-lsp-messages messages) + (let* ([input (apply string-append (map lsp-frame messages))] + [output (call-with-string-output-port + (lambda (out) + (start-server (open-input-string input) out)))] + [in (open-input-string output)]) + (let* ([first (read-lsp-message in)] + [second (read-lsp-message in)]) + (list first second)))) + +(register-request-handler! + "jerboa/testFailure" + (lambda (state params) + (error 'jerboa/testFailure "lsp-secret-token-123"))) + +(let* ([responses + (run-lsp-messages + (list + (json-obj "jsonrpc" "2.0" + "id" 1001 + "method" "initialize" + "params" (json-obj "capabilities" (json-obj))) + (json-obj "jsonrpc" "2.0" + "id" 1002 + "method" "jerboa/testFailure" + "params" (json-obj))))] + [err (json-get (cadr responses) "error")] + [message (json-get err "message")]) + (check "handler error response is internal" + (= (json-get err "code") INTERNAL-ERROR)) + (check "handler error response has reference" + (and (string-contains message "Internal server error") + (string-contains message "Reference:"))) + (check "handler error response redacts condition text" + (not (string-contains message "lsp-secret-token-123")))) ;;; ── LSP Types ────────────────────────────────────────────────────── @@ -852,9 +896,8 @@ (check "find-jerboa-home returns something" (or (not jh) (string? jh))) (when jh - (check "jerboa-home ends with expected path" - (or (string-suffix? "/jerboa" jh) - (string-suffix? "/mine/jerboa" jh))))) + (check "jerboa-home has lib directory" + (file-exists? (string-append jh "/lib"))))) (test-section "Module resolution: resolve-module-path")