Repository navigation
fix(mcp): pin stateless session IDs and stop logging session IDs in transport errors - #835
Conversation
Embed an analyze_sql report widget (ui://gosqlx/analysis-v1.html), return structuredContent from all tools with unchanged text output, add server instructions, noauth securitySchemes and invocation status metadata, accept mariadb/clickhouse in validate_sql, report gosqlx.Version and the real tool count, serve the optional OpenAI apps domain challenge, and sweep idle MCP sessions after 30 minutes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L2wnDjnjXs2u8baaf2PC6w
Install with /plugin marketplace add ajitpratap0/GoSQLX and /plugin install gosqlx@gosqlx: hosted MCP server, five SQL skills, four slash commands, a read-only sql-reviewer agent and an opt-out .sql validation hook. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L2wnDjnjXs2u8baaf2PC6w
Add gosqlx.dev/terms, describe hosted MCP server, ChatGPT app and Claude plugin data handling accurately in the privacy policy, document both integrations in README, MCP guide and CHANGELOG, and fix the Claude Desktop stdio example. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L2wnDjnjXs2u8baaf2PC6w
Name the maintainer as controller and contracting party, add a private contact and grievance officer, legal bases, recipients (incl. Vercel), international transfers, retention, security, user rights, children, DPDP Act 2023 and GDPR references; terms gain eligibility, liability cap, consumer carve-out, indemnity, termination, Indian governing law, boilerplate and conspicuous disclaimers. Correct the VS Code telemetry opt-in text and replace its dead PRIVACY.md link. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L2wnDjnjXs2u8baaf2PC6w
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L2wnDjnjXs2u8baaf2PC6w
…t_sql - widget: cap long lists (counted statement types, +N more), show failed tool calls as errors on both host bridges, skip clipboard API when the frame's permissions policy blocks it - server: one streamable transport per Server reused by Handler/Start, new Server.Shutdown, Start gains read-header/idle timeouts, transport logger drops Info so session IDs are never logged - format_sql: uppercase_keywords=true also uppercases operator/function keywords; option semantics documented precisely - docs/privacy: session state is swept 30-45 minutes after last request and IDs are not tracked - tests: full HTTP chain via newMux, session TTL wiring, sweeper leak, widget script structure, constants and metadata keys Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L2wnDjnjXs2u8baaf2PC6w
…rrors - rate limit keys on RemoteAddr; X-Forwarded-For no longer trusted by default; opt-in GOSQLX_MCP_TRUSTED_PROXY_HEADER / _HOPS with IP validation (render.yaml: CF-Connecting-IP) - bounded limiter memory with per-shard LRU eviction - GET/SSE streams excluded from load metric, capped at 4 per IP - POST body read deadline (408), idle timeout, 32KB header cap, read deadline on non-/mcp routes so stalled bodies cannot hold connections - oversized bodies get 413; denials are HTTP 429 with Retry-After and the request id (initialize keeps 200 for mcp-go clients); batches charged per tool call - bearer auth: constant-time digest compare, case-insensitive scheme - tool input problems return isError results naming the argument; indent_size limited to 0-16 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L2wnDjnjXs2u8baaf2PC6w
…ransport errors Pin the streamable HTTP transport to mcp-go's StatelessGeneratingSessionIdManager instead of relying on the library default. mcp-go's InsecureStatefulSessionIdManager stores every terminated session ID in a sync.Map that is never pruned (v0.45.0 server/streamable_http.go:1489,1523); pinning the manager keeps an upstream default change from turning the public no-auth server into an unbounded store of session IDs. Redact transport error arguments unless the format is known to be safe. mcp-go logs errors that embed the client-supplied Mcp-Session-Id header (streamable_http.go:363 via :826 and :1471), which any client can trigger by POSTing a response-shaped JSON-RPC message, contradicting the privacy policy's promise that session IDs are never logged. Generated IDs are also scrubbed from every error line as a second line of defence. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RkzVu3JY9Y6BRWfjesWts9
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Claude finished @ajitpratap0's task in 24s —— View job Review of PR #835I reviewed the Overall: The change is small, focused and does what it says. Pinning the session ID manager is a cheap guard against an upstream default change. Redacting by allowlist means formats added in future mcp-go releases fail closed. Strengths
Suggestions (non-blocking)
Performance: Negligible. The regex and Security: This closes a real, unauthenticated log-leak path. No new concerns found. Verdict: Looks good to merge once the stacked base PRs (#831 and #823) land. |
#831 was squash-merged; took main's versions of its files and re-applied only this PR's pkg/mcp/server.go change (import block union). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RkzVu3JY9Y6BRWfjesWts9
|
Claude finished @ajitpratap0's task in 14s —— View job Review of PR #835I read the Verdict: Good to merge once the stacked base PRs (#831 and #823) land. I found no blocking issues. What works
Suggestions (non-blocking)
Performance: The regex and Security: This closes an unauthenticated path where a client-supplied |
|
Claude finished @ajitpratap0's task in 17s —— View job Review of PR #835I read the Verdict: No blocking issues. Merge once the stacked base PRs (#831 and #823) land. What works
Suggestions (non-blocking)
Test coverage: The PR lists four tests that cover both the manager pinning and the log leak. The PR's mutation check (swapping in the stateful manager and the old |
* chore(parser): remove stray parser.go-e backup with conflict markers [vercel skip] The sed backup file was committed by accident in #791. It is not compiled but ships in the module zip. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RkzVu3JY9Y6BRWfjesWts9 * chore(release): bump version to 1.16.0 [vercel skip] gosqlx.Version and the CLI version were still 1.14.0 (v1.15.0 shipped with them), so gosqlx --version, the MCP server info and /health reported 1.14.0. Also bumps the VS Code extension, Claude Code plugin and marketplace, glama, performance baseline label, llms.txt and the site's release badge. pygosqlx __version__ now matches pyproject (0.2.0). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RkzVu3JY9Y6BRWfjesWts9 * docs(changelog): finalize 1.16.0 release notes [vercel skip] Turns [Unreleased] into [1.16.0] - 2026-10-21, adds the missing entries for #734, #735, #780, #786, #789, #790, #791, #826, #835, #836, #837, #840, #841, #843 and #844, updates the MCP and composite-field entries amended by #841 and #843, adds PR refs, and adds the compare links. The VS Code extension gets a 1.16.0 entry, and split-changelog.js generates the /changelog page. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RkzVu3JY9Y6BRWfjesWts9 * docs: v1.15 to v1.16 migration guide and release doc fixes [vercel skip] MIGRATION gains a v1.15.0 -> v1.16.0 section with a behaviour-change checklist. ERROR_CODES documents E4001 dialect gating and the 11 dialect names. CLI_GUIDE lists --dialect on format, parse, analyze and lint and all 11 dialects. SQL_COMPATIBILITY marks Oracle CONNECT BY/PRIOR and SQL Server PIVOT as parsed. api/mcp.md matches the current Config and middleware API, api/models.md drops the nonexistent Location.Offset and documents byte columns, and HIGHLIGHTING notes ANSI control-character neutralization. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RkzVu3JY9Y6BRWfjesWts9 * chore(website): regenerate sample AST and changelog pages The website build rewrote both files on main: sample-ast.json gains the WithOrdinality and ColumnAliases fields from #843, and v1-9-1.md drops the emoji the changelog splitter strips. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RkzVu3JY9Y6BRWfjesWts9 * docs(release): correct allocation claim, plugin history, E4001 and LoadConfig errors Address review notes on #845: the lead paragraph understated the perf audit's allocation cuts, the MIGRATION table listed a v1.15.0 plugin that never shipped, E4001 also covers LIMIT under SQL Server, and LoadConfig rejects more trusted-proxy misconfigurations than the API doc listed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RkzVu3JY9Y6BRWfjesWts9 * docs(release): make glama.json match the MCP server List all 11 validate_sql dialects, describe lint_sql as the 10 rules it runs (L001-L010; the CLI and SDK have 30), sync tool descriptions with pkg/mcp/server.go, and drop the unsourced 1.38M ops/sec claim in favour of a link to the benchmarks page. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RkzVu3JY9Y6BRWfjesWts9 * docs(release): add #846-#851 to the v1.16.0 CHANGELOG and migration guide Record the pygosqlx 0.3.0 API and wheels, the gosqlx_version double free, LSP serverInfo/CLI version reporting, the Python CI and release workflows and the #848 docs. MIGRATION companion table lists pygosqlx 0.3.0, libgosqlx exports and the LSP serverInfo version, with matching behaviour-change notes. Regenerate the v1.16.0 blog page. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RkzVu3JY9Y6BRWfjesWts9 * docs(release): address v1.16.0 release review; add #852 - Release date 2026-10-22 in CHANGELOG, MIGRATION, the extension changelog and the generated blog post (re-stamp at tag time if it slips). - vscode-extension CHANGELOG: Fixed entry for the #846 version reporting. - CHANGELOG: keep the Behavior changes list tight; name TranspileError and the LintResult/SecurityScanResult types in the pygosqlx 0.3.0 entry. - MIGRATION: PyPI wheels row is conditional on the pygosqlx-v0.3.0 tag. - Add #852 (sdist builds fail without libgosqlx; release tag lookup skips v1). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RkzVu3JY9Y6BRWfjesWts9 * build(release): replace deprecated goreleaser keys (formats, version_template) goreleaser check (v2.12.7) flagged archives.format, format_overrides.format and snapshot.name_template as deprecated; release.yml runs goreleaser 'latest'. Snapshot release dry-run builds all six archives. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RkzVu3JY9Y6BRWfjesWts9 * docs(release): add #853 to the v1.16.0 CHANGELOG Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RkzVu3JY9Y6BRWfjesWts9 * feat(python): version pygosqlx in lockstep with GoSQLX (1.16.0) pygosqlx wheels bundle libgosqlx built from the same commit, so its version now matches the GoSQLX release. The first PyPI release is 1.16.0 (it was 0.2.0, never published); no older versions are backfilled. - pygosqlx-release.yml also publishes on GoSQLX 'vX.Y.Z' tags (the floating 'v1' tag does not match); 'pygosqlx-v*' stays for Python-only .postN releases. Both must match pyproject.toml and be on main. - pyproject.toml / __version__ 1.16.0; the version test now asserts __version__ equals the bundled library's version(). - CHANGELOG, MIGRATION and python/README describe the lockstep scheme. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RkzVu3JY9Y6BRWfjesWts9 --------- Co-authored-by: Ajit Pratap Singh <ajitpratapsingh@Ajits-Mac-mini-2655.local> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Summary
Stacked on #831 (which is stacked on #823); retarget after those merge.
StatelessGeneratingSessionIdManager(newSessionIdManagerinpkg/mcp/server.go) instead of relying on the library default.transportLogger.Errorfnow prints arguments only for an allowlist of mcp-go formats known to carry no session ID (I/O write failures,ErrSessionExists). Any other format, including formats added in future mcp-go releases, is logged with[redacted]arguments. Generatedmcp-session-<uuid>IDs are also scrubbed from every error line as a second line of defence. Info messages are still dropped, as they were in fix(mcp): harden gosqlx-mcp HTTP server (rate limiting, timeouts, auth, tool errors) #831.Root cause
Terminated-session map (latent; not active on this branch). In mcp-go v0.45.0,
InsecureStatefulSessionIdManagerstores every terminated ID interminated sync.Map(server/streamable_http.go:1489,Storeat:1523), and nothing ever prunes it. I checked the code: this server never used that manager. It ran on the library default,StatelessGeneratingSessionIdManager(streamable_http.go:218), which keeps no record of IDs. So the unbounded growth was not happening in production. The risk was that a future change to mcp-go's default would turn it on with no change on our side. Pinning the manager removes that risk. Our tools need no per-session state, so stateless mode loses nothing. DELETE and the idle sweeper from #831 still free the transport's per-session state.Session IDs in logs (an active bug, which #831 did not fix). #831 drops
Infof, but someErrorfpaths include the client-suppliedMcp-Session-Id:streamable_http.go:363logsFailed to handle sampling response: %vwith errors fromdeliverSamplingResponse(no active session found for session <id>,:826) andValidate(invalid session id: <id>,:1471).:811logsFailed to deliver sampling response: %v.Any client can reach these paths without auth by POSTing a response-shaped JSON-RPC message (no
method, withidandresult). The ID, or any string the client puts in the header, then lands in the server log. The privacy page says MCP session identifiers are "never logged".Test Plan
New file
pkg/mcp/session_retention_test.go:TestNewSessionIdManager_KeepsNoTerminatedSet: the manager is the stateless generating one, and afterTerminateit does not report the ID as terminated, so nothing is tracked.TestServer_CreateTerminateCyclesRetainNothing: 500 initialize+DELETE cycles over HTTP, then apingwith the first terminated ID returns 200. WithInsecureStatefulSessionIdManagerit returns 404Session terminated, which means IDs are being recorded.TestTransportLogger_RedactsSessionIDsInErrors(table test): unknown formats are redacted, safe formats keep their error detail, and generated IDs are scrubbed even from safe formats.TestServer_SessionIDsNotLoggedEndToEnd: sends sampling-response POSTs through the real transport, with both a real ID and a forged one. The error is logged, but neither ID (nor the UUID part of the real one) appears in the log.Mutation check: I swapped in
InsecureStatefulSessionIdManagerand the oldErrorf. Both end-to-end tests failed (404 on the terminated ID; the real and forged IDs leaked into the log). Both pass with the fix.Commands run (all passing):
Breaking Changes
None. The transport already used stateless generating IDs, so client-visible behaviour is unchanged. Arguments of non-allowlisted transport errors are now redacted in server logs.
🤖 Generated with Claude Code
https://claude.ai/code/session_01RkzVu3JY9Y6BRWfjesWts9