Skip to main content

HTTP Security

Zelt provides built-in HTTP security through two auto-registered middleware classes: SecureHeadersMiddleware and CorsMiddleware. Both are registered globally on every HTTP app and run on all routes before your configured global middleware.

Configuration is controlled through SecureHeadersConfig and CorsConfig. Both use the @Config decorator pattern for type-safe, DI-based configuration.

  • SecureHeadersConfig is enabled by default with secure defaults
  • CorsConfig is disabled by default (empty origin) and must be explicitly configured

SecureHeadersConfig​

Security headers are automatically applied to all responses. The default configuration enables recommended security headers.

Default Headers​

HeaderDefault
Cross-Origin-Resource-Policysame-origin
Cross-Origin-Opener-Policysame-origin
Origin-Agent-Cluster?1
Referrer-Policyno-referrer
Strict-Transport-Securitymax-age=15552000; includeSubDomains
X-Content-Type-Optionsnosniff
X-DNS-Prefetch-Controloff
X-Download-Optionsnoopen
X-Frame-OptionsSAMEORIGIN
X-Permitted-Cross-Domain-Policiesnone
X-XSS-Protection0
X-Powered-Byremoved
Cross-Origin-Embedder-Policydisabled

Customizing Headers​

Extend SecureHeadersConfig and override properties to customize header values:

import { Config, SecureHeadersConfig } from '@zeltjs/core';

@Config
class MySecureHeadersConfig extends SecureHeadersConfig {
  override readonly xFrameOptions = 'DENY';

  override readonly referrerPolicy = 'strict-origin-when-cross-origin';
}

Disabling Headers​

Set a header property to false to disable it:

import { Config, SecureHeadersConfig } from '@zeltjs/core';

@Config
class MySecureHeadersConfig extends SecureHeadersConfig {
  override readonly xXssProtection = false;

  override readonly xDownloadOptions = false;
}

CorsConfig​

CORS is disabled by default. To enable it, extend CorsConfig and set the origin property.

Enabling CORS​

import { Config, CorsConfig } from '@zeltjs/core';

@Config
class MyCorsConfig extends CorsConfig {
  override readonly origin = 'https://example.com';
}

Multiple Origins​

import { Config, CorsConfig } from '@zeltjs/core';

@Config
class MyCorsConfig extends CorsConfig {
  override readonly origin = ['https://app.example.com', 'https://admin.example.com'];
}

Credentialed requests and wildcard migration​

For requests with cookies or other credentials, set credentials: true and explicitly list the trusted origins:

import { Config, CorsConfig } from '@zeltjs/core';

@Config
class MyCorsConfig extends CorsConfig {
  override readonly origin = ['https://app.example.com', 'https://admin.example.com'];
  override readonly credentials = true;
}

If upgrading from origin: '*' with credentials: true, replace the wildcard with an explicit allowlist. Updated Hono no longer reflects arbitrary request origins for this combination. For a credentialed request, a response with Access-Control-Allow-Origin: * is a CORS error: the browser does not expose the response to JavaScript. Simple requests can still reach the server and cause side effects; CORS does not prevent request delivery or server-side changes. This fixes a data-exposure vulnerability; do not restore the old behavior by reflecting untrusted origins.

Available Options​

OptionTypeDefaultDescription
originstring | string[][]Allowed origins (empty disables CORS)
credentialsbooleanfalseAllow credentials
allowMethodsstring[]['GET', 'HEAD', 'PUT', 'POST', 'DELETE', 'PATCH']Allowed HTTP methods
allowHeadersstring[][]Allowed request headers
exposeHeadersstring[][]Headers exposed to the client
maxAgenumber | undefinedundefinedPreflight cache duration in seconds

Full Configuration Example​

import { Config, CorsConfig } from '@zeltjs/core';

@Config
class MyCorsConfig extends CorsConfig {
  override readonly origin = 'https://example.com';

  override readonly credentials = true;

  override readonly allowHeaders = ['Content-Type', 'Authorization'];

  override readonly exposeHeaders = ['X-Request-Id'];

  override readonly maxAge = 86400;
}

Registration​

The middleware classes are registered automatically. Register custom configs when creating the app:

import { createApp, Config, CorsConfig, SecureHeadersConfig, Controller, Get, http } from '@zeltjs/core';

@Config
class MyCorsConfig extends CorsConfig {
  override readonly origin = 'https://example.com';
  override readonly credentials = true;
}

@Config
class MySecureHeadersConfig extends SecureHeadersConfig {
  override readonly xFrameOptions = 'DENY';
}

@Controller('/') class AppController { @Get('/') index() { return { ok: true }; } }

const app = createApp([http({
    controllers: [AppController],
  })], { configs: [MyCorsConfig, MySecureHeadersConfig] });

The framework automatically detects and uses your custom configuration classes when registered in the configs array.

Skipping Security Middleware​

Use @SkipMiddleware to skip either built-in middleware for one endpoint or every endpoint in a controller. Method-level and controller-level skips are combined.

import {
  Controller,
  CorsMiddleware,
  Get,
  SecureHeadersMiddleware,
  SkipMiddleware,
} from '@zeltjs/core';

@SkipMiddleware(CorsMiddleware)
@Controller('/webhook')
class WebhookController {
  @Get('/health')
  health() {
    return { ok: true };
  }

  @SkipMiddleware(SecureHeadersMiddleware)
  @Get('/raw')
  raw() {
    return { ok: true };
  }
}

In this example, non-preflight requests to WebhookController endpoints skip CORS response headers. The /webhook/raw endpoint also skips secure headers.

@SkipMiddleware(CorsMiddleware) does not disable CORS preflight handling. OPTIONS preflight requests are handled by CorsMiddleware before an endpoint handler is selected, so the preflight response can still include CORS allow headers. The actual endpoint response is the part that skips CorsMiddleware.