improvement(copilot): bound image decode work on the VFS file read path - #6421
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
PR SummaryMedium Risk Overview Introduces Text and document paths use the same capped fetch pattern; Reviewed by Cursor Bugbot for commit 50766d1. Configure here. |
Greptile SummaryThe PR bounds image decoding and workspace-file downloads while replacing wording-based rejection detection with structured placeholder tags.
Confidence Score: 5/5The PR appears safe to merge. The previously reported size-error and placeholder-classification failures are fixed at the current head, and no blocking failure remains.
|
| Filename | Overview |
|---|---|
| apps/sim/lib/copilot/vfs/file-reader.ts | Adds capped downloads, bounded image preparation, specific rejection reasons, and typed placeholder results. |
| apps/sim/lib/copilot/vfs/read-placeholders.ts | Centralizes generated read placeholders and classifies them using explicit metadata rather than user-visible wording. |
| apps/sim/lib/copilot/tools/handlers/vfs.ts | Uses structured placeholder metadata to distinguish oversized refusals from genuine file content. |
| apps/sim/lib/copilot/vfs/operations.ts | Prevents grep operations on tagged generated placeholders without misclassifying matching user content. |
| apps/sim/lib/uploads/contexts/workspace/workspace-file-manager.ts | Preserves payload-size errors so callers can return accurate oversized-file responses. |
| apps/sim/lib/uploads/server/heic.ts | Exposes the HEIC transcoder input ceiling for consistent preflight rejection. |
| apps/sim/lib/copilot/vfs/workspace-vfs.ts | Propagates placeholder metadata through dynamic workspace-file reads and compiled-artifact refusals. |
Reviews (10): Last reviewed commit: "fix(copilot): use sharp's own pixel defa..." | Re-trigger Greptile
eafbb35 to
04a225f
Compare
|
@cursor review |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
2 issues from previous reviews remain unresolved.
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 04a225f. Configure here.
04a225f to
c2e8091
Compare
|
@cursor review |
c2e8091 to
50c7c78
Compare
|
@cursor review |
50c7c78 to
15165ab
Compare
|
@cursor review |
15165ab to
3f9247c
Compare
|
@cursor review |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit d41a1dd. Configure here.
|
@cursor review |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 60d1696. Configure here.
60d1696 to
9ca96f4
Compare
|
@cursor review |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 9ca96f4. Configure here.
Apply explicit size and pixel limits when preparing workspace images for vision, and carry the specific rejection reason through to the read placeholder instead of a generic message. - cap the source image by byte size, enforced by the download itself - give sharp an explicit pixel budget instead of disabling its own - refuse oversized dimensions before the resize ladder runs - retry a failed resize at a smaller dimension rather than re-running the identical decode at each quality step - pass maxBytes on the text and document read paths too
The recorded size is client-declared, so the download's maxBytes is the check that actually holds. Breaching it threw past the placeholder and surfaced as a failed read on all three capped paths. Rethrow PayloadSizeLimitError unwrapped from fetchWorkspaceFileBuffer so callers can tell a size breach from a transport error, and answer with the same too-large placeholder the recorded-size check already returns.
…tion - track whether any resize rung produced an encode, rather than whether any threw: a rung that throws followed by rungs that encode but never fit was reported as undecodable, and the span outcome disagreed with the placeholder - format the rejected size with includeBytes, since formatFileSize collapses anything under 1KB to '0 Bytes' — which is every decompression bomb
…gate isOversizedReadPlaceholder tested for '[Image too large:', a prefix no code emitted — the image path's only placeholder was '[Image unavailable:'. The new source-cap placeholder is the first real one, so point the gate at it and fix the synthetic string in the test to match what the reader actually returns.
…eholders Follow-up review of the bounded image read. Three consistency defects and one shared-definition gap, none of which changed what the limits protect against. - derive the image source cap from the FormData upload ceiling instead of a number of its own. That cap exists for the same failure mode — a route holding an entire file in worker memory — so anything uploadable stays readable, and the tighter value was refusing images that read fine before for no gain - report a HEIF past the WebAssembly transcoder's own ceiling as a size refusal; it was falling through and telling the model the file could not be decoded - classify an oversized document like an oversized file or image. One of the three size refusals was reported to the model as a successful one-line read - report the observed size, not the recorded one, when the download cap trips — the recorded size is the figure that cap exists to distrust - move the read placeholders into one module that builds and matches them. Producers and matchers sat in four files and had already drifted twice - stop emitting trace outcomes absent from the generated contract, and derive the "vision limit" wording from the constant instead of restating it
The bytes are in hand by the time preparation fails, so quoting record.size back recreated the contradictory placeholder the download-cap path already fixed: a client-declared figure printed beside the real reason. Same defect, one line below the one it was fixed in.
…y claim Audit follow-up. No behaviour change; the code was right and the comments explaining it were not. Measured the bomb through this exact pipeline rather than reasoning about it (100MP/256MP/576MP/1024MP): libvips decodes sequentially, so peak RSS stays flat in the tens of MB no matter what the header declares. What scales is CPU, roughly linearly — ~240ms at 100MP, ~1.35s at 1024MP, once per resize rung. The pixel budget is a CPU bound, not the memory bound the comment described, and the "~400MB raster" arithmetic was wrong by an order of magnitude. - say that, with the measurements, so the next reader tunes against the real cost - stop claiming the source byte cap covers everything a user can upload: presigned and multipart accept gigabytes, so an image above it is stored fine and simply cannot be read inline. Deliberate trade, now stated as one - correct the resize-ladder comment, which asserted the failure is always a decode when the try also wraps the encoder - correct two claims in read-placeholders: the handler does pull the VFS, and the oversized set excludes an image size refusal it said it included Tests: cover the document and compiled-artifact size refusals (the document one was the newest behaviour change and had no coverage at all), the text and document download caps, and that the cap is handed to the download rather than merely producing the right message. Placeholder cases are built from the producers instead of hand-copied — a literal only proves the matcher agrees with the test, which is the drift this module exists to prevent.
isOversizedReadPlaceholder tested startsWith, so a real one-line file opening with one of those prefixes was returned to the model as a tool error instead of its contents. Every builder emits the same '(N bytes, limit M)]' tail, so requiring the full shape costs nothing and makes an accidental collision implausible. The pattern is built from the same prefix table, so it cannot drift from the producers.
…iffing its text Three rounds of review found the same defect in three places: a gate matching a prefix no producer emitted so it never fired, a real file misread as a refusal, a filename newline hiding a refusal. Each fix tightened the text matching and the next round found another way the text was the wrong thing to trust. So stop trusting it. `readPlaceholder.*` now returns the whole read result — content, totalLines, and a `placeholder` kind — and the two classifiers read the kind. A producer cannot emit a placeholder without tagging it, and no amount of user content can imitate one, so both failure directions are gone by construction rather than by a better pattern. `oversized` (the four size refusals) is reported as a tool error; `unreadable` (undecodable, binary, unparseable) is returned as content. The type flows through FileReadResult and ReadResult, so the compiler checks the tag survives to the gates rather than leaving a silent drop to be discovered later. Also removes the totalLines-of-1 heuristic, which was only ever a proxy for this.
…wer one The vulnerability was disabling sharp's pixel guard with limitInputPixels: false, so restoring the library's default is the fix; 100MP was a tidier number we picked, and tidy landed mid-market — a Fuji GFX 100 frame is 11648x8736, or 101.7MP, and was refused by a ceiling that claimed to clear every camera. 268402689 still removes the unbounded declaration the bug allowed and caps a rung near 400ms (measured; the curve is sublinear), while refusing nothing a camera produces. Tests now pin both ends: a 900MP bomb is refused, and 48MP / 61MP / 102MP frames are not.
9ca96f4 to
50766d1
Compare
|
@cursor review |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 50766d1. Configure here.

Summary
Type of Change
Testing
Added unit tests covering both new limits. Full
lib/copilotandlib/uploadssuites pass (1811 tests), type-check and lint clean.Checklist