Copy uploads into worktree on first message#71
Conversation
|
Security: Command injection via unsanitized file paths File paths are interpolated directly into a shell command using single quotes with no escaping: wisp/Wisp/ViewModels/ChatViewModel.swift Lines 1398 to 1400 in b10c5dd Single quotes in POSIX shell have no escape sequence — a filename containing a single-quote character (e.g. a file named with an apostrophe) breaks out of the quoting and allows arbitrary command execution on the Sprite. Since attachment.path comes from user-uploaded filenames, this is a shell injection vulnerability. To fix, escape single quotes in each path before interpolation. The standard POSIX idiom is to end the single-quoted string, insert an escaped literal quote, then reopen: replace each single-quote with the four-character sequence: close-single-quote, backslash, single-quote, open-single-quote. Then build the command with cp shellEscape(attachment.path) shellEscape(newPath). |
|
Bug: Copy result is discarded so failed copies still return the new (non-existent) path The wisp/Wisp/ViewModels/ChatViewModel.swift Lines 1406 to 1411 in b10c5dd The doc comment promises "Files that fail to copy are left with their original paths", but this is not what happens. When a To fix, either:
|
|
CLAUDE.md: Missing unit tests for new view model logic The PR adds a new method CLAUDE.md requires: The new path-construction logic, the |
When a file is uploaded and then a worktree is created on the first message send, the uploaded file only existed in the original working directory. This copies any pre-send attachments into the worktree after it's created, and rebuilds the prompt with the new paths so Claude operates on files that are actually inside the git worktree. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
b10c5dd to
24cb257
Compare
Three issues flagged in review: 1. Shell injection via unescaped single quotes: file paths were interpolated directly into `cp '...' '...'` with no escaping. Added `shellEscapePath(_:)` which applies the POSIX idiom of closing the quote, inserting `\'`, then reopening. 2. Failed copies returned the new (non-existent) path: `updated` was built with new paths before exec ran, so any `skip` still resolved to the worktree path. Fixed by building a `copyJobs` index first, then updating only entries where the exec echoed `ok`. 3. Missing unit tests: added WorktreeTests.swift covering `shellEscapePath` (plain, apostrophe, spaces, empty) and `copyAttachmentsToWorktree` (no worktree, already-in-worktree, failed-copy fallback, mixed). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The user bubble was added to messages[] with original file paths before the worktree was created. claudePrompt was rebuilt with worktree paths but the displayed message never reflected this. Since ChatMessage is a reference type, update userMessage.content in place so the bubble shows the same paths Claude actually receives. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Summary
/home/sprite/project/file.txt)main— which doesn't include the uncommitted uploadHow it works
In
sendMessage(), the raw text and attachments are captured before being cleared. AftersetupWorktree()succeeds, a newcopyAttachmentsToWorktree()helper:cpcommands into a single exec call on the spriteAttachedFilevalues with the new pathsThe prompt sent to Claude is then rebuilt with the worktree paths. The display message stored in
messageskeeps the original paths (already appended before the task runs) — this is a minor cosmetic inconsistency but doesn't affect functionality since absolute paths remain valid.If the copy fails, Claude falls back to the original absolute paths, which are still readable.
Test plan
git additattachment.path != newPath)🤖 Generated with Claude Code