Skip to content

fix(site-deploy): forward the signed uploadHeaders on every PUT - #1985

Merged
pyramation merged 1 commit into
mainfrom
feat/site-deploy-forward-upload-headers
Nov 11, 2026
Merged

pyramation merged 1 commit into
mainfrom
feat/site-deploy-forward-upload-headers

Conversation

@pyramation

Copy link
Copy Markdown
Contributor

Summary

Since #1983 the presigned PUT URL is signed over x-amz-meta-upload-id. The presigned-url plugin hands the exact headers back as uploadHeaders, but @constructive-io/site-deploy (0.4.1) never selected them: its bulk mutation asked for uploadUrl fileId key deduplicated only and defaultPut sent just Content-Type + checksum. Every site deploy now fails against S3/RustFS:

S3 PUT failed with status 403: SignatureDoesNotMatch — missing signed header: x-amz-meta-upload-id

(seen in constructive-db's knative-e2e (2) / site-deploy-client.e2e.test.ts on the e0fcfb4 bump, constructive-db#4336).

Changes, no signing change:

  • buildBulkUploadMutation selects uploadHeaders.
  • diffCas carries uploadHeaders with each missing file and throws UPLOAD_FAILED when a non-deduplicated file comes back without them (no guessing headers).
  • PutObject gains a trailing headers?: Record<string, string>; uploadMissing passes them, and defaultPut forwards them to putToPresignedUrl(url, body, contentType, { signal, contentHash, headers }), which sends them as-is.

Tests: both mock servers (packages/site-deploy/__tests__/mock-api.ts, examples/site-deploy-ssg/__tests__/mock-server.ts) now return plugin-shaped uploadHeaders and reject a PUT that lacks the signed upload id, as S3 does. A new test checks that a payload without headers fails before any PUT or release write. Without the src change the site-deploy suite fails to compile and the SSG example fails 4/5; with it, 45/45 and 5/5.

Link to Devin session: https://app.devin.ai/sessions/bbd6fe90213a42bebc60256a795675b5
Open in Devin Desktop: https://app.devin.ai/desktop/session/bbd6fe90213a42bebc60256a795675b5?variant=devin
Requested by: @pyramation

@devin-ai-integration

Copy link
Copy Markdown
Contributor

I'll fix CI failures and address comments from users with write access. I'll skip comments containing "(aside)".

  • Disable automatic comment, CI, and merge conflict monitoring

@tenki-reviewer

tenki-reviewer Bot commented Nov 11, 2026 •

Copy link
Copy Markdown

Review complete. No blocking issues — approved ✅; 3 nitpicks below.

🧹 Nitpicks (3) — 🟢 3 low
  • 🟢 Mock signature check verifies only one signed header (mock-api.ts:187) — The mocks' assertSignedHeaders rejects a PUT only when x-amz-meta-upload-id is missing, despite its doc comment claiming it enforces "every signed header" (packages/site-deploy/tests/mock-api.ts:187).
  • 🟢 Deduplicate the signed-header test helpers (mock-server.ts:128) — The signedHeaders and assertSignedHeaders helpers are duplicated verbatim in two test mocks introduced by this change (examples/site-deploy-ssg/tests/mock-server.ts:128).
  • 🟢 Mock upload-id header value diverges from the real plugin (mock-api.ts:180) — Both mocks fabricate x-amz-meta-upload-id as file-<first 8 hash chars> (packages/site-deploy/tests/mock-api.ts:184), but the real plugin signs the header over the file row's id and the server integration tests assert x-amz-meta-upload-id: payload.fileId (graphql/server-test/tests/upload.integration.test.ts:431).

The change closes a signature-fidelity gap: the client previously PUT to a presigned URL without the headers the URL was signed over, so the implementation now selects uploadHeaders from the bulk mutation payload, passes it through CasDiff into putToPresignedUrl's new headers option, and threads a matching uploadHeaders parameter through the PutObject interface and both mock servers, with a new test asserting the PUT is rejected when signed headers are absent.

Files Change
packages/site-deploy/src/documents.ts, src/types.ts Select the new uploadHeaders field and extend BulkFilePayload/PutObject types to carry the signed header set.
packages/site-deploy/src/cas-upload.ts putToPresignedUrl accepts and sends the upload headers alongside the body.
packages/site-deploy/__tests__/mock-api.ts, examples/site-deploy-ssg/__tests__/mock-server.ts Mocks emit and enforce the signed-header contract, mirroring the real plugin's presigned headers.
packages/site-deploy/__tests__/deploy.test.ts Adds coverage for the unsigned-header rejection path and updated payload assertions.

Reviewed commit: b3573e3

@pyramation
pyramation merged commit 328378a into main Nov 11, 2026
21 checks passed
@pyramation
pyramation deleted the feat/site-deploy-forward-upload-headers branch November 11, 2026 04: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