Skip to content

fix(security): sanitize session id to block path traversal in grounding-mcp session store#102

Merged
LanNguyenSi merged 1 commit into
masterfrom
security/high-findings
May 31, 2026
Merged

fix(security): sanitize session id to block path traversal in grounding-mcp session store#102
LanNguyenSi merged 1 commit into
masterfrom
security/high-findings

Conversation

@LanNguyenSi
Copy link
Copy Markdown
Owner

Summary

The grounding-mcp session store derived a session file path from a client-controlled sessionId with no sanitisation, allowing path traversal (arbitrary <path>.json read and file-existence probing) outside the sessions root. This applies the same guard the sibling verdict store already ships.

Finding fixed

  • Path traversal via unsanitised sessionId (packages/grounding-mcp/src/session-store.ts:25-27, pathFor). The read verbs grounding_advance (server.ts:146), grounding_guardrail_check (server.ts:167), and claim_evaluate_from_session (server.ts:286) pass the raw MCP-client string into loadSession / sessionExists, which built the path via join(sessionsRoot(), ${id}.json). A client sending sessionId: "../../../../etc/hostname" escaped sessionsRoot(). Guard added: a new sanitizeSessionId() (session-store.ts:36) collapses any character outside [A-Za-z0-9._-] to _, applies path.basename, and rejects empty / . / .., mirroring the existing sanitizeVerdictId() in solution-verdict.ts:88. It is called inside pathFor() (session-store.ts:55) so loadSession, saveSession, and sessionExists all inherit the guard. Server-generated ids (gs-<slug>-<base36>) only contain safe characters, so legitimate sessions round-trip unchanged.

Verification

  • npx tsc --noEmit in packages/grounding-mcp: exit 0, no type errors.
  • npx vitest run tests/roundtrip.test.ts: 7/7 passed, confirming legitimate session ids still save and load correctly through the hardened pathFor().

Refs: 2026-05-30 security audit (HIGH)

The read verbs (grounding_advance, grounding_guardrail_check,
claim_evaluate_from_session) pass a client-controlled sessionId straight
into loadSession/sessionExists, which built the file path via
join(sessionsRoot(), `${id}.json`) with NO sanitisation. A client could
send sessionId "../../../../etc/hostname" to read or probe arbitrary
<path>.json files outside the sessions root.

Add sanitizeSessionId() mirroring the existing sanitizeVerdictId() guard in
solution-verdict.ts (collapse non [A-Za-z0-9._-] to "_", path.basename,
reject ""/"."/".."), and call it inside pathFor() so loadSession,
saveSession and sessionExists all inherit the guard. Server-generated ids
(gs-<slug>-<base36>) only use safe characters, so legitimate sessions
round-trip unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@LanNguyenSi LanNguyenSi added review:tests-pass merge-approval prereq review:checklist-complete merge-approval prereq review:comments-resolved merge-approval prereq review:scope-matches-task merge-approval prereq review:evidence-logged merge-approval prereq labels May 31, 2026
@LanNguyenSi LanNguyenSi merged commit 6205df7 into master May 31, 2026
3 of 4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

review:checklist-complete merge-approval prereq review:comments-resolved merge-approval prereq review:evidence-logged merge-approval prereq review:scope-matches-task merge-approval prereq review:tests-pass merge-approval prereq

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants