Skip to content
These docs describe @green-tea/core 26.9.0-beta.1what changed. Install with npm i @green-tea/core@beta.

Transport security

Four createApp options cover TLS, proxying, CORS, and response headers: tls, trustProxy, cors, and security. See the createApp reference for the full option list.

import { readFileSync } from 'fs';
const app = createApp({
modules: [App],
tls: { key: readFileSync('key.pem'), cert: readFileSync('cert.pem') },
});

When tls is set the server serves https, and WebSocket upgrades become wss on the same port — no other changes.

const app = createApp({ modules: [App], trustProxy: true });

Behind a reverse proxy, reads X-Forwarded-Proto / X-Forwarded-For to populate ctx.protocol ('http' | 'https') and ctx.ip (read via @ctx()). Off by default — forwarded headers are ignored (and spoofable) until you opt in. It trusts the immediate hop unconditionally; there’s no CIDR allowlist yet.

Off unless origins is set.

const app = createApp({
modules: [App],
cors: { origins: 'https://app.example.com', credentials: true },
});

origins accepts string | string[] | '*' | (origin) => boolean. A preflight OPTIONS request (with Access-Control-Request-Method) is auto-answered with 204 before explicit or automatic OPTIONS routing. A bare OPTIONS request is instead handled by an explicit @Options route or the router’s automatic 204 + Allow response.

  • With credentials: true, the server never sends Access-Control-Allow-Origin: * — it echoes the concrete allowed origin (or blocks the request).

A predicate is the reason the option takes a function at all: the shapes it exists for are lookups — an allowlist in Redis, a tenant query, a regex over a parsed URL. Two things are worth knowing before you put I/O in one.

It is consulted once per request. Once for a normal request, once for a preflight, and not at all when the request carries no Origin header — the predicate is only reached when there is an origin to judge. You can put a round-trip in it without it being charged twice.

A predicate that throws denies the origin. The failure is logged and the request is answered without CORS headers, so the browser blocks it. It is not a 500 and it is not an open door: a lookup that failed has not said yes, and a backing store being briefly unavailable must never widen an allowlist. Watch the logs for it — from the outside, a broken lookup and a genuinely disallowed origin look the same to the caller, which is the price of failing closed.

Applied to every HTTP response. Disable everything with security: false, or override individual headers with an options object.

Header Default
X-Content-Type-Options nosniff
X-Frame-Options SAMEORIGIN
Referrer-Policy no-referrer
X-DNS-Prefetch-Control off
Strict-Transport-Security max-age=15552000 (180d) — only when the connection is secure (native TLS, or trustProxy + X-Forwarded-Proto: https); no includeSubDomains/preload
Content-Security-Policy not set — pass security: { csp: "..." }
const app = createApp({ modules: [App], security: { csp: "default-src 'self'" } });
Option Default
tls off (plain http/ws)
trustProxy off
cors off (same-origin only)
security ON (HSTS only when the connection is secure)