Skip to content

feat(api-server): add session history endpoint#189

Closed
lngdao wants to merge 2 commits intoOpen-ACP:developfrom
lngdao:feat/session-history-endpoint
Closed

feat(api-server): add session history endpoint#189
lngdao wants to merge 2 commits intoOpen-ACP:developfrom
lngdao:feat/session-history-endpoint

Conversation

@lngdao
Copy link
Copy Markdown
Contributor

@lngdao lngdao commented Apr 1, 2026

Summary

  • Add GET /api/v1/sessions/:sessionId/history endpoint to retrieve full conversation history
  • Reads from the context plugin's HistoryStore (file-based JSON)
  • Returns 404 if history store unavailable or no history for the session
  • Clean up inline import() type in index.ts → proper top-level import type

Files changed

File Change
src/plugins/api-server/routes/sessions.ts Add history route
src/plugins/api-server/routes/types.ts Add historyStore to RouteDeps
src/plugins/api-server/index.ts Wire historyStore into deps, clean up import
src/plugins/context/index.ts Register history-store service

Test plan

  • pnpm build passes
  • GET /api/v1/sessions/:id/history returns history for active session
  • Returns 404 when history store not available
  • Returns 404 when no history for given sessionId

GET /api/v1/sessions/:sessionId/history returns the full conversation
history for a session from the context plugin's HistoryStore.
Copy link
Copy Markdown
Contributor

@0xmrpeter 0xmrpeter left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
0xmrpeter added a commit that referenced this pull request Apr 2, 2026
…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>
0xmrpeter pushed a commit that referenced this pull request Apr 2, 2026
…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
0xmrpeter added a commit that referenced this pull request Apr 2, 2026
…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>
@0xmrpeter
Copy link
Copy Markdown
Contributor

Superseded by #192 — merged into develop with security fixes, encapsulation improvements, and tests.

@0xmrpeter 0xmrpeter closed this Apr 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants