Bug: Invalid CORS origin header sent with `sam local start-api`
- 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
Assessment
This issue has not been assessed yet.