From c18b9d265b7703de160411de92d32a830e35080f Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 29 Jul 2026 09:08:17 +0700 Subject: [PATCH] fix: preserve trailing slash in SigV4 canonical URI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit AWS SDK includes trailing slash in the canonical URI for bucket operations (e.g. PUT /bucket-name/). My earlier 'fix' that stripped trailing slashes broke SigV4 signature verification. The trailing slash is intentional per AWS SigV4 — only dot-segments are removed, not trailing slashes. Re-verified with @smithy/signature-v4: path /bucket-name/ produces the client signature, while /bucket-name does not match. Co-Authored-By: Claude Opus 5 (1M context) --- src/utils/s3/auth.ts | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/src/utils/s3/auth.ts b/src/utils/s3/auth.ts index 381f3f4..d3a3f9b 100644 --- a/src/utils/s3/auth.ts +++ b/src/utils/s3/auth.ts @@ -166,9 +166,9 @@ const normalizeUri = (uri: string): string => { result.push(segment); } - // Reconstruct path (no trailing slash except root) + // Reconstruct path — preserved as-is (SigV4 includes trailing slashes) const normalized = result.length > 0 ? `/${result.join('/')}` : '/'; - return normalized === '/' ? '/' : normalized.replace(/\/+$/, ''); + return normalized; }; const awsEncode = (value: string): string => @@ -331,22 +331,7 @@ export const verifySignature = async ( const signingKey = await getSigningKey(s3SecretKey, dateStamp, region); const expectedSignature = await hmacHex(signingKey, stringToSign); - // TEMP DEBUG: log signature mismatch details if (!timingSafeCompare(expectedSignature, parsed.signature)) { - const debugInfo = { - method, - uri: canonicalUri, - canReq: canonicalRequest.slice(0, 400), - hashedCR: hashedCanonicalRequest, - amzDate, - dateStamp, - scope: credentialScope, - stringToSign: stringToSign.slice(0, 300), - expectedSig: expectedSignature, - receivedSig: parsed.signature, - accessKey: parsed.accessKey, - }; - console.error('SIGV4_MISMATCH:' + JSON.stringify(debugInfo)); return { isValid: false, credential: null, errorCode: 'SignatureDoesNotMatch' }; }