diff --git a/src/aws-signature.ts b/src/aws-signature.ts index e52831d..30b094b 100644 --- a/src/aws-signature.ts +++ b/src/aws-signature.ts @@ -64,6 +64,12 @@ async function createCanonicalRequest(request: Request, isQueryAuth: boolean): P if (port && !((url.protocol === "https:" && port === "443") || (url.protocol === "http:" && port === "80"))) { headerValue += `:${port}`; } + } else if (headerName === "accept-encoding") { + // Cloudflare's edge rewrites the incoming Accept-Encoding value before the Worker + // sees it, so the literal value can never be recovered here. S3 SDKs that sign this + // header (aws-sdk-go, used by rclone/mc) always set it to "identity" beforehand, since + // S3 doesn't support transparent content-encoding on object bodies. + headerValue = "identity"; } else { headerValue = request.headers.get(headerName)?.trim() ?? ""; } diff --git a/test/s3.test.ts b/test/s3.test.ts index f5bd170..559a16f 100644 --- a/test/s3.test.ts +++ b/test/s3.test.ts @@ -215,6 +215,16 @@ describe("S3 compatibility", () => { expect(await missing.text()).toContain("NoSuchKey"); }); + it("verifies signatures that include Accept-Encoding even when the delivered value differs (Cloudflare rewrites it in transit)", async () => { + const original = await signed("/test-bucket/ae.txt", { method: "PUT", body: "hello", headers: { "accept-encoding": "identity" } }); + const rewrittenHeaders = new Headers(original.headers); + rewrittenHeaders.set("accept-encoding", "gzip, deflate, br"); + const mutated = new Request(original, { headers: rewrittenHeaders }); + + const response = await worker.fetch(mutated, ENV, CTX); + expect(response.status).toBe(200); + }); + it("decodes both aws-chunked framing variants across arbitrary boundaries", async () => { const payload = bytes(70_013); for (const trailer of [true, false]) {