expressjs/cors
Node.js CORS middleware
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".
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.
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)
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
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
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
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
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
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
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.
- 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)
- 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)]
- 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)
- 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)
- 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)
- 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]
- 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.
lib/index.jsPlain 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.
lib/index.jsObject 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.
lib/index.jsStandard 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
- Dynamic options callback (polling, balancing) — Trigger: Every incoming request when cors() was initialized with a function instead of a plain options object. Action: The options function is called with (req, callback); the callback receives either an error or a fresh CorsOptions object, which is then merged with defaults and used for that request only. Exit: Callback is called once per request; there is no retry — errors from the options function are forwarded to next(err).
Control Points
- origin (runtime-toggle) — Controls: Which browser origins are permitted. '*' allows all; a string allows exactly that origin; a RegExp or Array tests the request's Origin header; true reflects the request origin back (effectively allowing all but setting Vary); false blocks all cross-origin requests by suppressing the header. Default: '*' (default)
- preflightContinue (feature-flag) — Controls: Whether OPTIONS preflight requests are terminated by this middleware (false = middleware responds with optionsSuccessStatus and stops) or passed through to the application route handlers (true = headers are set but next() is called). Default: false (default)
- optionsSuccessStatus (runtime-toggle) — Controls: The HTTP status code sent in response to preflight OPTIONS requests. Default 204 (No Content); can be set to 200 for compatibility with IE11 and some SmartTV browsers that treat 204 as an error. Default: 204 (default)
- credentials (feature-flag) — Controls: Whether Access-Control-Allow-Credentials: true is added. When set, browsers will include cookies and Authorization headers in cross-origin requests. Note: cannot be combined with origin: '*' — browsers reject that combination. Default: undefined (omitted by default)
- maxAge (hyperparameter) — Controls: Seconds the browser should cache the preflight response (Access-Control-Max-Age header). Reduces the number of preflight round-trips for frequently accessed endpoints. Default: undefined (omitted by default, browser uses its own default)
Technology Stack
Runtime environment; the middleware hooks into Node's HTTP IncomingMessage/ServerResponse pipeline via the Express/Connect middleware contract
The host framework this middleware integrates with; cors() returns a standard (req, res, next) function that both Express and Connect recognize
Polyfill for Object.assign used to merge caller options with defaults without mutating either object
Safely appends 'Origin' to the response's Vary header without overwriting values set by other middleware
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
- cors (factory function) (factory) — The exported function. When called with options (or a dynamic options callback), it returns a ready-to-use Express middleware function. It handles two code paths: if options is a function, it calls that function with (req, callback) on every request and defers to corsWithOptions; otherwise it calls corsWithOptions directly with the merged static options.
lib/index.js - corsWithOptions (orchestrator) — The core middleware execution function. Receives the resolved CorsOptions and the Express (req, res, next) triplet. Decides whether the request is a CORS preflight (OPTIONS + Origin header present and preflightContinue=false) or a simple CORS request, assembles the correct set of HeaderDescriptors by calling each configurator, applies them via applyHeaders, and either terminates the preflight with the configured status code or calls next() to pass control to the application.
lib/index.js - configureOrigin (processor) — Decides the value of the Access-Control-Allow-Origin response header. Three branches: if origin is '*' or absent → sets header to '*'; if origin is a plain string → sets header to that fixed string and adds Vary: Origin; otherwise (RegExp, Array, boolean) → calls isOriginAllowed to test req.headers.origin and either reflects the request's origin back or sets value to false (suppressing the header), plus adds Vary: Origin.
lib/index.js - isOriginAllowed (validator) — Recursively checks whether a given request origin matches the configured allowedOrigin. Handles all four forms: Array (any element matches → true), string (exact equality), RegExp (regex test), or any other truthy value (treated as wildcard allow).
lib/index.js - applyHeaders (adapter) — Bridges the internal HeaderDescriptor array and the Node.js response object. Iterates over the collected headers; for each entry, if the value is not false, calls res.setHeader(key, value). This is the only function that writes to the HTTP response.
lib/index.js
Explore the interactive analysis
See the full architecture map, data flow, and code patterns visualization.
Analyze on CodeSeaRelated 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 Karolina Sarna.