Skip to content

feat(kiro): wire up new file-based session format (~May 2026) - #268

Open
Sean10 wants to merge 2 commits into
vakovalskii:mainfrom
Sean10:feat/kiro-cli-file-format
Open

feat(kiro): wire up new file-based session format (~May 2026)#268
Sean10 wants to merge 2 commits into
vakovalskii:mainfrom
Sean10:feat/kiro-cli-file-format

Conversation

@Sean10

@Sean10 Sean10 commented Jul 22, 2026

Copy link
Copy Markdown

Follow-up to #230. Part 1 (maxBuffer + ToolUse) already landed in f2b77f9; this PR is Part 2 reworked around @vakovalskii's review feedback.

What & why

Kiro CLI moved to per-session files under ~/.kiro/sessions/cli/:

  • <uuid>.json — metadata (session_id, cwd, title, created_at, updated_at)
  • <uuid>.jsonl — events, one per line: Prompt → user, AssistantMessage → assistant, ToolResults → skipped

The previous version of Part 2 listed these sessions but wasn't wired end-to-end — detail/preview/search/replay/export all resolve through findSessionFile() / _buildSessionFileIndex(), which didn't know about kiro-cli, so opening a session returned "Session file not found" and the new found.format === 'kiro-cli' branches were never reached.

Changes (addressing review points)

  1. Index the new files_buildSessionFileIndex() now scans ~/.kiro/sessions/cli/*.jsonl and registers them as { format: 'kiro-cli', sessionId }, so findSessionFile() resolves them and every downstream consumer (detail, preview, search, replay, export) works end-to-end.
  2. Path-traversal fixloadKiroCliDetail() validates sessionId as a strict UUID before path.join, since it takes untrusted request input once wired. The scanner uses the same strict UUID pattern.
  3. Fixture-based tests — new test/kiro-cli-session.test.js covers scan, detail parsing, path-traversal rejection, index resolution, and full end-to-end wiring (detail/preview/replay/export).

scanKiroCliSessions() + loadKiroCliDetail() carry the parsing; old SQLite-based Kiro sessions remain fully supported alongside the new format.

Testing

  • node --test test/*.test.js → 207 pass, 2 skipped (win32-only), 0 fail
  • New end-to-end test fails without the index wiring and passes with it

Kiro CLI moved to per-session files under ~/.kiro/sessions/cli/:
  <uuid>.json  — metadata (session_id, cwd, title, created_at, updated_at)
  <uuid>.jsonl — events: Prompt / AssistantMessage / ToolResults

- Add KIRO_SESSIONS_DIR + scanKiroCliSessions() and loadKiroCliDetail()
- Index the .jsonl files in _buildSessionFileIndex so they resolve to
  { format: 'kiro-cli' }, wiring detail/preview/search/replay/export
  end-to-end (previously listed but "Session file not found" on open)
- Validate the sessionId as a strict UUID in loadKiroCliDetail before
  path.join to close a path-traversal vector on untrusted input
- Add fixture-based tests covering scan, detail, path-traversal
  rejection, index resolution, and end-to-end wiring

Old SQLite-based Kiro sessions remain fully supported alongside.

@vakovalskii vakovalskii left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Thanks for this — the file-based Kiro reader works and merges cleanly (all tests green, incl. the new ones). One blocker before merge, from a security pass:

Symlink traversal when reading session files (HIGH). The <uuid>.jsonl filenames are correctly UUID-validated (so ../ traversal is blocked), but fs.readFileSync(path.join(KIRO_SESSIONS_DIR, f)) still follows symlinks. A symlink named <valid-uuid>.jsonl inside ~/.kiro/sessions/cli/ pointing to e.g. ~/.ssh/id_rsa would be read and surfaced through the dashboard (detail / export / search / replay). This matters because codbash can be bound to a LAN address, and it's inconsistent with how the rest of the codebase already defends against this — see the Claude reader (src/data.js, ~line 679: if (entry.isSymbolicLink()) continue;) and isSafeLaunchPath in projects.js.

Two read sites are affected:

  • the session-list loader (readFileSync of the meta/.jsonl)
  • the detail loader (readFileSync(jsonlPath, ...))

Fix direction: before each read, reject symlinks / enforce containment, e.g.

const st = fs.lstatSync(p);
if (st.isSymbolicLink() || !st.isFile()) continue; // or: return null in the detail path

or fs.realpathSync(p) and verify it still starts with KIRO_SESSIONS_DIR + path.sep. Prefer lstat + isSymbolicLink to match the existing pattern.

No command-injection or prototype-pollution issues found in the new code — just this. Happy to merge once the symlink guard is in. 🙏

…view)

The <uuid> UUID check blocks ../ path traversal, but readFileSync still
followed symlinks: a symlink named <valid-uuid>.jsonl (or .json) inside
~/.kiro/sessions/cli/ pointing at e.g. ~/.ssh/id_rsa would be read and
exposed via the dashboard (detail/export/search/replay), especially when
codbash binds to a LAN address.

Guard both read sites with lstat + isSymbolicLink, matching the existing
Claude reader (src/data.js: `if (entry.isSymbolicLink()) continue;`):
- scanKiroCliSessions: readdir withFileTypes skips symlinked metadata;
  the .jsonl is lstat'd so a symlinked events file reports has_detail:false
- loadKiroCliDetail: lstat the .jsonl and refuse non-regular files
- _buildSessionFileIndex: skip symlinked .jsonl for defense-in-depth

Tests craft UUID-named symlinks to out-of-tree files with content that
would surface if followed (LEAKED-TITLE / LEAKED-CONTENT); verified they
fail with the guards removed and pass with them in place.
@Sean10

Sean10 commented Jul 27, 2026

Copy link
Copy Markdown
Author

Thanks for the careful review — good catch on the symlink follow. Fixed in 16e14ef.

The UUID check only blocked ../ traversal; readFileSync still followed symlinks. Guarded both read sites with lstat + isSymbolicLink, matching the existing Claude reader (if (entry.isSymbolicLink()) continue;):

  • scanKiroCliSessionsreaddirSync(..., { withFileTypes: true }) skips symlinked metadata; the .jsonl is lstat'd so a symlinked events file reports has_detail: false / file_size: 0.
  • loadKiroCliDetaillstat the .jsonl and refuse anything that isn't a regular file before reading.
  • _buildSessionFileIndex — skip symlinked .jsonl too, for defense-in-depth (so it never resolves to { format: 'kiro-cli' }).

Added two tests that craft UUID-named symlinks pointing at out-of-tree files whose content would surface if followed (LEAKED-TITLE in metadata, LEAKED-CONTENT in events). I verified they fail with the guards removed and pass with them in place, so they actually bite rather than passing vacuously. Full suite: 209 pass, 2 skipped (win32-only), 0 fail — and re-confirmed against real data (125 CLI sessions still scan, legitimate sessions read fine).

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