Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(http-cors): Wrong Access-Control-Allow-Origin is returned with multiple origins on subsequent calls when returned headers are defined outside from handler #1263

Open
WtfJoke opened this issue Dec 12, 2024 · 6 comments
Labels

Comments

@WtfJoke
Copy link

WtfJoke commented Dec 12, 2024

Describe the bug

It sounds really odd and silly and I hope I dont oversee anything huge here....

However when we return headers (from type Record<string, string>) in our lambdaHandler, somehow middy seems to cache the cors headers origin (Access-Control-Allow-Origin) and on subsequent calls it returns always the same Access-Control-Allow-Origin header and doesn't respect the request Origin Header.

Examples:
Assuming we have two allowed origins configured: http://localhost:3000 and https://example.org.
Make sure the lambda is cold when changing/testing use cases.

Use case 1

  1. Request with Origin = https://example.org --> The lambda responds with Access-Control-Allow-Origin https://example.org 🥳
  2. Request with Origin = http://localhost:3000 --> The lambda responds with Access-Control-Allow-Origin https://example.org 😢
  3. Request with Origin = http://localhost:3000 --> The lambda responds with Access-Control-Allow-Origin https://example.org 😢
  4. ....

Use case 2

  1. Request with Origin = http://localhost:3000 --> The lambda responds with Access-Control-Allow-Origin http://localhost:3000 🥳
  2. Request with Origin = https://example.org --> The lambda responds with Access-Control-Allow-Origin http://localhost:3000 😢
  3. Request with Origin = https://example.org --> The lambda responds with Access-Control-Allow-Origin http://localhost:3000 😢
  4. ...

To Reproduce

  1. Sample code
import middy, { type MiddyfiedHandler } from "@middy/core";
import cors from "@middy/http-cors";
import type { APIGatewayProxyEvent, APIGatewayProxyResult } from "aws-lambda";

const CONTENT_TYPE_JSON_HEADER: Record<string, string> = {
    "Content-Type": "application/json",
};

const origins = ["http://localhost:3000", "https://example.org"];

const lambdaHandler = async (event: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> => {
    return {
        statusCode: 200,
        body: JSON.stringify({ hi: "there" }),
        headers: CONTENT_TYPE_JSON_HEADER,
    };
};
export const handler: MiddyfiedHandler<APIGatewayProxyEvent, APIGatewayProxyResult> = middy()
    .use(cors({ origins }))
    .handler(lambdaHandler);
  1. Inputs
    Input a)
{
  "headers": {
    "Origin": "http://localhost:3000"
  }
}

or
Input b)

{
  "headers": {
    "Origin": "https://example.org"
  }
}
  1. Make sure the lambda is cold (for example by adding/changing an env variable).
  2. Invoke the lambda with input a) first, then input b) (its possible to do it the other way around as described above in the use cases)
  3. Error: Allowed-Origin of input a) is returned

Expected behaviour
When we have two allowed origins "http://localhost:3000" and "https://example.org"
I would expect always the correct Access-Control-Allow-Origin is returned when requesting it with one of the origins.

Environment (please complete the following information):

  • Node.js: 22
  • Middy: 5.5.1 (middy 6.0.0 is affected as well if I tested correctly)
  • AWS SDK: 3.709.0

Additional context
The error is not reproducable when we inline the CONTENT_TYPE_JSON_HEADER variable (or define the variable inside of the lambdaHandler method).
E.g returning

 return {
        statusCode: 200,
        body: JSON.stringify({ hi: "there" }),
        headers: { "Content-Type": "application/json" },
    };

will NOT result in a wrong behaviour.

image
Left is working, right is not working

@WtfJoke WtfJoke added the bug label Dec 12, 2024
@WtfJoke WtfJoke changed the title (http-cors): Wrong Access-Control-Allow-Origin is returned with multiple origins on subsequent calls when returning headers from type record (http-cors): Wrong Access-Control-Allow-Origin is returned with multiple origins on subsequent calls when returned headers are defined outside from handler Dec 12, 2024
@willfarrell
Copy link
Member

In you above use cases your Origin is https://localhost:3000 while in your config you allow http://localhost:3000. Is the missing/extra s a typo or the root cause of the issue?

@WtfJoke
Copy link
Author

WtfJoke commented Dec 13, 2024

Is the missing/extra s a typo or the root cause of the issue?

Sorry, its a typo, I corrected it 🙈

@willfarrell
Copy link
Member

I added in this unit test, and it passed.

test('It should return whitelisted origin (static & localhost)', async (t) => {
  const handler = middy((event, context) => ({ statusCode: 200 }))

  handler.use(
    cors({
      disableBeforePreflightResponse: false,
      origins: ['http://localhost:3000', 'https://example.com']
    })
  )

  const event = {
    httpMethod: 'OPTIONS',
    headers: { Origin: 'http://localhost:3000' }
  }

  const response = await handler(event, context)

  deepEqual(response, {
    statusCode: 204,
    headers: {
      'Access-Control-Allow-Origin': 'http://localhost:3000',
      Vary: 'Origin'
    }
  })
})

@WtfJoke
Copy link
Author

WtfJoke commented Dec 14, 2024

Thank you for taking the time to look into this.
I wasnt able to reproduce it locally either with a unit test, but please deploy the lambda and test it there. I've verified it with an e2e test.

@willfarrell
Copy link
Member

Can you share the entire event you're seeing in your e2e test? Put that into the unit test. The only thing coming to mind is there is something else in the event having an effect, or something in the build process that's causing the issue.

@WtfJoke
Copy link
Author

WtfJoke commented Dec 14, 2024

Input a) and b) are the minimal events I used in the lambda test console with the sample code from above (originally the lambda is attached to an API Gateway but I wanted to exclude as many factors as possible).

If you want I can build a sample cdk project with the e2e test. Would that help? Alternatively I can provide you a lambda zip with the bundled code if you prefer that.

or something in the build process that's causing the issue.

Unlikely, initially I made the screenshot with the diff on the resulting javascript, the diff looked the same.

EDIT: I just saw that in the response something odd is happening additionally. For each invocation , Origin is added to the "Vary" response header. Full example response:

{
  "statusCode": 200,
  "body": "{\"hi\":\"there\"}",
  "headers": {
    "Content-Type": "application/json",
    "Access-Control-Allow-Origin": "https://example.org",
    "Vary": "Origin, Origin, Origin, Origin, Origin, Origin, Origin, Origin"
  }
}

EDIT2: I've set up a cdk demo project here: https://github.com/WtfJoke/middy_cors_1263

WtfJoke added a commit to WtfJoke/middy_cors_1263 that referenced this issue Dec 14, 2024
WtfJoke added a commit to WtfJoke/middy_cors_1263 that referenced this issue Dec 14, 2024
WtfJoke added a commit to WtfJoke/middy_cors_1263 that referenced this issue Dec 14, 2024
WtfJoke added a commit to WtfJoke/middy_cors_1263 that referenced this issue Dec 14, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Development

No branches or pull requests

2 participants