From 36da723ff6fbe07e3af028279212c5320fbef558 Mon Sep 17 00:00:00 2001 From: Luis Rodriguez Date: Wed, 24 Jun 2026 20:21:45 +0000 Subject: [PATCH 1/2] docs: warn about dangerous origin:true + credentials:true combination --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index bd6ebbc1..1a53c9ca 100644 --- a/README.md +++ b/README.md @@ -219,7 +219,7 @@ app.listen(80, function () { ## Configuration Options * `origin`: Configures the **Access-Control-Allow-Origin** CORS header. Possible values: - - `Boolean` - set `origin` to `true` to reflect the [request origin](https://datatracker.ietf.org/doc/html/draft-abarth-origin-09), as defined by `req.header('Origin')`, or set it to `false` to disable CORS. + - `Boolean` - set `origin` to `true` to reflect the [request origin](https://datatracker.ietf.org/doc/html/draft-abarth-origin-09), as defined by `req.header('Origin')`, or set it to `false` to disable CORS. **Avoid combining `origin: true` with `credentials: true`** (see the `credentials` option below). - `String` - set `origin` to a specific origin. For example, if you set it to - `"http://example.com"` only requests from "http://example.com" will be allowed. - `"*"` for all domains to be allowed. @@ -230,6 +230,8 @@ app.listen(80, function () { * `allowedHeaders`: Configures the **Access-Control-Allow-Headers** CORS header. Expects a comma-delimited string (ex: 'Content-Type,Authorization') or an array (ex: `['Content-Type', 'Authorization']`). If not specified, defaults to reflecting the headers specified in the request's **Access-Control-Request-Headers** header. * `exposedHeaders`: Configures the **Access-Control-Expose-Headers** CORS header. Expects a comma-delimited string (ex: 'Content-Range,X-Content-Range') or an array (ex: `['Content-Range', 'X-Content-Range']`). If not specified, no custom headers are exposed. * `credentials`: Configures the **Access-Control-Allow-Credentials** CORS header. Set to `true` to pass the header, otherwise it is omitted. + + > **Security note:** Do not combine `credentials: true` with `origin: true`. When `origin` is `true`, this package reflects whatever origin the browser sends back in `Access-Control-Allow-Origin`. Paired with `Access-Control-Allow-Credentials: true`, this means *any* website can make credentialed (cookie-bearing) requests to your server and read the responses — equivalent to opening your API to the entire web with full session access. Use an explicit allowlist (`origin: ['https://app.example.com']`) instead. * `maxAge`: Configures the **Access-Control-Max-Age** CORS header. Set to an integer to pass the header, otherwise it is omitted. * `preflightContinue`: Pass the CORS preflight response to the next handler. * `optionsSuccessStatus`: Provides a status code to use for successful `OPTIONS` requests, since some legacy browsers (IE11, various SmartTVs) choke on `204`. From 2ca379afb3f02fab9f85ac4f1f884f3cc86241e8 Mon Sep 17 00:00:00 2001 From: Luis Rodriguez Date: Thu, 25 Jun 2026 08:13:41 -0400 Subject: [PATCH 2/2] docs: add dynamic origin function example near credentials warning --- README.md | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 1a53c9ca..046c9751 100644 --- a/README.md +++ b/README.md @@ -231,7 +231,21 @@ app.listen(80, function () { * `exposedHeaders`: Configures the **Access-Control-Expose-Headers** CORS header. Expects a comma-delimited string (ex: 'Content-Range,X-Content-Range') or an array (ex: `['Content-Range', 'X-Content-Range']`). If not specified, no custom headers are exposed. * `credentials`: Configures the **Access-Control-Allow-Credentials** CORS header. Set to `true` to pass the header, otherwise it is omitted. - > **Security note:** Do not combine `credentials: true` with `origin: true`. When `origin` is `true`, this package reflects whatever origin the browser sends back in `Access-Control-Allow-Origin`. Paired with `Access-Control-Allow-Credentials: true`, this means *any* website can make credentialed (cookie-bearing) requests to your server and read the responses — equivalent to opening your API to the entire web with full session access. Use an explicit allowlist (`origin: ['https://app.example.com']`) instead. + > **Security note:** Do not combine `credentials: true` with `origin: true`. When `origin` is `true`, this package reflects whatever origin the browser sends back in `Access-Control-Allow-Origin`. Paired with `Access-Control-Allow-Credentials: true`, this means *any* website can make credentialed (cookie-bearing) requests to your server and read the responses — equivalent to opening your API to the entire web with full session access. Use an explicit allowlist (`origin: ['https://app.example.com']`) instead, or a function for dynamic lists (env-specific, tenant-specific, etc.): + + ```js + app.use(cors({ + origin: function (origin, callback) { + const allowlist = process.env.ALLOWED_ORIGINS.split(','); + if (!origin || allowlist.includes(origin)) { + callback(null, true); + } else { + callback(new Error('Not allowed by CORS')); + } + }, + credentials: true + })); + ``` * `maxAge`: Configures the **Access-Control-Max-Age** CORS header. Set to an integer to pass the header, otherwise it is omitted. * `preflightContinue`: Pass the CORS preflight response to the next handler. * `optionsSuccessStatus`: Provides a status code to use for successful `OPTIONS` requests, since some legacy browsers (IE11, various SmartTVs) choke on `204`.