Skip to content

fix(uploads): bound multipart body read in workspace-file and knowledge-document upload routes - #5413

Merged
waleedlatif1 merged 3 commits into
stagingfrom
fix-bound-upload-formdata
Jul 4, 2026
Merged

fix(uploads): bound multipart body read in workspace-file and knowledge-document upload routes#5413
waleedlatif1 merged 3 commits into
stagingfrom
fix-bound-upload-formdata

Conversation

@waleedlatif1

Copy link
Copy Markdown
Collaborator

Summary

  • workspace files upload route and v1 knowledge document upload route now read the multipart body through the existing readFormDataWithLimit helper instead of raw request.formData(), matching the pattern already used by files/upload and v1/files
  • existing post-parse size checks are kept as-is (defense in depth)

Type of Change

  • Bug fix

Testing

Added route tests covering: declared content-length over the limit rejected immediately, chunked/no-content-length body over the limit rejected once the streaming cap trips, and a normal under-limit upload still succeeding. Ran the full route test suites, typecheck, biome, and bun run check:api-validation — all pass.

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

vercelBot commented Jul 4, 2026

Copy link
Copy Markdown

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

1 Skipped Deployment
ProjectDeploymentActionsUpdated (UTC)
docsSkippedSkippedJul 4, 2026 7:16pm

Request Review

@cursor

cursorBot commented Jul 4, 2026

Copy link
Copy Markdown

PR Summary

Low Risk
Hardens upload parsing against oversized bodies without changing auth or storage logic; existing post-parse size checks remain as defense in depth.

Overview
Workspace file upload (POST /api/workspaces/[id]/files) and v1 knowledge document upload (POST /api/v1/knowledge/[id]/documents) now parse multipart bodies with readFormDataWithLimit instead of unbounded request.formData(), aligned with files/upload and v1/files.

Caps use the existing file size limits plus a shared MAX_MULTIPART_OVERHEAD_BYTES (1MB) exported from stream-limits; duplicate local constants were removed from those other upload routes. Oversized bodies return 413 with the limit error message; invalid multipart still returns 400. Per-file size checks after parse are unchanged.

New route tests cover declared Content-Length over the cap, chunked bodies without Content-Length that trip the stream cap, and successful under-limit uploads.

Reviewed by Cursor Bugbot for commit f8f83b8. Configure here.

@greptile-apps

greptile-appsBot commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes two upload routes — workspaces/[id]/files and v1/knowledge/[id]/documents — that were reading multipart bodies via the unbounded request.formData(), making them vulnerable to oversized payloads. Both now go through the existing readFormDataWithLimit helper, matching the pattern already in files/upload and v1/files. As a prerequisite, MAX_MULTIPART_OVERHEAD_BYTES is lifted out of each call site and exported from stream-limits.ts as a single source of truth.

  • Replaced request.formData() with readFormDataWithLimit in both new routes, with 413 responses on overflow and 400 on malformed multipart.
  • Centralized MAX_MULTIPART_OVERHEAD_BYTES = 1 MB into stream-limits.ts and updated all four upload routes to import it from there.
  • Added route-level test suites for both updated routes, covering declared-content-length rejection, chunked-body overflow, and successful under-limit uploads.

Confidence Score: 5/5

Safe to merge — the change is a targeted, well-tested hardening of two upload routes with no behavioral changes outside the new overflow guard.

Both routes now share the same bounded-read helper already proven in the two sibling upload routes. The centralized constant eliminates drift, the error handling is consistent (413 on overflow, 400 on malformed multipart), and the existing post-parse file-size checks are kept intact. New test suites cover all three important cases for each route with no gaps in the logic.

No files require special attention.

Important Files Changed

FilenameOverview
apps/sim/lib/core/utils/stream-limits.tsExports the new MAX_MULTIPART_OVERHEAD_BYTES constant (1 MB) so all upload routes share a single definition; no logic changes to existing helpers.
apps/sim/app/api/workspaces/[id]/files/route.tsReplaces unbounded request.formData() with readFormDataWithLimit; adds 413/400 error handling and imports MAX_MULTIPART_OVERHEAD_BYTES from the shared module. Existing post-parse size check is retained for defense-in-depth.
apps/sim/app/api/v1/knowledge/[id]/documents/route.tsSame bounded-read fix as the workspace route; MAX_FILE_SIZE + MAX_MULTIPART_OVERHEAD_BYTES caps the 100 MB knowledge-document limit correctly, and the secondary file.size guard is preserved.
apps/sim/app/api/files/upload/route.tsMinor cleanup only: removes the now-duplicated local MAX_MULTIPART_OVERHEAD_BYTES declaration and imports it from stream-limits.
apps/sim/app/api/v1/files/route.tsSame import-consolidation cleanup as files/upload; no behavioral change.
apps/sim/app/api/v1/knowledge/[id]/documents/route.test.tsNew test suite with three cases: declared content-length rejection, chunked-body overflow via pull-based stream, and successful under-limit upload.
apps/sim/app/api/workspaces/[id]/files/route.test.tsNew test suite mirroring the knowledge-document tests; mocks MAX_WORKSPACE_FORMDATA_FILE_SIZE to 1 KB to keep threshold low and tests realistic, all three cases covered.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant Client
participant Route as Upload Route (POST)
participant Helper as readFormDataWithLimit
participant Native as request.formData()
participant Stream as readStreamToBufferWithLimit
Client->>Route: POST (multipart body)
Route->>Route: Auth / permissions check
Route->>Helper: "readFormDataWithLimit(request, { maxBytes })"
Helper->>Helper: assertContentLengthWithinLimit()
alt content-length present
alt "content-length > maxBytes"
Helper-->>Route: throw PayloadSizeLimitError
Route-->>Client: 413 payload too large
else content-length within limit
Helper->>Native: request.formData()
Native-->>Helper: FormData
end
else no content-length + body present
Helper->>Stream: readStreamToBufferWithLimit(body, maxBytes)
alt "streamed bytes > maxBytes"
Stream-->>Helper: throw PayloadSizeLimitError
Helper-->>Route: throw
Route-->>Client: 413 payload too large
else within limit
Stream-->>Helper: Buffer
Helper->>Native: new Request(...).formData()
Native-->>Helper: FormData
end
end
Helper-->>Route: FormData
Route->>Route: Post-parse file.size check (defense-in-depth)
Route->>Route: Upload / index file
Route-->>Client: 200 success
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
participant Client
participant Route as Upload Route (POST)
participant Helper as readFormDataWithLimit
participant Native as request.formData()
participant Stream as readStreamToBufferWithLimit
Client->>Route: POST (multipart body)
Route->>Route: Auth / permissions check
Route->>Helper: "readFormDataWithLimit(request, { maxBytes })"
Helper->>Helper: assertContentLengthWithinLimit()
alt content-length present
alt "content-length > maxBytes"
Helper-->>Route: throw PayloadSizeLimitError
Route-->>Client: 413 payload too large
else content-length within limit
Helper->>Native: request.formData()
Native-->>Helper: FormData
end
else no content-length + body present
Helper->>Stream: readStreamToBufferWithLimit(body, maxBytes)
alt "streamed bytes > maxBytes"
Stream-->>Helper: throw PayloadSizeLimitError
Helper-->>Route: throw
Route-->>Client: 413 payload too large
else within limit
Stream-->>Helper: Buffer
Helper->>Native: new Request(...).formData()
Native-->>Helper: FormData
end
end
Helper-->>Route: FormData
Route->>Route: Post-parse file.size check (defense-in-depth)
Route->>Route: Upload / index file
Route-->>Client: 200 success
Loading

Reviews (4): Last reviewed commit: "fix(uploads): share MAX_MULTIPART_OVERHE..." | Re-trigger Greptile

Comment threadapps/sim/app/api/v1/knowledge/[id]/documents/route.ts Outdated
@waleedlatif1

Copy link
Copy Markdown
CollaboratorAuthor

Fixed — MAX_MULTIPART_OVERHEAD_BYTES is now exported once from @/lib/core/utils/stream-limits and all four upload routes (files/upload, v1/files, workspaces/[id]/files, v1/knowledge/[id]/documents) import it instead of redeclaring locally.

@waleedlatif1

Copy link
Copy Markdown
CollaboratorAuthor

@greptile review

1 similar comment
@waleedlatif1

Copy link
Copy Markdown
CollaboratorAuthor

@greptile review

@waleedlatif1

Copy link
Copy Markdown
CollaboratorAuthor

@cursor review

@cursorcursorBot 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 f8f83b8. Configure here.

@waleedlatif1

Copy link
Copy Markdown
CollaboratorAuthor

@greptile review

@waleedlatif1

Copy link
Copy Markdown
CollaboratorAuthor

@cursor review

@cursorcursorBot 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 f8f83b8. Configure here.

@waleedlatif1
waleedlatif1 merged commit 1a371e5 into stagingJul 4, 2026
18 checks passed
@waleedlatif1
waleedlatif1 deleted the fix-bound-upload-formdata branch July 4, 2026 22:32
Sign up for freeto 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

@waleedlatif1