Summary
The README's documented regex example for the origin option (/example\.com$/) matches unintended origins like evil-example.com, creating a CORS bypass when developers follow the documented pattern.
Reproduction
const cors = require('cors');
// Pattern from README line: "For example the pattern /example\.com$/ will reflect
// any request that is coming from an origin ending with 'example.com'."
const app = express();
app.use(cors({ origin: /example\.com$/ }));
Expected: Only example.com and its subdomains are allowed.
Actual: Any domain ending in example.com is allowed, including attacker-controlled domains.
https://example.com → allowed ✓ (intended)
https://sub.example.com → allowed ✓ (intended)
https://evil-example.com → allowed ✓ (UNINTENDED)
https://notexample.com → allowed ✓ (UNINTENDED)
The attacker registers evil-example.com → gets full CORS access to the API.
Root Cause
The regex /example\.com$/ only anchors the end ($) but not the beginning. Since browser Origin headers include the scheme (https://evil-example.com), the substring example.com matches anywhere after the scheme.
Interestingly, the test file (test/test.js:184) uses the correct pattern:
// From test file — properly anchored
{ origin: /:\/\/(.+\.)?example.com$/ }
This correctly rejects evil-example.com because :// anchors after the scheme, and (.+\.)? requires an optional subdomain preceded by a dot.
Impact
- Severity: Medium — CORS origin bypass via domain registration
- Scope: Any application that follows the README's regex example
- Likelihood: High — the README example is the first thing developers copy when configuring regex-based origin matching
- With
credentials: true: Attacker's site can make authenticated cross-origin requests and read responses
Suggested Fix
Option 1 (minimal): Update the README example to use the pattern from the test file:
- For example the pattern `/example\.com$/` will reflect any request
- that is coming from an origin ending with "example.com".
+ For example the pattern `/:\/\/(.+\.)?example\.com$/` will reflect
+ any request from "example.com" or its subdomains.
Option 2 (better): Add a security note to the origin configuration docs:
> **⚠️ Security note:** When using RegExp origins, always anchor both the scheme
> and domain boundary to prevent unintended matches. `/example\.com$/` matches
> `evil-example.com` — use `/:\/\/(.+\.)?example\.com$/` instead.
Option 3 (best): Add a runtime console.warn when a RegExp origin lacks scheme anchoring:
if (allowedOrigin instanceof RegExp && !allowedOrigin.source.includes(':\\/\\/')) {
console.warn('cors: RegExp origin %s may match unintended domains. ' +
'Consider anchoring after the scheme (e.g., /:\\/\\/(.+\\.)?example\\.com$/)', allowedOrigin);
}
Verification
// Insecure (current README example)
/example\.com$/.test('https://evil-example.com') // true — bypass!
// Secure (current test file pattern)
/:\/\/(.+\.)?example\.com$/.test('https://evil-example.com') // false — blocked
/:\/\/(.+\.)?example\.com$/.test('https://sub.example.com') // true — allowed
/:\/\/(.+\.)?example\.com$/.test('https://example.com') // true — allowed
Environment
- cors version: latest (reviewed from
master branch)
- Node.js: N/A (documentation issue)
Summary
The README's documented regex example for the
originoption (/example\.com$/) matches unintended origins likeevil-example.com, creating a CORS bypass when developers follow the documented pattern.Reproduction
Expected: Only
example.comand its subdomains are allowed.Actual: Any domain ending in
example.comis allowed, including attacker-controlled domains.The attacker registers
evil-example.com→ gets full CORS access to the API.Root Cause
The regex
/example\.com$/only anchors the end ($) but not the beginning. Since browserOriginheaders include the scheme (https://evil-example.com), the substringexample.commatches anywhere after the scheme.Interestingly, the test file (
test/test.js:184) uses the correct pattern:This correctly rejects
evil-example.combecause://anchors after the scheme, and(.+\.)?requires an optional subdomain preceded by a dot.Impact
credentials: true: Attacker's site can make authenticated cross-origin requests and read responsesSuggested Fix
Option 1 (minimal): Update the README example to use the pattern from the test file:
Option 2 (better): Add a security note to the
originconfiguration docs:Option 3 (best): Add a runtime console.warn when a RegExp origin lacks scheme anchoring:
Verification
Environment
masterbranch)