shield
Every export of @kitr/shield, @kitr/shield/provider, read from the published type definitions. The guide shows them in use.
@kitr/shield
cors
function cors(options?: CorsOptions): Middleware;
Middleware that answers CORS preflights with 204 and adds Access-Control-Allow-* headers for allowed origins. Throws at once for origin: '*' with credentials.
corsPolicy
function corsPolicy(options?: CorsOptions): (requestOrigin: string | null) => CorsDecision;
The CORS rules for options, independent of any framework: @kitr/shield wraps it as h3
middleware and @kitr/api as route middleware, so both apply exactly the same policy. Throws
at once for origin: '*' with credentials, which would let any site send requests with the
user's cookies.
createMemoryRateLimitStore
function createMemoryRateLimitStore(): RateLimitStore;
The default rate-limit store: counters in this process, pruned past 10,000 keys. Each instance counts on its own.
createRedisRateLimitStore
function createRedisRateLimitStore(redis: RedisRateLimitClient, options?: {
prefix?: string;
}): RateLimitStore;
A rate-limit store over Redis, so every instance counts against the same limit. Each window is one key that Redis expires itself, so nothing needs sweeping:
rateLimit: { store: createRedisRateLimitStore(new Redis(process.env.REDIS_URL)) }
Keys live under prefix (default kitr:rl:).
csrf
function csrf(options?: CsrfOptions): Middleware;
Double-submit cookie CSRF: a readable cookie carries a token the browser must echo in a header
(fetch from another origin cannot read the cookie, so it cannot forge the header).
defaultCsp
function defaultCsp(options?: {
strictDynamic?: boolean;
unsafeInline?: boolean;
devStyles?: boolean;
}): string;
The default policy for the options: strictDynamic and unsafeInline rewrite its script-src and style-src.
getCspNonce
function getCspNonce(event: H3Event): string | undefined;
The current request's CSP nonce, for code that has the h3 event. Undefined when securityHeaders() did not run or the policy is off.
rateLimit
function rateLimit(options?: RateLimitOptions): Middleware;
Fixed-window rate limit with RateLimit-* headers and a 429 when exceeded.
securityHeaders
function securityHeaders(options?: HeadersOptions): Middleware;
Middleware that sets nosniff, frame, referrer, permissions, CSP and (in production) HSTS headers on every response, errors included.
shield
function shield<S extends {
use(middleware: Middleware): unknown;
app?: {
config: any;
isProduction: boolean;
};
}>(server: S, config?: ShieldConfig): S;
Attach the shield to a kitr server (or any object with use(middleware)), reading
config/shield.ts. Production (HSTS, Secure cookies) follows the app's mode, so a Workers or
Deno deployment that never sets NODE_ENV still gets production defaults from the build.
shieldMiddlewares
function shieldMiddlewares(config?: ShieldConfig): Middleware[];
The middlewares config/shield.ts asks for, in the right order.
useCspNonce
function useCspNonce(): string | undefined;
The CSP nonce of the request being rendered, for inline <script> and <style> tags you write
yourself: <script nonce={useCspNonce()}>. Server-side it comes from the request context
@kitr/server keeps for the request; in the browser, and outside a request, it is undefined
(the browser has already checked the tag against the nonce, and hides the attribute afterwards).
<ThemeScript /> and TanStack Start's own scripts take the nonce without you passing it.
DEFAULT_CSP
const DEFAULT_CSP = "default-src 'self'; script-src 'self' 'nonce-{nonce}'; style-src 'self' 'nonce-{nonce}'; style-src-attr 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self' data:; connect-src 'self' ws: wss:; frame-ancestors 'none'; base-uri 'self'; form-action 'self'";
The Content-Security-Policy securityHeaders() sends when you don't set one, as a template:
{nonce} is replaced by each request's nonce. Same-origin everything, inline scripts and style
tags only with the nonce, inline style="" attributes allowed (they cannot run script), no framing.
defineConfig
const defineConfig: <const C extends ShieldConfig>(config: C) => C;
Type the default export of config/shield.ts. Returns its argument unchanged.
CorsDecision
interface CorsDecision {
/** Headers to set on the actual response: empty when the origin is not allowed. */
headers: Record<string, string>;
/** Whether the response varies by `Origin`, so caches keep one copy per origin. */
vary: boolean;
/** Extra headers for a preflight (`OPTIONS`) answer. */
preflight: Record<string, string>;
}
What corsPolicy() decides for one request.
CorsOptions
interface CorsOptions {
/** `'*'`, one origin, a list of origins, or a predicate. Default: same-origin only (no header). */
origin?: '*' | string | string[] | ((origin: string) => boolean);
/** Methods a preflight allows. Default GET, POST, PUT, PATCH, DELETE, OPTIONS. */
methods?: string[];
/** Request headers a preflight allows. Default Content-Type, Authorization, X-CSRF-Token, X-Requested-With. */
headers?: string[];
/** Let the browser send cookies cross-origin. Refused together with `origin: '*'`. Default false. */
credentials?: boolean;
/** Seconds a preflight answer may be cached. Default 600. */
maxAge?: number;
}
Options for cors() and the cors key of config/shield.ts.
CsrfOptions
interface CsrfOptions {
/** Cookie that carries the token. Default `kitr-csrf`. */
cookie?: string;
/** Header the client sends it back in. Default `x-csrf-token`. */
header?: string;
/** Methods that need a token. Default POST, PUT, PATCH and DELETE. */
methods?: string[];
/** Paths that never need a token (webhooks). Prefix match. */
ignore?: string[];
/** Add `Secure` to the cookie. Default: `production`. */
secure?: boolean;
/** Whether this is a production process. `shield(server)` fills it from the app's mode; standalone, `inProduction()` from `@kitr/core` decides. */
production?: boolean;
}
Options for csrf() and the csrf key of config/shield.ts.
HeadersOptions
interface HeadersOptions {
/**
* `Content-Security-Policy`. A string, or false to leave it out. Default: a strict policy for an
* SPA/SSR app (`DEFAULT_CSP`), where inline scripts and styles run only with this request's
* nonce. A string may contain `{nonce}`, which becomes the request's nonce, so a custom policy
* can allow the page's own inline tags too: `script-src 'self' 'nonce-{nonce}' https://cdn.example.com`.
* `strictDynamic` and `unsafeInline` shape the default policy only.
*/
contentSecurityPolicy?: string | false;
/**
* Add `'strict-dynamic'` to the default `script-src`: scripts the page's nonced scripts load are
* trusted without listing hosts, and `'self'` and host lists are ignored by browsers that know
* it. Suits apps that load third-party scripts from their own code. Default false.
*/
strictDynamic?: boolean;
/**
* Allow every inline script and style in the default policy (`'unsafe-inline'`, no nonce), which
* is what the policy was before nonces. Only for pages you cannot give a nonce: a widget that
* injects inline `<script>` or `<style>` tags itself, or an HTML template you do not render
* through kitr. It lets any injected markup run script, so prefer fixing the source. Default false.
*/
unsafeInline?: boolean;
/** `Strict-Transport-Security` max-age in seconds; false to leave it out. Default one year in production. */
hsts?: number | false;
/** `X-Frame-Options`. Default `DENY`; false to leave it out. */
frameOptions?: 'DENY' | 'SAMEORIGIN' | false;
/** `Referrer-Policy`. Default `strict-origin-when-cross-origin`; false to leave it out. */
referrerPolicy?: string | false;
/** `Permissions-Policy`. Default `camera=(), microphone=(), geolocation=()`; false to leave it out. */
permissionsPolicy?: string | false;
/** Whether this is a production process, which turns HSTS on. `shield(server)` fills it from the app's mode; standalone, `inProduction()` from `@kitr/core` decides. */
production?: boolean;
}
Options for securityHeaders() and the headers key of config/shield.ts.
RateLimitOptions
interface RateLimitOptions {
/** Window length in milliseconds. Default 60 000. */
windowMs?: number;
/** Requests per key per window. Default 100. */
max?: number;
/** Key a request. Default: the client IP. */
key?: (event: H3Event) => string;
/**
* Read the client IP from `X-Forwarded-For` (first entry). Only turn this on behind a proxy
* you control that overwrites the header: otherwise any client can send a new value with every
* request and is never limited. Default false: the address of the connection.
*/
trustProxy?: boolean;
/** Where counters live. Default: this process, so each instance counts on its own; use `createRedisRateLimitStore` to share one limit. */
store?: RateLimitStore;
/** Paths that are never limited. Prefix match. */
ignore?: string[];
}
Options for rateLimit() and the rateLimit key of config/shield.ts.
RateLimitStore
interface RateLimitStore {
/** Increment and return the count and when the window resets (ms epoch). */
hit(key: string, windowMs: number): Promise<{
count: number;
resetAt: number;
}>;
}
Where rate-limit counters live. Implement it over Redis to share limits between instances.
RedisRateLimitClient
interface RedisRateLimitClient {
/** `SET key value PX ms NX`: starts a window only when none is open. */
set(key: string, value: string, px: 'PX', ms: number, nx: 'NX'): Promise<unknown>;
/** `INCR`: keeps the key's expiry. */
incr(key: string): Promise<number>;
/** `PTTL`: milliseconds left on the window. */
pttl(key: string): Promise<number>;
/** `PEXPIRE`: repairs a counter that lost its window. */
pexpire(key: string, ms: number): Promise<number>;
}
The part of an ioredis client the Redis rate-limit store
uses. An ioredis Redis or Cluster fits as it is.
ShieldConfig
interface ShieldConfig {
/** Security headers. On unless false. */
headers?: HeadersOptions | false;
/** CORS. Off unless set to an object. */
cors?: CorsOptions | false;
/** Double-submit cookie CSRF. On unless false. */
csrf?: CsrfOptions | false;
/** Fixed-window rate limit. On unless false. */
rateLimit?: RateLimitOptions | false;
}
The options in config/shield.ts. Each key takes its middleware's options, or false to turn it off.
@kitr/shield/provider
shieldProvider
const shieldProvider: import("@kitr/core").KitrProvider;
Validates config/shield.ts at boot (refuses origin: '*' with credentials); shield(server) in server/main.ts attaches the middleware.