Skip to content

fix(uploads): set Content-Type once on presigned PUTs; document x-goog-meta-folderid for GCS CORS - #6121

Merged
waleedlatif1 merged 2 commits into
stagingfrom
fix/gcs-upload-content-type
Jul 31, 2026
Merged

fix(uploads): set Content-Type once on presigned PUTs; document x-goog-meta-folderid for GCS CORS#6121
waleedlatif1 merged 2 commits into
stagingfrom
fix/gcs-upload-content-type

Conversation

@waleedlatif1

Copy link
Copy Markdown
Collaborator

Summary

  • Single-shot GCS uploads failed V4 signature verification (403). XMLHttpRequest.setRequestHeader appends on repeated calls (values join with a comma), and GCS is the only storage provider whose signed uploadHeaders include Content-Type — so the browser sent Content-Type: x, x while 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 default Content-Type only 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).
  • GCS CORS docs were missing x-goog-meta-folderid. Workspace uploads sign a folderId metadata header, and GCS matches preflight Access-Control-Request-Headers against the CORS responseHeader list 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

  • Bug fix

Testing

  • Two regression tests on the presigned PUT path: signed Content-Type present → header set exactly once with the signed value; absent (S3/Azure shape) → client default set exactly once
  • Both failure modes verified against Google's documentation (canonical-request header rules → 403 on mismatch; CORS responseHeader governs preflight allow-headers with exact matching)
  • Full uploads suite passes (202 tests), typecheck 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)

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

vercel Bot commented Jul 31, 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 Jul 31, 2026 5:16am

Request Review

@cursor

cursor Bot commented Jul 31, 2026

Copy link
Copy Markdown

PR Summary

Low Risk
Client upload header logic and documentation only; no auth or server signing changes, with targeted tests on the presigned PUT path.

Overview
Fixes GCS single-shot presigned PUT uploads that failed with 403 because the client always set Content-Type and then applied server uploadHeaders that already included it. Repeated setRequestHeader merges values, so the browser sent a doubled header that no longer matched the V4 signature. uploadViaPresignedPut now sets the file’s default Content-Type only when signed headers do not already provide one (case-insensitive); S3/Azure paths stay the same.

Self-hosting GCS CORS docs add x-goog-meta-folderid to the example responseHeader list so browser preflight allows folder workspace uploads that sign that metadata header.

Regression tests cover one Content-Type when present in uploadHeaders (GCS) and when omitted (Azure-style).

Reviewed by Cursor Bugbot for commit 3a62851. Configure here.

@greptile-apps

greptile-apps Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

Fixes presigned object-storage uploads and updates GCS configuration guidance.

  • Avoids setting Content-Type twice when signed upload headers already provide it.
  • Adds regression coverage for signed and default Content-Type behavior.
  • Documents x-goog-meta-folderid in the GCS CORS response-header list.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

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

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 3a62851. Configure here.

@waleedlatif1
waleedlatif1 merged commit 48aeac2 into staging Jul 31, 2026
27 checks passed
@waleedlatif1
waleedlatif1 deleted the fix/gcs-upload-content-type branch July 31, 2026 05:23
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