fix(aws-sigv4): sign the bytes the transport sends for form/multipart bodies#401
Merged
Conversation
… bodies `signBody: true` hashed `JSON.stringify(body)` for every non-string body, but core's transport serialises `bodyType: 'form'` as application/x-www-form-urlencoded (URLSearchParams) and `'multipart'` as FormData. The signed x-amz-content-sha256 therefore never matched the sent bytes, so AWS returned 403 SignatureDoesNotMatch. Branch the payload hash on `req.bodyType` (which the engine threads onto the signed request and cloneReq preserves): - form: hash the exact URL-encoded bytes via a byte-for-byte mirror of core's encodeRequestBody, so payload signing works for the SQS/SNS/STS query protocol. - multipart: throw — the transport picks a non-deterministic boundary at send time, so the payload cannot be signed. The default (no signBody) still sends UNSIGNED-PAYLOAD. - json / unset: unchanged (JSON.stringify already matches the wire bytes). Add regression tests that fail before / pass after, and document the per-bodyType behaviour and the multipart restriction in the README and docs "Payload signing" sections. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The signed x-amz-content-sha256 must equal SHA-256 of the exact bytes the transport sends (insertion-order JSON.stringify on the same object), so a canonical/sorted "stable" object hash would itself cause SignatureDoesNotMatch. Guard against a well-meaning future refactor. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
5 tasks
Owner
Author
|
Follow-up for the architectural hardening (sign the transport's actual bytes instead of re-deriving them, so |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
awsSigV4()withsignBody: truehashedJSON.stringify(body)for every non-string body. But core's transport (encodeRequestBody,packages/core/src/http-adapter.ts) serialises bybodyType:bodyType: 'form'→application/x-www-form-urlencoded(URLSearchParams), e.g.a=1&b=two+wordsbodyType: 'multipart'→FormData(boundary generated by the transport at send time)So for a
form/multipartbody withsignBody: true, the signedx-amz-content-sha256was computed over the JSON encoding while the transport sent different bytes → AWS403 SignatureDoesNotMatch. The strategy's own doc-comment even asserted the JSON hash "must match what the transport sends" — silently violated.Found via a review-sweep dry-run; both premises verified against
aws-sigv4/src/index.tsandcore/src/http-adapter.ts. Correctness bug, not a vulnerability.Fix
Branch the payload hash on
req.bodyType(the engine threads it onto the signed request andcloneReqpreserves it):bodyTypesignBody: truebehaviourjson/ unsetformapplication/x-www-form-urlencodedencoding via a byte-for-byte mirror of core'sencodeRequestBody→ payload signing now works (SQS/SNS/STS query protocol)multipartsignBody) still sendsUNSIGNED-PAYLOAD, unchanged.Deliberately not mirroring "verbatim bytes for binary": core's default adapter
JSON.stringifys a non-string/non-form/non-multipart body, which the strategy already matches — so binary is not a mismatch, and hashing raw bytes would have introduced one.encodeRequestBodyis mirrored (not imported) because it isn't in the publicstitchapientry; the coupling is called out in a code comment.Tests
Added regression tests to
packages/aws-sigv4/test/sigv4.spec.tsthat fail before / pass after:form+signBody: truesigns the URL-encoded wire bytes (parity with the equivalent string body; ≠ the buggy JSON hash)multipart+signBody: truethrowsform/multipartwithoutsignBodystayUNSIGNED-PAYLOAD(guards against over-throwing)Docs
Rewrote the "Payload signing" section in the README and
apps/docs/content/docs/integrations/aws-sigv4.mdxto document the per-bodyTypebehaviour and the multipart restriction.Verification
Full pre-push gate green: format, lint, contract, typecheck, typecheck-d, test (whole tree), exports, build-docs (full Next build — validates the new docs link), yakir drift check. Package: 13/13 tests,
check:typesclean,tsupbuild clean.🤖 Generated with Claude Code