feat(api-server): add session history endpoint#189
feat(api-server): add session history endpoint#189lngdao wants to merge 2 commits intoOpen-ACP:developfrom
Conversation
GET /api/v1/sessions/:sessionId/history returns the full conversation history for a session from the context plugin's HistoryStore.
0xmrpeter
left a comment
There was a problem hiding this comment.
Code Review
Clean and focused PR (+25 lines, 4 files). The wiring pattern follows existing conventions well. A few issues to address before merge:
Blocking
1. Missing session existence check
Every other /:sessionId/* route validates session existence first via sessionManager.getSession() and throws SESSION_NOT_FOUND. This route skips that step and goes straight to the history store.
Impact: API consumers can't distinguish "session never existed" from "session exists but has no history yet" — both return 404 with different codes but collapsed semantics.
Fix: Add the standard session existence check before querying the history store, matching the pattern used in other routes (e.g., PATCH /:sessionId).
2. Path traversal risk via sessionId
HistoryStore.filePath() does:
path.join(this.dir, `${sessionId}.json`)SessionIdParamSchema only validates z.string().min(1) with no character set constraint. After decodeURIComponent, a crafted sessionId like ../../etc/passwd could resolve outside the history directory.
This vulnerability exists in the schema itself, but this PR creates a new attack surface by exposing HistoryStore.read() directly to HTTP input (previously only used with trusted internal session IDs).
Fix (pick one):
- Best: Add regex to
SessionIdParamSchema:z.string().min(1).regex(/^[a-zA-Z0-9_:-]+$/) - Minimum: Add path traversal guard in the route handler (reject
..,/,\)
Should Fix
3. Wrong HTTP status for "history store unavailable"
When the context plugin isn't installed, the route returns:
404 HISTORY_UNAVAILABLE — "History store not available"
404 = "resource not found". A missing service/dependency is semantically 503 Service Unavailable or 501 Not Implemented. Returning 404 misleads consumers into thinking the history doesn't exist when the real problem is the capability isn't installed.
Nice to Have (non-blocking)
4. Unbounded response size — Full SessionHistory with all turns/steps/tool calls can get very large for long-lived sessions. Fine for v1, but worth planning pagination (?limit=N&offset=M) for a future iteration.
5. Documentation sync — Per project conventions, new features should update README and/or docs/ to describe the endpoint, usage, and example response.
Summary
| # | Issue | Severity |
|---|---|---|
| 1 | Missing session existence check | Blocking |
| 2 | Path traversal via sessionId | Blocking (security) |
| 3 | 404 → 503 for unavailable service | Should fix |
| 4 | Unbounded response size | Nice to have |
| 5 | Documentation update | Nice to have |
Overall: well-structured change, just needs the session check + path sanitization before merge. 👍
… traversal, 503 status - Add session existence check before querying history store - Add regex constraint to SessionIdParamSchema to prevent path traversal - Return 503 instead of 404 when history store is unavailable
…ror handling - Revert SessionIdParamSchema regex that was a breaking change for all session routes - Add path traversal protection in HistoryStore.filePath() using path.basename() - Remove decodeURIComponent bypass risk in history route - Add ServiceUnavailableError class for consistent 503 error handling - Refactor: route history access through ContextManager.getHistory() instead of exposing internal HistoryStore as standalone service - Add path traversal and ContextManager.getHistory() tests Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…al, 503 status - Add session existence check before querying history store - Add regex constraint to SessionIdParamSchema to prevent path traversal - Return 503 instead of 404 when history store is unavailable
…ror handling - Revert SessionIdParamSchema regex that was a breaking change for all session routes - Add path traversal protection in HistoryStore.filePath() using path.basename() - Remove decodeURIComponent bypass risk in history route - Add ServiceUnavailableError class for consistent 503 error handling - Refactor: route history access through ContextManager.getHistory() instead of exposing internal HistoryStore as standalone service - Add path traversal and ContextManager.getHistory() tests Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
Superseded by #192 — merged into develop with security fixes, encapsulation improvements, and tests. |
Summary
GET /api/v1/sessions/:sessionId/historyendpoint to retrieve full conversation historyHistoryStore(file-based JSON)404if history store unavailable or no history for the sessionimport()type inindex.ts→ proper top-levelimport typeFiles changed
src/plugins/api-server/routes/sessions.tssrc/plugins/api-server/routes/types.tshistoryStoretoRouteDepssrc/plugins/api-server/index.tshistoryStoreinto deps, clean up importsrc/plugins/context/index.tshistory-storeserviceTest plan
pnpm buildpassesGET /api/v1/sessions/:id/historyreturns history for active session