aws / aws/aws-sam-cli

Bug: Invalid CORS origin header sent with `sam local start-api`

Open
#7,848 5 comments 0 reactions 0 assignees View on GitHub
stage/needs-investigation
Dominant language
Python
Stars
6.7k
Forks
1.2k
Avg merge
1d 10h
Merged PRs (30d)
52

Description

### Description:

When a browser makes a request to a service I have running locally using `sam local start-api` and I have the `Cors` property configured for the `AWS::Serverless::Api` resource, the local service sends the exact value of `Cors.AllowOrigin` (which is required to be in this `"'http://localhost:3000, https://someservice.dev'"` format) as the `Access-Control-Allow-Origin` header, which the browser (Chrome in this case) reports as an invalid value:

> ```Access to fetch at 'http://127.0.0.1:8080/sentry' from origin 'http://localhost:3000' has been blocked by CORS policy: The 'Access-Control-Allow-Origin' header contains multiple values 'https://*.someservice.dev, https://someservice.com', but only one is allowed. Have the server send the header with a valid value, or, if an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.```

My function code returns these exact headers with every response:

```json
"headers": {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Headers": "*",
"Access-Control-Allow-Methods": "POST",
"Content-Type": "application/json",
}
```

which are being used with the deployed service, because my live service returns `"Access-Control-Allow-Origin": "*"` instead of the multi-value one.

### Steps to reproduce:

SAM template:

```yml
AWSTemplateFormatVersion: "2010-09-09"
Transform: AWS::Serverless-2016-10-31
Parameters:
Env:
Description: Environment name
Type: String
AllowedValues:
- dev
- staging
- prod
Default: dev
Name:
Description: Resource name, used as a prefix for most resources.
Type: String
Default: sentry-tunnel
AllowedOrigin:
Description: CORS allowed origin(s)
Type: String
Default: "https://*.someservice.dev, https://someservice.com"

Mappings:
Domain:
dev:
Name: metrics.dev.someservice.dev
HostedZone: someservice.dev.
staging:
Name: metrics.staging.someservice.dev
HostedZone: staging.someservice.dev.
prod:
Name: metrics.someservice.com
HostedZone: someservice.com.

Resources:
Api:
Type: AWS::Serverless::Api
Properties:
Name: !Ref Name
StageName: release
BinaryMediaTypes:
- "*~1*"
Cors:
AllowOrigin: !Sub "'${AllowedOrigin}'"
AllowHeaders: "'*'"
AllowMethods: "'POST'"
Domain:
BasePath: /
DomainName:
Fn::FindInMap:
- Domain
- !Ref Env
- Name
CertificateArn: "{{resolve:ssm:/someservice/metrics/certificate-arn}}"
Route53:
HostedZoneName:
Fn::FindInMap:
- Domain
- !Ref Env
- HostedZone

SentryTunnelFunc:
Type: AWS::Serverless::Function
Properties:
FunctionName: !Sub "${Name}-handler"
CodeUri: ./
Handler: lambda.handler
Runtime: python3.12
Architectures:
- x86_64
Role: !GetAtt LambdaRole.Arn
Events:
PostEvent:
Type: Api
Properties:
RestApiId: !Ref Api
Path: /sentry
Method: POST
Environment:
Variables:
ENV: !Ref Env

LambdaRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub "${Name}-handler"
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
Action:
- sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
```

Python function that returns this http response:

```py
{
"statusCode": status_code,
"headers": {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Headers": "*",
"Access-Control-Allow-Methods": "POST",
"Content-Type": "application/json",
},
"body": json.dumps(body),
}
```

Build and run:

```sh
sam build --parallel
sam local start-api --port 8080
```

Make an appropriate request to `http://127.0.0.1:8080/sentry` from the browser.

### Observed result:

The request results in status 200, but the browser console shows the errors:

> ```Access to fetch at 'http://127.0.0.1:8080/sentry' from origin 'http://localhost:3000' has been blocked by CORS policy: The 'Access-Control-Allow-Origin' header contains multiple values 'https://*.someservice.dev, https://someservice.com', but only one is allowed. Have the server send the header with a valid value, or, if an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.```

> ```POST http://127.0.0.1:8080/sentry net::ERR_FAILED 200 (OK)```

### Expected result:

The `Access-Control-Allow-Origin` header should reflect the same as the live service (`"Access-Control-Allow-Origin": "*"` in this case), which would prevent the errors in the browser.

### Additional environment details (Ex: Windows, Mac, Amazon Linux etc)

Output of `sam --info`:

```json
{
"version": "1.132.0",
"system": {
"python": "3.11.10",
"os": "macOS-15.2-x86_64-i386-64bit"
},
"additional_dependencies": {
"docker_engine": "27.4.0",
"aws_cdk": "Not available",
"terraform": "1.10.2"
},
"available_beta_feature_env_vars": [
"SAM_CLI_BETA_FEATURES",
"SAM_CLI_BETA_BUILD_PERFORMANCE",
"SAM_CLI_BETA_TERRAFORM_SUPPORT",
"SAM_CLI_BETA_RUST_CARGO_LAMBDA"
]
}
```

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.