From b7a3d6dac31292bde731a87a001c65ba7d013e2e Mon Sep 17 00:00:00 2001 From: George Fu Date: Fri, 17 Oct 2025 14:51:17 -0400 Subject: [PATCH] fix(cloudfront-signer): encode uri components in base url --- packages/cloudfront-signer/package.json | 1 + packages/cloudfront-signer/src/sign.spec.ts | 18 ++++++++++++++++++ packages/cloudfront-signer/src/sign.ts | 17 +++++++++++++++-- yarn.lock | 1 + 4 files changed, 35 insertions(+), 2 deletions(-) diff --git a/packages/cloudfront-signer/package.json b/packages/cloudfront-signer/package.json index 9d4f442f4e3b..e4e8e1bfca18 100644 --- a/packages/cloudfront-signer/package.json +++ b/packages/cloudfront-signer/package.json @@ -23,6 +23,7 @@ }, "license": "Apache-2.0", "dependencies": { + "@smithy/core": "^3.16.1", "@smithy/url-parser": "^4.2.2", "tslib": "^2.6.2" }, diff --git a/packages/cloudfront-signer/src/sign.spec.ts b/packages/cloudfront-signer/src/sign.spec.ts index 3327827bdd74..69edcf909310 100644 --- a/packages/cloudfront-signer/src/sign.spec.ts +++ b/packages/cloudfront-signer/src/sign.spec.ts @@ -810,3 +810,21 @@ describe("getSignedUrl- when signing a URL with a date range", () => { expect(verifySignature(signatureQueryParam, policyStr)).toBeTruthy(); }); }); + +describe("url component encoding", () => { + it("should use extended encoding for query params in the base URL", () => { + const url = + "https://d111111abcdef8.cloudfront.net/private-content/private.jpeg?q=!@#$%^&*()&image-description=aws's image&'''&!()=5"; + const signedUrl = getSignedUrl({ + url: url, + keyPairId, + privateKey, + dateLessThan: "2026-01-01", + }); + + const target = + "https://d111111abcdef8.cloudfront.net/private-content/private.jpeg?q=%21%40%23%24%25%5E&%2A%28%29=&image-description=aws%27s%20image&%27%27%27=&%21%28%29=5"; + + expect(signedUrl.slice(0, target.length)).toBe(target); + }); +}); diff --git a/packages/cloudfront-signer/src/sign.ts b/packages/cloudfront-signer/src/sign.ts index 6d742216843b..c323ea346c22 100644 --- a/packages/cloudfront-signer/src/sign.ts +++ b/packages/cloudfront-signer/src/sign.ts @@ -1,3 +1,4 @@ +import { extendedEncodeURIComponent } from "@smithy/core/protocols"; import { createSign } from "crypto"; /** @@ -139,9 +140,21 @@ export function getSignedUrl({ const startFlag = baseUrl!.includes("?") ? "&" : "?"; const params = Object.entries(cloudfrontSignBuilder.createCloudfrontAttribute()) .filter(([, value]) => value !== undefined) - .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`) + .map(([key, value]) => `${extendedEncodeURIComponent(key)}=${extendedEncodeURIComponent(value)}`) .join("&"); - const urlString = baseUrl + startFlag + params; + + function encodeBaseUrlQuery(url: string) { + if (url.includes("?")) { + const [hostAndPath, query] = url.split("?"); + const params = [...new URLSearchParams(query).entries()] + .map(([key, value]) => `${extendedEncodeURIComponent(key)}=${extendedEncodeURIComponent(value)}`) + .join("&"); + return `${hostAndPath}?${params}`; + } + return url; + } + + const urlString = encodeBaseUrlQuery(baseUrl!) + startFlag + params; return getResource(urlString); } diff --git a/yarn.lock b/yarn.lock index 6b7fa90aba53..7e020f465eac 100644 --- a/yarn.lock +++ b/yarn.lock @@ -23331,6 +23331,7 @@ __metadata: version: 0.0.0-use.local resolution: "@aws-sdk/cloudfront-signer@workspace:packages/cloudfront-signer" dependencies: + "@smithy/core": "npm:^3.16.1" "@smithy/url-parser": "npm:^4.2.2" "@tsconfig/recommended": "npm:1.0.1" concurrently: "npm:7.0.0"