aws-sdk-js-v3 icon indicating copy to clipboard operation
aws-sdk-js-v3 copied to clipboard

s3-request-presigner: `Content-Type` is not signed by default when specified

Open spencerwilson opened this issue 2 years ago • 2 comments

Describe the bug

The Content-Type header is unsigned by default.

Your environment

SDK version number

@aws-sdk/[email protected]

Is the issue in the browser/Node.js/ReactNative?

Node.js

Details of the browser/Node.js/ReactNative version

v12.22.1

Steps to reproduce

Create a presigned PUT URL like

const putObjectRequest: PutObjectRequest = {
  Bucket: 'uptrust-spencer-test',
  Key: 'my/cool/object.txt',
  ContentMD5: 'fTRbDyK9f+FJrC8Ulj2+4w==',
  ContentType: 'text/plain',
  ContentLength: 123,
};

const command = new PutObjectCommand(putObjectRequest);
const signedUrl = await getSignedUrl(s3Client, command, {
  expiresIn: 3600,
});

Observed behavior

The URL resulting from the config above has the query param

X-Amz-SignedHeaders=content-length%3Bcontent-md5%3Bhost

That is, three signed headers: content-length, content-md5, host.

Expected behavior

I expected those three, plus content-type. Rationale: Why would content-type be different than the other two content-_____ headers?

Screenshots

n/a

Additional context

Looks like content-type is excluded explicitly by default here

Workaround: Include content-type in signableHeaders, which will override the default omission:

const signedUrl = await getSignedUrl(s3Client, command, {
  expiresIn: 3600,
  // new; must be lower case!
  signableHeaders: new Set(['content-type']),
});

If there's a good reason for omitting it by default, could the default behavior + rationale be documented?

spencerwilson avatar Mar 30 '22 01:03 spencerwilson

Hi @spencerwilson, thanks for opening this issue. Some of the headers are not signed by default to prevent signature mismatch errors. However, you can specify which headers you would like to sign when calling getSignerUrl(). Please see my example below:

import {getSignedUrl} from "@aws-sdk/s3-request-presigner";
import {PutObjectCommand, S3Client} from "@aws-sdk/client-s3";

const s3Client = new S3Client({
    region: 'us-east-2'
});
const command = new PutObjectCommand({
    Bucket: 'BUCKET',
    Key: 'KEY',
    ContentMD5: 'MD5',
    ContentType: 'CONTENT-TYPE',
    ContentLength: 123
});
const headersMap = new Map();
headersMap.set('content-type', true);
const url = await getSignedUrl(s3Client, command, {
    expiresIn: 3600,
    signableHeaders: headersMap
});

console.log('URL: ', url);

And here we can see the signed headers:

X-Amz-SignedHeaders=content-length%3Bcontent-md5%3Bcontent-type%3Bhost

Thanks!

yenfryherrerafeliz avatar Aug 12 '22 19:08 yenfryherrerafeliz

If there's a good reason for omitting it by default, could the default behavior + rationale be documented? @spencerwilson I will bring this to discussion to the team to provide you a valid answer for this.

Thanks!

yenfryherrerafeliz avatar Aug 12 '22 19:08 yenfryherrerafeliz

Hi @spencerwilson, after doing some researches I found that that header "content-type" along with others are black listed by default to prevent signature mismatch errors. I could not find any public documentation but we can confirm that by checking other SDKs as JS V2 and PHP where we can see that content-type is defined as an unsignable header.

Thanks!

yenfryherrerafeliz avatar Aug 22 '22 15:08 yenfryherrerafeliz

This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs and link to relevant comments in this thread.

github-actions[bot] avatar Sep 10 '22 00:09 github-actions[bot]