Impact
History MCP scanning follows symlinked workspace/session paths outside the saved Workspaces tree and does not verify that a decoded session's UUID matches the requested filename/ID. A history lookup can therefore expose or misattribute another durable transcript while reporting it as the requested session/workspace.
Affected snapshot
Confirmed statically on origin/main at a094dd9a83d03820201769ebc079c49fbd2e86fc.
Reachable scenarios
- A workspace directory,
AgentSessions component, or AgentSession-<UUID>.json beneath the history root is replaced with a symlink to readable data outside that root.
- Or a valid session JSON is copied under another session UUID's filename.
locateSession, search enrichment, or get_session stats and reads the pathname, decodes the session, and returns its transcript without containment/no-follow checks or session.id == requested sessionID validation.
A focused test can seed a symlinked session file plus a same-format file whose embedded UUID differs from its filename.
Evidence
Sources/RepoPrompt/Features/AgentMode/History/HistorySessionScanner.swift:839-864,1702-1722 uses stat, which follows symlinks, to accept workspace directories and files.
HistorySessionScanner.swift:1225-1329 builds the requested pathname, uses Data(contentsOf:), and decodes AgentSession without verifying the embedded session ID.
- Cache keys use standardized path strings rather than an opened descriptor/canonical authority identity.
- The history surface is intentionally cross-workspace, but its scope is still the application's saved workspace roots and the explicitly requested session identity.
Expected behavior
History reads must remain inside the canonical saved-workspace authority, must not follow attacker-controlled symlink components, and must return only the session whose embedded identity matches the request.
Suggested direction
Open from a trusted root descriptor with no-follow component traversal, verify canonical descendant containment and regular-file identity, and validate session.id == sessionID after decode. Treat violations as explicit non-retryable authority/corruption failures.
Acceptance criteria
- Symlinked workspace, AgentSessions, index, and session-file components cannot escape the history root.
- A filename/embedded-session-ID mismatch is rejected and never cached under the requested ID.
- Direct lookup, list/search enrichment, and
get_session share the same authority checks.
- Regression tests cover path escape, symlink replacement, and copied-under-another-UUID files.
Validation status
Static source review only; no history files were modified or read outside the review worktree.
Related
Impact
History MCP scanning follows symlinked workspace/session paths outside the saved Workspaces tree and does not verify that a decoded session's UUID matches the requested filename/ID. A history lookup can therefore expose or misattribute another durable transcript while reporting it as the requested session/workspace.
Affected snapshot
Confirmed statically on
origin/mainata094dd9a83d03820201769ebc079c49fbd2e86fc.Reachable scenarios
AgentSessionscomponent, orAgentSession-<UUID>.jsonbeneath the history root is replaced with a symlink to readable data outside that root.locateSession, search enrichment, orget_sessionstats and reads the pathname, decodes the session, and returns its transcript without containment/no-follow checks orsession.id == requested sessionIDvalidation.A focused test can seed a symlinked session file plus a same-format file whose embedded UUID differs from its filename.
Evidence
Sources/RepoPrompt/Features/AgentMode/History/HistorySessionScanner.swift:839-864,1702-1722usesstat, which follows symlinks, to accept workspace directories and files.HistorySessionScanner.swift:1225-1329builds the requested pathname, usesData(contentsOf:), and decodesAgentSessionwithout verifying the embedded session ID.Expected behavior
History reads must remain inside the canonical saved-workspace authority, must not follow attacker-controlled symlink components, and must return only the session whose embedded identity matches the request.
Suggested direction
Open from a trusted root descriptor with no-follow component traversal, verify canonical descendant containment and regular-file identity, and validate
session.id == sessionIDafter decode. Treat violations as explicit non-retryable authority/corruption failures.Acceptance criteria
get_sessionshare the same authority checks.Validation status
Static source review only; no history files were modified or read outside the review worktree.
Related