Skip to content

improvement(copilot): bound image decode work on the VFS file read path - #6421

Merged
waleedlatif1 merged 10 commits into
stagingfrom
vfs-image-read-limits
Aug 8, 2026
Merged

improvement(copilot): bound image decode work on the VFS file read path#6421
waleedlatif1 merged 10 commits into
stagingfrom
vfs-image-read-limits

Conversation

@waleedlatif1

Copy link
Copy Markdown
Collaborator

Summary

  • apply explicit size and pixel limits when preparing workspace images for vision, instead of running the decoder with its own limits turned off
  • enforce the source byte cap on the download itself, not just the recorded file size
  • 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
  • carry the specific rejection reason through to the read placeholder instead of a generic three-way message
  • pass the existing byte caps through on the text and document read paths too

Type of Change

  • Improvement

Testing

Added unit tests covering both new limits. Full lib/copilot and lib/uploads suites pass (1811 tests), type-check and lint clean.

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 Aug 8, 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 Aug 8, 2026 10:30pm

Request Review

@cursor

cursor Bot commented Aug 8, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Changes copilot VFS file read and image decode limits on a security-sensitive path (decompression bombs, memory/CPU), but behavior is heavily tested and mostly tightens existing processing rather than widening access.

Overview
Hardens workspace image reads for vision by enforcing source byte limits on the actual download (not only client-declared record.size), refusing oversized declared dimensions before the resize ladder, and restoring sharp’s pixel budget on decode/resize instead of limitInputPixels: false. Failed resize attempts advance to a smaller dimension rather than repeating the same decode at every quality step; HEIF above the WASM transcode ceiling is refused as “too large to decode,” not corrupt.

Introduces read-placeholders so size/binary/parse stand-ins carry placeholder: oversized | unreadable instead of being detected by message text. VFS read/grep then treat oversized refusals as tool errors, unreadable answers as normal content, and never mis-tag a real file whose text merely looks like a placeholder.

Text and document paths use the same capped fetch pattern; PayloadSizeLimitError from downloads is preserved for accurate observed-byte reporting in placeholders.

Reviewed by Cursor Bugbot for commit 50766d1. Configure here.

@greptile-apps

greptile-apps Bot commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR bounds image decoding and workspace-file downloads while replacing wording-based rejection detection with structured placeholder tags.

  • Enforces byte and pixel ceilings before expensive image processing.
  • Preserves authoritative size-limit errors across storage and VFS layers.
  • Propagates typed placeholder metadata through read and grep paths.
  • Adds regression coverage for oversized objects, decompression bombs, and real content resembling placeholder text.

Confidence Score: 5/5

The 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.

Important Files Changed

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

Comment thread apps/sim/lib/copilot/vfs/file-reader.ts Outdated
Comment thread apps/sim/lib/copilot/vfs/file-reader.ts
Comment thread apps/sim/lib/copilot/vfs/file-reader.ts Outdated
@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.

✅ Bugbot reviewed your changes and found no new issues!

2 issues from previous reviews remain unresolved.

Fix All in Cursor

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 04a225f. Configure here.

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

Comment thread apps/sim/lib/copilot/vfs/file-reader.ts Outdated
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

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

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

Comment thread apps/sim/lib/copilot/vfs/file-reader.ts Outdated
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

Comment thread apps/sim/lib/copilot/vfs/read-placeholders.ts Outdated

@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.

✅ 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.

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

Comment thread apps/sim/lib/copilot/vfs/read-placeholders.ts Outdated
Comment thread apps/sim/lib/copilot/vfs/read-placeholders.ts Outdated

@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.

✅ 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.

@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.

✅ 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.
@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.

✅ 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.

@waleedlatif1
waleedlatif1 merged commit 8a312a7 into staging Aug 8, 2026
30 checks passed
@waleedlatif1
waleedlatif1 deleted the vfs-image-read-limits branch August 8, 2026 22:45
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