Uh oh!
There was an error while loading. Please reload this page.
fix(uploads): set Content-Type once on presigned PUTs; document x-goog-meta-folderid for GCS CORS - #6121
Conversation
…g-meta-folderid in the GCS CORS example XMLHttpRequest.setRequestHeader appends on repeated calls (values join with a comma), and GCS is the only provider whose signed uploadHeaders include Content-Type — so single-shot GCS uploads sent 'x, x', which fails V4 signature verification with 403 (headers canonicalize to a comma-separated value that must match what was signed; multipart part PUTs are unaffected since part URLs don't sign Content-Type). The client now sets its default Content-Type only when the server's signed headers don't already carry one, with regression tests for both paths. Also adds x-goog-meta-folderid to the documented GCS CORS responseHeader list — workspace uploads now sign a folderId metadata header, and GCS CORS matches preflight request headers against that list exactly (no wildcards), so the missing entry blocked browser uploads into folders.
The latest updates on your projects. Learn more about Vercel for GitHub. |
PR SummaryLow Risk Overview Self-hosting GCS CORS docs add Regression tests cover one Reviewed by Cursor Bugbot for commit 3a62851. Configure here. |
Greptile SummaryFixes presigned object-storage uploads and updates GCS configuration guidance.
Confidence Score: 5/5The PR appears safe to merge. No blocking failure remains.
|
| Filename | Overview |
|---|---|
| apps/sim/lib/uploads/client/direct-upload.ts | Uses a case-insensitive header-name check to ensure presigned PUT requests set Content-Type exactly once. |
| apps/sim/lib/uploads/client/direct-upload.test.ts | Adds regression tests for both server-supplied and client-defaulted Content-Type headers. |
| apps/docs/content/docs/en/platform/self-hosting/object-storage.mdx | Adds the folder metadata header required by workspace uploads to the documented GCS CORS configuration. |
Reviews (2): Last reviewed commit: "chore(uploads): drop inline comment" | Re-trigger Greptile
waleedlatif1
commented
Jul 31, 2026
waleedlatif1
commented
Jul 31, 2026
@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 3a62851. Configure here.
Uh oh!
There was an error while loading. Please reload this page.
Summary
XMLHttpRequest.setRequestHeaderappends on repeated calls (values join with a comma), and GCS is the only storage provider whose signeduploadHeadersincludeContent-Type— so the browser sentContent-Type: x, xwhile the URL was signed for the single value. Per Google's canonical-request rules, headers canonicalize to a comma-separated value that must match what was signed exactly, so the doubled value 403s. The client now sets its defaultContent-Typeonly when the server's signed headers don't already carry one (case-insensitive check); S3/Azure behavior is unchanged, and multipart part PUTs were never affected (part URLs don't sign Content-Type).x-goog-meta-folderid. Workspace uploads sign afolderIdmetadata header, and GCS matches preflightAccess-Control-Request-Headersagainst the CORSresponseHeaderlist exactly (no wildcards) — so the missing entry blocked browser uploads into folders. Added to the documented list; a sweep of every metadata key passed to presigned-upload generation confirms the list is now complete (originalname, uploadedat, purpose, userid, workspaceid, workflowid, executionid, folderid).Type of Change
Testing
Content-Typepresent → header set exactly once with the signed value; absent (S3/Azure shape) → client default set exactly onceresponseHeadergoverns preflight allow-headers with exact matching)Checklist