expressjs/cors

Node.js CORS middleware

6,195 stars JavaScript 5 components

9 hidden assumptions · 7-stage pipeline · 5 components

Like any codebase, this library makes assumptions it never checks — most are routine. The ones worth your attention are below, in plain language with what to do about each.

Adds CORS headers to HTTP responses so browsers allow cross-origin requests

A browser sends an HTTP request (or a preflight OPTIONS request) to an Express server with this middleware installed. The cors() factory has already captured the options at setup time. When the request arrives, the middleware reads req.method and req.headers.origin, resolves the effective CorsOptions (calling the dynamic options function if one was provided), and runs each configurator function to produce an array of {key, value} header descriptors. applyHeaders writes those descriptors to the response via res.setHeader. For preflight requests (OPTIONS method, non-preflightContinue mode), the middleware ends the response immediately with the configured status code (default 204). For all other requests, it calls next() so the application handler runs normally — the CORS headers are already set on the response object and will be sent when the handler eventually calls res.send/res.json.

Under the hood, the system uses 1 feedback loop, 5 control points to manage its runtime behavior.

A 5-component library. 6 files analyzed. Data flows through 7 distinct pipeline stages.

Hidden Assumptions

Most of what this code assumes is routine. These 2 are the ones most likely to cause trouble here — in plain terms, with what to do about each. The rest are minor; they're under "Show everything".

Worth your attention first

If you turn on the 'include cookies / login credentials' option while also leaving the 'who can access this' setting at its default open-to-everyone value, browsers will refuse every request that tries to send a cookie or login token — silently. The server looks like it's working fine; the browser quietly throws away the response. This is one of the most common CORS traps and nothing in the library warns you.

What to do: If you need to allow cookies or auth headers in cross-origin requests, set the allowed-origins option to a specific domain or list of domains rather than leaving it at the default open-to-everyone setting.

Worth your attention first

If you configure this library to decide CORS rules on-the-fly by calling your own code (for example, looking up a database to see if an origin is approved), and that code either crashes or never finishes, the incoming browser request will either crash the server or hang forever — with no timeout and no error message to the caller.

What to do: Wrap your dynamic-origin lookup in error handling and always make sure it calls back within a reasonable time limit, even if the lookup fails.

Show everything (7 more)
Domain

If you accidentally pass the wrong type of value for the 'who is allowed' setting — for example a number or an object — the library won't complain; it will either quietly allow everyone in or quietly block everyone, depending on whether the value is 'truthy'. You won't get an error message to tell you something is wrong.

What to do: Double-check that your origin setting is a string, a list of strings, or a regular expression pattern — anything else will silently behave in unexpected ways.

lib/index.js:configureOrigin
Contract

The part of the library that builds the list of allowed HTTP methods uses a duck-typing shortcut that can be fooled by unusual inputs. If you pass a non-standard object as the methods setting, the header sent to browsers could contain garbage text, causing all preflight checks to fail silently.

What to do: Always pass the list of allowed methods as either a plain comma-separated string or a simple array of strings.

lib/index.js:configureMethods
Ordering

The library assumes that setting response headers and then ending the response always works in the right order. In some unusual server environments — for example HTTP/2 adapters or certain testing setups — headers set this way might be silently ignored, so preflight responses arrive empty and browsers block the real request.

What to do: If you are running this library outside of a standard Express or Connect server, verify that your response object follows the same header-setting behavior as a normal Node.js HTTP response.

lib/index.js:corsWithOptions
Domain

Whatever origin label a browser (or anyone making a raw HTTP request) sends, the library copies it directly into the response without checking whether it looks like a real web address. On older server versions this could be exploited to inject extra content into the response; on modern servers the platform will reject the bad value with an error.

What to do: Make sure your server is running a recent version of Node.js (version 14 or newer) which automatically rejects malformed header values, and consider using an explicit allowlist rather than reflecting arbitrary origin values.

lib/index.js:configureOrigin
Scale

If you accidentally pass a deeply nested list-within-a-list as your allowed origins setting, the library will follow every level of nesting until it runs out of stack space and crashes. Flat lists of allowed origins work fine.

What to do: Keep your allowed origins as a simple flat list of strings or patterns, not a list of lists.

lib/index.js:isOriginAllowed
Environment

This library is built specifically for Express-style servers. If you plug it into a different kind of server framework — for example a serverless function host or an alternative Node.js framework — the response object may not have the right shape, and headers will either silently not be set or the whole thing will crash.

What to do: Use this library only with Express or Connect; for other frameworks, look for a CORS package built specifically for that framework.

lib/index.js:corsWithOptions
Temporal

If you build a list of allowed origins, pass it to this library, and then later add or remove items from that same list in your own code, the changes will immediately affect which origins the running server allows — without restarting or reconfiguring anything. This can be surprising and hard to trace.

What to do: Treat the options you pass to this library as final; if you need to change allowed origins at runtime, use the dynamic callback form instead of mutating the original list.

lib/index.js:cors

Open the standalone hidden-assumptions report for cors →

How Data Flows Through the System

A browser sends an HTTP request (or a preflight OPTIONS request) to an Express server with this middleware installed. The cors() factory has already captured the options at setup time. When the request arrives, the middleware reads req.method and req.headers.origin, resolves the effective CorsOptions (calling the dynamic options function if one was provided), and runs each configurator function to produce an array of {key, value} header descriptors. applyHeaders writes those descriptors to the response via res.setHeader. For preflight requests (OPTIONS method, non-preflightContinue mode), the middleware ends the response immediately with the configured status code (default 204). For all other requests, it calls next() so the application handler runs normally — the CORS headers are already set on the response object and will be sent when the handler eventually calls res.send/res.json.

  1. Receive and normalize options — When the application calls cors(options), the factory merges the provided options with defaults using object-assign: { origin: '*', methods: 'GET,HEAD,PUT,PATCH,POST,DELETE', preflightContinue: false, optionsSuccessStatus: 204 }. If options is a function (dynamic mode), the merge is deferred to per-request time. The merged object is captured in the middleware closure. [CorsOptions → CorsOptions] (config: defaults.origin, defaults.methods, defaults.preflightContinue +1)
  2. Inspect request for CORS relevance — On each incoming request, corsWithOptions checks req.headers.origin. If no Origin header is present, the request is not a cross-origin browser request and next() is called immediately without setting any headers. This avoids polluting same-origin responses. [IncomingRequest (req)]
  3. Detect preflight vs. simple request — If req.method === 'OPTIONS' AND options.preflightContinue is false, the request is treated as a CORS preflight — a browser dry-run before sending a 'complex' request (e.g. POST with JSON or a custom header). The middleware will terminate this request after setting headers. If preflightContinue is true or the method is not OPTIONS, the request flows through to the application handler. [IncomingRequest (req)] (config: options.preflightContinue)
  4. Build origin header descriptor — configureOrigin(options, req) reads options.origin. Wildcard ('*') → Access-Control-Allow-Origin: *. Fixed string → Access-Control-Allow-Origin: <that string> + Vary: Origin. RegExp/Array/boolean → calls isOriginAllowed(req.headers.origin, options.origin); if allowed, reflects req.headers.origin back as the header value; if denied, sets value to false (header is suppressed). Vary: Origin is always added in the non-wildcard cases so caches don't serve the wrong origin's response. [IncomingRequest (req) → HeaderDescriptor] (config: options.origin)
  5. Build method, credentials, and cache-control header descriptors — For preflight requests: configureMethods joins options.methods into a comma-separated string → Access-Control-Allow-Methods. configureAllowedHeaders uses options.allowedHeaders or falls back to reflecting the request's Access-Control-Request-Headers value back. configureMaxAge outputs Access-Control-Max-Age if options.maxAge is set (tells the browser how long to cache the preflight result). configureCredentials outputs Access-Control-Allow-Credentials: true if options.credentials is true. For simple (non-preflight) requests: only configureCredentials and configureExposedHeaders are run. [CorsOptions → HeaderDescriptor] (config: options.methods, options.allowedHeaders, options.maxAge +2)
  6. Apply headers to response — applyHeaders(headers, res) iterates the flat array of HeaderDescriptor objects collected from all configurators. For each entry where value !== false, it calls res.setHeader(key, value). The vary package is used separately via vary(res, 'Origin') to correctly append to an existing Vary header rather than overwriting it. [HeaderDescriptor]
  7. Terminate preflight or continue to application — If this was a preflight request (OPTIONS + preflightContinue=false): res.writeHead(options.optionsSuccessStatus) and res.end() are called, returning the response immediately with status 204 (or 200 if configured). The application route handler never runs. For all other requests: next() is called, passing control to the next middleware or route handler with CORS headers already attached to the res object. (config: options.optionsSuccessStatus, options.preflightContinue)

Data Models

The data structures that flow between stages — the contracts that hold the system together.

CorsOptions lib/index.js
Plain JS object with: origin (string | RegExp | Array | boolean | function), methods (string | Array<string>), allowedHeaders (string | Array<string>), exposedHeaders (string | Array<string>), credentials (boolean), maxAge (number), preflightContinue (boolean), optionsSuccessStatus (number). Defaults: { origin: '*', methods: 'GET,HEAD,PUT,PATCH,POST,DELETE', preflightContinue: false, optionsSuccessStatus: 204 }
Created by the calling application and passed to cors(). Merged with defaults via object-assign at middleware-creation time. Never mutated (the options object is frozen-safe). If a function is passed as options, it is called on every request to produce a fresh CorsOptions object.
HeaderDescriptor lib/index.js
Object with key: string (e.g. 'Access-Control-Allow-Origin') and value: string | false. A value of false signals that this header should be skipped (e.g. origin was not allowed).
Created inside each configurator function (configureOrigin, configureMethods, etc.), collected into a flat array by the middleware, then consumed by applyHeaders which calls res.setHeader for each entry where value is not false.
IncomingRequest (req) lib/index.js
Standard Node.js/Express IncomingMessage with: req.method (string), req.headers.origin (string), req.headers['access-control-request-headers'] (string, present on preflight).
Provided by Express on every request. The middleware reads req.method to detect preflight (OPTIONS) and req.headers.origin to decide which origin header to reflect back.

System Behavior

How the system operates at runtime — where data accumulates, what loops, what waits, and what controls what.

Feedback Loops

Control Points

Technology Stack

Node.js (runtime)
Runtime environment; the middleware hooks into Node's HTTP IncomingMessage/ServerResponse pipeline via the Express/Connect middleware contract
Express / Connect (framework)
The host framework this middleware integrates with; cors() returns a standard (req, res, next) function that both Express and Connect recognize
object-assign (library)
Polyfill for Object.assign used to merge caller options with defaults without mutating either object
vary (library)
Safely appends 'Origin' to the response's Vary header without overwriting values set by other middleware
Mocha + supertest (testing)
Test framework (Mocha) and HTTP assertion layer (supertest) used in the test suite to spin up real Express apps and assert on response headers

Key Components

Explore the interactive analysis

See the full architecture map, data flow, and code patterns visualization.

Analyze on CodeSea

Related Library Repositories

Frequently Asked Questions

What is cors used for?

Adds CORS headers to HTTP responses so browsers allow cross-origin requests expressjs/cors is a 5-component library written in JavaScript. Data flows through 7 distinct pipeline stages. The codebase contains 6 files.

How is cors architected?

cors is organized into 3 architecture layers: Public API / Middleware Factory, Header Configurators, Header Application. Data flows through 7 distinct pipeline stages. This layered structure keeps concerns separated and modules independent.

How does data flow through cors?

Data moves through 7 stages: Receive and normalize options → Inspect request for CORS relevance → Detect preflight vs. simple request → Build origin header descriptor → Build method, credentials, and cache-control header descriptors → .... A browser sends an HTTP request (or a preflight OPTIONS request) to an Express server with this middleware installed. The cors() factory has already captured the options at setup time. When the request arrives, the middleware reads req.method and req.headers.origin, resolves the effective CorsOptions (calling the dynamic options function if one was provided), and runs each configurator function to produce an array of {key, value} header descriptors. applyHeaders writes those descriptors to the response via res.setHeader. For preflight requests (OPTIONS method, non-preflightContinue mode), the middleware ends the response immediately with the configured status code (default 204). For all other requests, it calls next() so the application handler runs normally — the CORS headers are already set on the response object and will be sent when the handler eventually calls res.send/res.json. This pipeline design reflects a complex multi-stage processing system.

What technologies does cors use?

The core stack includes Node.js (Runtime environment; the middleware hooks into Node's HTTP IncomingMessage/ServerResponse pipeline via the Express/Connect middleware contract), Express / Connect (The host framework this middleware integrates with; cors() returns a standard (req, res, next) function that both Express and Connect recognize), object-assign (Polyfill for Object.assign used to merge caller options with defaults without mutating either object), vary (Safely appends 'Origin' to the response's Vary header without overwriting values set by other middleware), Mocha + supertest (Test framework (Mocha) and HTTP assertion layer (supertest) used in the test suite to spin up real Express apps and assert on response headers). A focused set of dependencies that keeps the build manageable.

What system dynamics does cors have?

cors exhibits 1 feedback loop, 5 control points. The feedback loops handle polling. These runtime behaviors shape how the system responds to load, failures, and configuration changes.

What design patterns does cors use?

4 design patterns detected: Middleware factory pattern, Configurator pipeline with descriptor objects, Vary header correctness via the vary package, Options immutability via defensive copy.

Analyzed on June 9, 2026 by CodeSea. Written by .