ReferenceSecurity
@nextrush/cors

CORS

Security-hardened CORS middleware with presets and origin validation.

Browsers block cross-origin API requests by default. CORS middleware tells browsers which origins, methods, and headers your API accepts. Without it, frontend applications on different domains cannot reach your endpoints.

Source & internals

This middleware implements the full CORS specification with additional protections against null origin attacks, regex denial-of-service, and credential misconfigurations.

Default Behavior

With no options, cors() sets origin to false — no CORS headers are added and requests pass through unmodified. You must configure at least an origin value for the middleware to take effect.

The middleware always adds Vary: Origin for correct cache behavior, even when the request has no Origin header.

Installation

$ pnpm add @nextrush/cors

Minimal Usage

import { createApp } from '@nextrush/core';
import { cors } from '@nextrush/cors';

const app = createApp();

app.use(
  cors({
    origin: 'https://app.example.com',
  })
);

Configuration Options

CorsOptions

PropertyTypeDescription
originboolean | string | string[] | RegExp | OriginValidator= falseAllowed origin(s). false disables CORS, true reflects the request origin, '*' allows any origin.
methodsstring | string[]= 'GET,HEAD,PUT,PATCH,POST,DELETE'Allowed HTTP methods for preflight requests.
allowedHeaders?string | string[]Allowed request headers. If omitted, mirrors Access-Control-Request-Headers from the preflight.
exposedHeaders?string | string[]Response headers the browser can access from JavaScript.
credentialsboolean= falseAllow cookies and authorization headers in cross-origin requests.
maxAge?numberPreflight cache duration in seconds.
preflightContinueboolean= falsePass preflight requests to the next middleware instead of ending them.
optionsSuccessStatusnumber= 204HTTP status code for successful preflight responses.
privateNetworkAccessboolean= falseAdd Access-Control-Allow-Private-Network header for local/private network requests.
blockNullOriginboolean= trueBlock requests with Origin: null (from file://, data: URLs, sandboxed iframes).

Origin Types

Exact string — case-sensitive match:

cors({ origin: 'https://example.com' });

Array — whitelist of allowed origins:

cors({ origin: ['https://app.example.com', 'https://admin.example.com'] });

RegExp — pattern matching:

cors({ origin: /\.example\.com$/ });

Function — dynamic validation with async support:

cors({
  origin: async (origin, ctx) => {
    const allowed = await db.getAllowedOrigins(ctx.get('x-tenant-id'));
    return allowed.includes(origin);
  },
});

The validator receives the Origin header value and a CorsContext object. Return true to allow, false to deny, or a string to set a specific Access-Control-Allow-Origin value.

Presets

Choose a preset that matches your deployment scenario:

Allows any origin (*) without credentials. Use for public APIs or development.

import { simpleCors } from '@nextrush/cors';

app.use(simpleCors());

Requires an explicit origin. Enables credentials and blocks null origins. Caches preflight for 24 hours by default.

import { strictCors } from '@nextrush/cors';

app.use(strictCors('https://app.example.com'));

app.use(
  strictCors(['https://app.example.com', 'https://admin.example.com'], {
    exposedHeaders: ['X-Request-Id'],
  })
);

Allows localhost origins (127.0.0.1, ::1, *.localhost) with credentials and Private Network Access enabled. Null origins are allowed for file:// testing.

import { devCors } from '@nextrush/cors';

app.use(devCors());
app.use(devCors(['https://staging.example.com']));

Restricted to a list of internal service domains. Enables credentials and sets common internal headers.

import { internalCors } from '@nextrush/cors';

app.use(
  internalCors([
    'https://service1.internal.example.com',
    'https://service2.internal.example.com',
  ])
);

staticAssetsCors

Optimized for CDN and static file serving. Limits methods to GET, HEAD, OPTIONS. Caches preflight for 7 days. Defaults to '*' if no origins are specified.

import { staticAssetsCors } from '@nextrush/cors';

app.use(staticAssetsCors(['https://example.com', 'https://cdn.example.com']));

Options Builder

Build configuration with a fluent API:

import { createCorsOptions } from '@nextrush/cors';

const options = createCorsOptions()
  .allowOrigin(['https://example.com', 'https://app.example.com'])
  .allowMethods(['GET', 'POST'])
  .allowCredentials()
  .setMaxAge(86400)
  .build();

app.use(cors(options));

Integration Example

import { createApp } from '@nextrush/core';
import { cors } from '@nextrush/cors';

const app = createApp();

app.use(
  cors({
    origin: process.env.FRONTEND_URL,
    credentials: true,
    exposedHeaders: ['X-Request-Id'],
    maxAge: 86400,
  })
);

app.get('/api/users', (ctx) => ctx.json([{ id: 1, name: 'Alice' }]));

Common Mistakes

Wildcard with credentials

// ❌ Throws at configuration time
cors({ origin: '*', credentials: true });

// ✅ Use an explicit origin
cors({ origin: 'https://app.example.com', credentials: true });

Setting credentials: true with origin: '*' throws an error immediately. The CORS specification forbids this combination.

Wrong middleware order

// ❌ Auth rejects before CORS headers are set
app.use(authMiddleware);
app.use(cors({ origin: 'https://app.example.com' }));

// ✅ CORS runs first
app.use(cors({ origin: 'https://app.example.com' }));
app.use(authMiddleware);

Unsafe regex patterns

// ❌ ReDoS vulnerability — catastrophic backtracking
cors({ origin: /(.*)+\.example\.com/ });

// ✅ Safe pattern
cors({ origin: /\.example\.com$/ });

Advanced: building custom CORS logic

The source labels these exports "for advanced use cases" and "for custom implementations" — application code should reach for the presets and cors() options above first. These are exported for building your own CORS variant or unit-testing origin logic directly.

Origin validation

PropertyTypeDescription
isOriginAllowed(origin: string, option: OriginOption) => booleanCore matcher: whether an origin satisfies a configured OriginOption (string/array/RegExp/validator).
isOriginInList(origin: string, list: string[]) => booleanWhether origin exactly matches an entry in list.
isOriginMatchingPattern(origin: string, pattern: RegExp) => booleanWhether origin matches pattern, guarded by isRegexSafe.
isOriginSecure(origin: string) => booleanWhether an origin uses HTTPS (or is localhost, exempted for local dev).
isValidOriginFormat(origin: string) => booleanStructural validation of an Origin header value before any matching logic runs.
isRegexSafe(pattern: RegExp) => booleanReDoS guard — rejects patterns with catastrophic-backtracking risk before use.
createOriginCache() => Map<string, boolean>A cache the middleware uses internally to avoid re-validating the same origin on every request.
securityWarning(message: string) => voidThe internal dev-time console warning helper (e.g. for a risky origin+credentials combination).

Header helpers

PropertyTypeDescription
appendVary(ctx: Context, value: string) => voidAppends a value to the Vary header without clobbering existing values.
setVaryHeaders(ctx: Context) => voidSets the middleware's required Vary: Origin (and related) headers.
buildMethodList(methods: string | string[]) => stringNormalizes a methods option into the comma-joined header value.
normalizeHeaders(headers: string | string[]) => string[]Normalizes a headers option into an array form for comparison.
parseHeaderList(value: string) => string[]Parses a comma-separated header value (e.g. Access-Control-Request-Headers) into an array.
headerContains(list: string[], value: string) => booleanCase-insensitive membership check used when reflecting allowed headers.

Constants (CORS_HEADERS, DEFAULT_MAX_AGE, DEFAULT_METHODS, DEFAULT_OPTIONS_SUCCESS_STATUS, ORIGIN_HEADER, PREFLIGHT_INDICATORS, VARY_HEADER) are the literal header names and defaults the middleware itself uses — exported so a custom implementation stays consistent with NextRush's own naming rather than duplicating string literals.

Troubleshooting

No CORS headers in response — Verify the request includes an Origin header and that the origin matches your configuration. With origin: false (the default), no CORS headers are set.

Preflight fails — Check that the requested method and headers are allowed. If allowedHeaders is not set, the middleware reflects the request headers automatically.

Credentials rejected — Ensure origin is set to a specific value (not '*'). The browser requires an exact origin match when credentials are included.

Was this helpful?

On this page