Seting up CORS on a S3 Bucket¶
CORS, or Cross-Origin Resource Sharing, is a security feature built into your web browser that decides whether to allow a website to access resources from a different website. Think of it as your browser's own security guard, making sure you don't receive potentially dangerous or unauthorized content.
What CORS does¶
Imagine you're on a website, let's say my-party.com
. A script on this site wants to grab some data from another website, their-party.com
.
-
The Request: Your browser sends a request to
their-party.com
asking for the data. The request includes a special "Origin" note that says, "Hey, I'm from my-party.com." -
The Server's Response: The server at
their-party.com
receives the request. The server doesn't block it; it just responds with the data as requested. However, it also adds an extra header, a little stamp of approval, that lists which websites are allowed to use this data. This stamp is called theAccess-Control-Allow-Origin
header. -
The Browser's Decision: This is the most crucial step. When the response arrives back, your browser looks at that stamp of approval.
If the stamp says my-party.com
is on the allowed list, your browser says, "Okay, this is safe," and passes the data on to the website to use.
If the stamp is missing, or it lists a different website, your browser's security guard immediately steps in. It will block the data from being used by my-party.com and throw a CORS error.
The key point is that the server didn't deny the request; your browser did. The server just didn't provide the necessary permission slip for the data to be used by an outside source.
When is CORS Needed¶
This browser-enforced rule is triggered anytime a website tries to access a resource from a different origin. An origin is defined by the combination of the protocol (http or https), the domain (example.com), and the port number (80 or 443).
For example, your browser will trigger a CORS check if https://example.com tries to access data from:
http://example.com (❌ Different protocol)
https://api.example.com (❌ Different subdomain)
https://example.com:8080 (❌ Different port)
In all these cases, the browser will look for that special approval stamp from the server. Without it, the browser will block the resource, protecting you from potentially malicious cross-site data theft.
How CORS works¶
-
Same-Origin Policy: By default, browsers prevent web pages from making requests to a different origin (domain, scheme, or port) than the one it was loaded from.
-
CORS Mechanism: CORS introduces HTTP headers that allow a server to explicitly list which other origins are allowed to access its resources.
-
Preflight Requests: For certain requests, the browser performs a "preflight" request to the server to check if the actual request is allowed. This preflight request uses the HTTP OPTIONS method to ask the server if the subsequent request is permitted.
-
Server Response: If the server's response includes the necessary CORS headers (like Access-Control-Allow-Origin) and permits the origin, the browser allows the actual request to proceed; otherwise, it blocks it.
What does a CORS Policy look like¶
Below is a simple CORS policy in json
{
"CORSRules": [
{
"AllowedOrigins": ["http://www.example.com"],
"AllowedHeaders": ["*"],
"AllowedMethods": [
"GET"
],
"MaxAgeSeconds": 3000,
"ExposeHeaders": []
}
]
}
Below is a simple CORS policy in xml
<CORSConfiguration>
<CORSRule>
<AllowedOrigin>http://www.example.com</AllowedOrigin>
<AllowedMethod>GET</AllowedMethod>
<AllowedHeader>*</AllowedHeader>
</CORSRule>
</CORSConfiguration>
You can apply this to your bucket with s3cmd like this (s3cmd ONLY accepts xml formatted CORS policies)
You can view the buckets CORS policy with s3cmd like this
You can apply this to your bucket with the awscli like this
You can view your buckets current cors policy with awscli like this
Common pitfalls¶
Not specifying the tenant ID in the URL for the resource¶
CORS preflight requests are not authenticated so you must provide the tennant ID in the URL that is provided to the users browser for the requested resource. This is different than many other s3 providers but allows you the freedom to name your bucket without worrying about another user taking the bucket name you wanted.
URLs for resources should look like this
For example:
Some common CORS policy mistakes include using overly permissive rules, not accounting for preflight requests, and misconfiguring the AllowedHeaders or ExposeHeaders values.
Using Overly Permissive Policies¶
One of the most frequent mistakes is using a wildcard (*) for AllowedOrigin, which essentially disables a key security feature of CORS. While this is simple to set up, it can make your bucket or API vulnerable. If an attacker can get a user to visit a malicious website, they can make cross-origin requests to your resource and potentially steal sensitive data if your authentication is handled via cookies or other credentials. A better practice is to explicitly list the domains that need access, for example:
Misunderstanding Preflight Requests¶
Many developers don't realize that some complex CORS requests require a separate "preflight" OPTIONS request before the actual request is sent. This preflight request checks with the server to see if the main request is safe to send.
Incorrect AllowedHeaders or ExposeHeaders¶
Headers are a frequent source of CORS errors.
-
AllowedHeaders
: This specifies which headers the browser can include in a preflight request. If you send a request with a custom header (likeX-My-Custom-Header
) but haven't listed it inAllowedHeaders
, the preflight request will fail. Many developers use a wildcard * to avoid this, which is again, less secure. -
ExposeHeaders
: This is often forgotten. By default, browsers only give your JavaScript access to a limited set of response headers (likeContent-Type
). If your server sends a custom response header (e.g.,X-My-Response-Id
) that your code needs to read, you must explicitly list it in the ExposeHeaders section of your policy. If you don't, the browser will receive the header but hide it from your script, leading to silent failures in your application.
Specifying the Wrong Protocol or Port¶
An origin is the combination of the protocol, domain, and port. A common mistake is to only specify the domain.
-
http://www.example.com is a different origin from https://www.example.com.
-
https://www.example.com:8080 is a different origin from https://www.example.com.
If your web application is served over HTTPS, but your CORS policy only allows HTTP, all your requests will fail with a CORS error. Always ensure the protocol and port in your policy match the origin of your application.
Testing your CORS policy¶
A Quick way to test your CORS policy is to use the curl command like this
curl -i -X OPTIONS -H "Origin: http://www.example.com" -H "Access-Control-Request-Method: GET" "${URL}"
Your should see a "200 OK" response that includes the CORS headers if your CORS policy is correct.
HTTP/1.1 200 OK
Date: Wed, 03 Sep 2025 20:24:29 GMT
Content-Length: 0
Connection: keep-alive
Access-Control-Allow-Origin: http://www.example.com
Vary: Origin
Access-Control-Allow-Methods: GET
Access-Control-Max-Age: 3000
x-amz-request-id: tx0000086b4cac3f4073e5f-0068b8a3fd-47539710-cle9-local
Strict-Transport-Security: max-age=31536000;includeSubDomains;preload