@samveys/security

CSRF guard (Fetch Metadata + tokens), CORS, rate limiting

Version
1.0.2Nov 5, 2026
Install
pnpm add @samveys/security
Import
import { cors, corsPreflight, csrf, csrfToken, rateLimit, security, … } from '@samveys/security'

Function (6)

cors Function

CORS with exact origin matching. A preflight reaching this middleware (for example on a route you mounted for `OPTIONS`) is answered 204 with the allow headers. Other responses to an allowed origin get `Access-Control-Allow-Origin` and `Vary: Origin` (plus `Allow-Credentials` when set). A disallowed origin gets no `Access-Control-*` header at all, so the browser blocks the script.

function cors(options: CorsOptions): Middleware;

Exported from packages/security/src/cors.ts

corsPreflight Function

Answers a CORS preflight (`OPTIONS` with `Access-Control-Request-Method`) from an allowed origin with 204 and the allow headers; returns `undefined` for anything else, so a disallowed origin gets no `Access-Control-*` header. Router-less: the `security()` plugin runs it in the `http:request` hook, because the router has no `OPTIONS` routes.

function corsPreflight(options: CorsOptions): (ctx: Pick<HttpContext, 'method' | 'headers'>) => Response | undefined;

Exported from packages/security/src/cors.ts

csrf Function

CSRF guard from Fetch Metadata, `Origin`, and an optional form token. State-changing requests (anything but GET, HEAD, OPTIONS) are checked in this order: 1. `api: true` and a `POST` with a simple content type → rejected. 2. `Sec-Fetch-Site` `same-origin`/`none` → allowed; `same-site`/`cross-site` → rejected unless `Origin` is in `allowOrigins`/`trustedOrigins`. 3. Without it, `Origin` must be this server's origin (`ctx.url.origin`), a trusted origin or an allowed one. 4. With neither header, a valid form token (`x-csrf-token` header or `_csrf` field) is required. Every response gets `Vary: Sec-Fetch-Site, Origin`. A rejection is SVS_E091 (403).

function csrf(options?: CsrfOptions): Middleware;

Exported from packages/security/src/csrf.ts

csrfToken Function

The form token for this request: an HMAC of the browser's random cookie value. Put it in a hidden `_csrf` field or send it as `x-csrf-token`. Calling it makes the middleware set the cookie. Only works on routes that use the `csrf` middleware.

function csrfToken(ctx: Pick<HttpContext, 'state'>): string;

Exported from packages/security/src/csrf.ts

rateLimit Function

Fixed-window rate limiter on a cache store's atomic `increment`, so the count is shared by every process using the same store (database store, not the memory store). Over the limit it answers 429 SVS_E092 with `Retry-After` in seconds. If the store fails the request is let through and the failure is logged: a broken cache must not take the API down.

function rateLimit(options: RateLimitOptions): Middleware;

Exported from packages/security/src/rate-limit.ts

security Function

The `@samveys/security` plugin. Registers the named middleware `csrf` and `cors` (use them with `use: ['csrf']`) and, when `rateLimit` is given, `rateLimit` backed by the `@samveys/cache` plugin, which is then required. Invalid options (e.g. `*` with credentials) throw right here.

function security(options?: SecurityOptions): import("@samveys/core").Plugin<SecurityOptions>;

Exported from packages/security/src/plugin.ts

Value (1)

Interface (4)

CorsOptions Interface

Options of {@link cors}.

interface CorsOptions {
    /** Exact origins, e.g. `https://app.example.com`. `'*'` allows any origin but not with `credentials`. */
    readonly origins: readonly string[];
    /** Default `GET, HEAD, PUT, PATCH, POST, DELETE`. */
    readonly methods?: readonly string[];
    /** Request headers a preflight may ask for. Default: whatever the preflight asked for. */
    readonly allowHeaders?: readonly string[];
    /** Response headers scripts may read. */
    readonly exposeHeaders?: readonly string[];
    /** Send `Access-Control-Allow-Credentials: true`. Not allowed together with `origins: ['*']`. */
  …
Show the whole declaration (14 lines)
interface CorsOptions {
    /** Exact origins, e.g. `https://app.example.com`. `'*'` allows any origin but not with `credentials`. */
    readonly origins: readonly string[];
    /** Default `GET, HEAD, PUT, PATCH, POST, DELETE`. */
    readonly methods?: readonly string[];
    /** Request headers a preflight may ask for. Default: whatever the preflight asked for. */
    readonly allowHeaders?: readonly string[];
    /** Response headers scripts may read. */
    readonly exposeHeaders?: readonly string[];
    /** Send `Access-Control-Allow-Credentials: true`. Not allowed together with `origins: ['*']`. */
    readonly credentials?: boolean;
    /** Preflight cache time in seconds. Default 600. */
    readonly maxAge?: number;
}

Exported from packages/security/src/contracts.ts

CsrfOptions Interface

Options of {@link csrf}.

interface CsrfOptions {
    /**
     * `'enforce'` (default) rejects with SVS_E091 (403). `'log'` lets the request through and records
     * the rejection with `logger.warn`, for a safe roll-out.
     */
    readonly mode?: 'enforce' | 'log';
    /** JSON API: reject a `POST` with a simple content type (`text/plain`, urlencoded, multipart). */
    readonly api?: boolean;
    /** Other origins that may send state-changing requests (e.g. a separate front-end). Exact match. */
    readonly allowOrigins?: readonly string[];
  …
Show the whole declaration (23 lines)
interface CsrfOptions {
    /**
     * `'enforce'` (default) rejects with SVS_E091 (403). `'log'` lets the request through and records
     * the rejection with `logger.warn`, for a safe roll-out.
     */
    readonly mode?: 'enforce' | 'log';
    /** JSON API: reject a `POST` with a simple content type (`text/plain`, urlencoded, multipart). */
    readonly api?: boolean;
    /** Other origins that may send state-changing requests (e.g. a separate front-end). Exact match. */
    readonly allowOrigins?: readonly string[];
    /**
     * Origins that count as this server's own origin. Use behind a TLS-terminating proxy, where
     * `ctx.url.origin` is `http://…` but browsers send `https://…`.
     */
    readonly trustedOrigins?: readonly string[];
    /**
     * HMAC key for form tokens. When omitted a random key is made per process: tokens then stop
     * working after a restart and across several instances, so set it in production.
     */
    readonly secret?: string;
    /** Receives log-mode rejections. The `security()` plugin passes its own logger. */
    readonly logger?: Logger;
}

Exported from packages/security/src/contracts.ts

RateLimitOptions Interface

Options of {@link rateLimit}.

interface RateLimitOptions {
    /** Requests allowed per window and key. */
    readonly limit: number;
    /** Window length in ms. Windows are fixed and aligned to the clock. */
    readonly windowMs: number;
    /** Separates the counters of different limits. Default `'default'`. */
    readonly name?: string;
    /**
     * The counter key. Default: `ctx.clientIp`, the socket peer, or the first `X-Forwarded-For` hop
     * only when the server's `trustProxy` trusts that peer (configure it on `http()`, not here).
     */
    readonly key?: (ctx: HttpContext) => string;

Exported from packages/security/src/contracts.ts

SecurityOptions Interface

Options of the `security()` plugin. Each part is optional.

interface SecurityOptions {
    /** CSRF guard defaults for the named `csrf` middleware. */
    readonly csrf?: CsrfOptions;
    /** CORS policy for the named `cors` middleware. Without it, no origin is allowed. */
    readonly cors?: CorsOptions;
    /** Registers the named `rateLimit` middleware and requires the `@samveys/cache` plugin. */
    readonly rateLimit?: RateLimitOptions;
}

Exported from packages/security/src/contracts.ts