Skip to content

fix(security): enforce canonical path containment on renderer-supplied meeting paths#329

Open
Optic00 wants to merge 1 commit into
ruzin:mainfrom
Optic00:fix/path-validation-trust-boundary
Open

fix(security): enforce canonical path containment on renderer-supplied meeting paths#329
Optic00 wants to merge 1 commit into
ruzin:mainfrom
Optic00:fix/path-validation-trust-boundary

Conversation

@Optic00

@Optic00 Optic00 commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator

Summary

Closes several path-traversal issues where the untrusted Electron renderer (XSS / compromised renderer / DevTools) could drive the backend or the main process into reading, writing, or deleting arbitrary local files. Enforcement is at the Electron trust boundary; the Python CLI stays intentionally permissive so the documented query transcript.txt debug path keeps working.

Issues fixed

  • Arbitrary file read (+ potential exfiltration)query-transcript / query-transcript-stream passed the renderer's summaryFile straight to the backend query command, which reads any UTF-8 file (no extension gate on the plain-text path). With a cloud/org provider the contents are also sent to the external AI service.
  • Arbitrary file writeadd-/remove-meeting-to-folder passed summaryFile straight to the folders backend, which rewrites the target file.
  • Symlink escapeupdate-meeting used a lexical-only path check, then read/wrote the original (symlink-followed) path across the whole project/storage root.
  • Symlink-followed deletedelete-meeting / reveal / system-audio shared the same lexical check.

Fix

  • Route the four meeting-file handlers through validateMeetingFilePath() (output/-only, realpath-canonicalized) and pass the canonical realPath downstream — never the original renderer string.
  • Harden validateSafeFilePath() with realpathSync canonicalization of both target and base dirs, covering delete/reveal/system-audio with no call-site change.
  • Additional hardening on the stream handler this change makes async: guard a query-cancel or sender-destroy landing during pre-spawn validation (cancel-race / orphaned backend), type-guard non-string input at the validateMeetingFilePath source (avoids an unhandled rejection that could crash main), and make query-done delivery exception-safe. The chat_message_sent analytics from feat: instrument product analytics for activation, engagement and usage #323 is preserved.

Tests

New model-free T2 e2e spec path-validation-security.t2.spec.ts: asserts rejection of out-of-output/ paths, non-string input, and symlink escapes on both validator seams, and that the out-of-bounds file is never read / modified / deleted. meetings-crud / folders-crud / chat-sessions T2 regressions green.


Summary by cubic

Enforces symlink-safe, canonical path containment for renderer-supplied meeting files to block arbitrary local file read/write/delete from the Electron renderer. Validation now happens at the Electron trust boundary; the Python CLI remains permissive for documented debug flows.

  • Bug Fixes
    • Guard query-transcript, query-transcript-stream, update-meeting, and add/remove-meeting-to-folder with validateMeetingFilePath (output/ only) and pass the canonical realPath downstream.
    • Canonicalize both target and base dirs in validateSafeFilePath using fs.realpathSync, securing delete/reveal/system-audio without call-site changes.
    • Pre-validate in query-transcript-stream, add pending-cancel handling to avoid orphaned backends, and make query-done delivery exception-safe.
    • Reject non-string paths early to prevent unhandled rejections and main-process crashes.
    • Add a T2 e2e spec covering out-of-output/ paths, non-string input, and symlink escapes; asserts no unauthorized disk reads/writes/deletes.

Written for commit 541654f. Summary will update on new commits.

Review in cubic

…d meeting paths

The Electron renderer is an untrusted trust boundary (XSS / compromised
renderer / DevTools). Several IPC handlers passed renderer-supplied file paths
straight to the Python backend or to fs without symlink-safe containment,
enabling arbitrary local file read/write/delete:

- query-transcript / query-transcript-stream: arbitrary file READ (and
  exfiltration via cloud/org providers) - the backend's query command reads any
  UTF-8 file, with no extension gate on the plain-text path.
- add-/remove-meeting-to-folder: arbitrary file WRITE via the folders backend.
- update-meeting: symlink escape - used the lexical validateSafeFilePath then
  read/wrote the original (symlink-followed) path, over the whole project root.
- delete-meeting / reveal / system-audio: validateSafeFilePath was lexical-only,
  so a symlink inside an allowed dir escaped it.

Route the four meeting-file handlers through validateMeetingFilePath()
(output/-only, realpath-canonicalized) and pass the canonical realPath
downstream. Harden validateSafeFilePath() with realpathSync canonicalization of
both target and base dirs, covering delete/reveal/system-audio with no call-site
change. The Python CLI intentionally still accepts arbitrary paths (the
documented `query transcript.txt` debug command); the boundary is enforced in
Electron.

Also harden the stream handler that this change makes async: guard against a
query-cancel or sender-destroy landing during the pre-spawn validation
(cancel-race / orphaned backend), type-guard non-string input at the
validateMeetingFilePath source (avoids an unhandled rejection), and make
query-done delivery exception-safe.

Adds a model-free T2 e2e spec asserting rejection of out-of-output/ paths,
non-string input, and symlink escapes on both validator seams.
@Optic00 Optic00 requested a review from ruzin as a code owner July 9, 2026 20:49

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

No issues found across 2 files

Re-trigger cubic

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.

1 participant