Skip to content

feat(chat): support zip uploads as virtual folders in the copilot VFS#5252

Open
waleedlatif1 wants to merge 3 commits into
stagingfrom
zip-upload-support
Open

feat(chat): support zip uploads as virtual folders in the copilot VFS#5252
waleedlatif1 wants to merge 3 commits into
stagingfrom
zip-upload-support

Conversation

@waleedlatif1

Copy link
Copy Markdown
Collaborator

Summary

  • Accept .zip chat attachments (previously rejected with "Unsupported file type: zip")
  • Present each uploaded archive as a virtual folder in the copilot VFS — the agent lists entries with glob("uploads/x.zip/*"), reads them with read("uploads/x.zip/<path>"), and greps inside them
  • Store the archive once; extract entries lazily on read, reusing the existing file-parsers and the zip-bomb / zip-slip / symlink guards (factored out of the file-manage decompress route into lib/uploads/archive.ts)
  • The upload message shows a capped inline file tree so the agent sees contents without a glob round-trip
  • No changes to the Go copilot service — it only ever sees normal VFS reads

Type of Change

  • New feature

Testing

  • 72 unit tests across archive, validation, file-reader (renderFileBuffer + binary no-fetch), and the VFS read/glob/grep routing (incl. an NFD-unicode entry round-trip)
  • tsc, biome, and check:api-validation all clean
  • manage/route.ts decompress refactor is behavior-identical (shared primitives match the removed local copies exactly)

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

@vercel

vercel Bot commented Jun 28, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Skipped Skipped Jun 28, 2026 2:03am

Request Review

@cursor

cursor Bot commented Jun 28, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
User-supplied zip parsing and copilot file reads are security- and resource-sensitive, though caps and shared guards are tested; the decompress refactor should be behavior-identical.

Overview
Chat .zip attachments are now allowed and exposed to the agent as virtual folders under uploads/<name>/, with list/read/grep wired through the copilot VFS (lazy entry extraction, no full unpack).

Shared zip-bomb / zip-slip / symlink-safe logic moves from the file-manage decompress route into lib/uploads/archive.ts; the decompress API imports those primitives unchanged. Upload context on send can include a capped inline entry tree (fallback to glob hints if listing fails).

readChatUploadPath / grepChatUploadPath replace flat upload read/grep and handle nested archive paths, manifests on bare archive reads, size caps before download, and literal % filenames via an encoded-name match. renderFileBuffer centralizes buffer rendering for archive entries; readFileRecord skips fetching multi-GB binary placeholders when metadata is enough.

Reviewed by Cursor Bugbot for commit 5ea2699. Configure here.

Comment thread apps/sim/lib/copilot/tools/handlers/upload-file-reader.ts
Comment thread apps/sim/lib/copilot/tools/handlers/upload-file-reader.ts Outdated
@greptile-apps

greptile-apps Bot commented Jun 28, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds zip uploads as virtual folders in the copilot VFS. The main changes are:

  • Accept .zip chat attachments.
  • List, read, and grep archive entries through uploads/<zip>/<path>.
  • Share archive safety checks between decompression and VFS reads.
  • Show a capped archive tree in the chat upload context.
  • Add tests for archive routing, validation, rendering, and path handling.

Confidence Score: 5/5

This looks safe to merge.

  • No blocking issues found in the changed code.

Important Files Changed

Filename Overview
apps/sim/lib/copilot/tools/handlers/upload-file-reader.ts Adds archive-aware upload lookup, listing, reading, grepping, and size checks.
apps/sim/lib/copilot/tools/handlers/vfs.ts Routes uploads paths with optional archive entry suffixes and expands specific archive globs.
apps/sim/lib/uploads/archive.ts Adds shared archive listing and extraction helpers with zip safety guards.
apps/sim/lib/copilot/vfs/file-reader.ts Refactors file buffer rendering so archive entries reuse the existing read behavior.

Reviews (2): Last reviewed commit: "fix(chat): resolve uploads whose name co..." | Re-trigger Greptile

Comment thread apps/sim/lib/copilot/tools/handlers/upload-file-reader.ts
Comment thread apps/sim/lib/copilot/tools/handlers/vfs.ts
Comment thread apps/sim/lib/copilot/tools/handlers/upload-file-reader.ts
Accept .zip chat attachments and present each archive as a virtual folder the
agent lists and reads entry-by-entry. The archive is stored once; entries are
extracted lazily on read, reusing the existing file-parsers and zip-bomb /
zip-slip guards. No changes to the Go copilot service.

- allow zip in the attachment allowlist + chat accept attribute
- shared lib/uploads/archive.ts (factored from the file-manage decompress route)
- split readFileRecord into a pure renderFileBuffer reused for in-zip entries
- single-resolve readChatUploadPath/grepChatUploadPath dispatchers + VFS routing
- inline file tree in the upload context message
…ntries

Address review findings on the zip-upload feature:
- guard archive list/read/grep on record.size > MAX_ARCHIVE_BYTES before
  downloading, so an oversized zip is never buffered into memory
- a not-found archive entry now returns the file-tree manifest with a note
  (handles a stray /content habit suffix and typos) instead of failing
- de-duplicate archive entries that sanitize to the same path (./a/b vs a/b)
A name like test%2A.zip is exposed double-encoded by glob/upload-context
(test%252A.zip) but canonicalUploadKey decodes the input first, so a literal
%2A is indistinguishable from an encoded * and the lookup misses. Add an
encoded-form fallback (encode the stored name, compare to the raw input) which
recovers the row without affecting the U+202F normalization path.
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 5ea2699. Configure here.

const entries = await listChatUploadArchiveEntries(archiveSegment, context.chatId)
if (entries) {
files = [...files, ...entries.map((entry) => entry.vfsPath)]
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Archive glob ignores path pattern

Medium Severity

When executeVfsGlob expands an archive, it appends every entry vfsPath from listChatUploadArchiveEntries without checking them against the caller’s glob pattern. A pattern like uploads/bundle.zip/data/* still returns all files in the archive, not only those under data/, so the agent gets incorrect listings for nested archive globs.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 5ea2699. Configure here.

const entries = await listArchiveEntries(buffer)
return entries.map((path) => ({
path,
vfsPath: `uploads/${encodedZip}/${encodeEntryPath(path)}`,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Archive paths use wrong encoder

Low Severity

Archive entry vfsPath values and manifest read hints use canonicalUploadKey for the zip segment, while upload listing and upload context use per-segment encoding (encodeUploadSegment / encodeVfsSegment without decoding). For display names containing a literal %, glob shows uploads/test%252A.zip but expanded entries use uploads/test%2A.zip/…, producing inconsistent paths in one result set.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 5ea2699. Configure here.

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