allowedOrigins? | string[] | Origins allowed to read this app’s responses from a browser (CORS). Browsers block a cross-origin fetch unless the response carries a matching Access-Control-Allow-Origin. The common case is Expo Web, which Metro serves on a different port from the API — a different port is a different origin, so every method call is blocked without this. Scope: this covers your module routes and the framework’s own API routes (method calls and the OAuth endpoints). SSR pages and static assets are excluded, so a listed origin can call your API but cannot read your rendered pages with credentials. The scope is derived from the routes actually registered, not matched by path prefix: module routes carry no framework-imposed prefix (the docs’ example mounts /todos at the root), so a prefix rule would silently drop CORS from the user-defined routes that most need it. Each entry must be an exact origin (scheme://host[:port]); patterns and wildcards are not supported, since the response header carries one concrete origin. Entries are normalized (trimmed, lowercased, default port and trailing slash dropped) to match what browsers send, and anything that is not a valid origin throws at startup rather than silently never matching. The matched origin is echoed back rather than *, and Access-Control-Allow-Credentials is sent, so the browser will expose a credentialed response to JS. Note this only covers same-site requests: the auth cookie is SameSite=Lax, so a genuinely cross-site caller (app.example.com → api.other.com) never has the cookie attached in the first place, regardless of this setting. The Expo Web case works because localhost:8081 and localhost:3000 differ only by port, and port is not part of a site. Listed origins are also the only foreign origins allowed to call methods and the OAuth linking endpoints from a browser. Those routes act as the signed-in user, so a browser request to them from any other origin — including a sibling subdomain of the same site — is rejected with 403. List only origins you trust to act on behalf of your users. Opt-in by design: when unset, no CORS headers are sent at all. Deployments that already add CORS at a proxy or router therefore stay untouched — a duplicated Access-Control-Allow-Origin is invalid and would break them. Native iOS/Android do not enforce CORS and never need this. | packages/modelence/src/app/securityConfig.ts:163 |
clientIpHeader? | string | Name of a single-value header the trusted proxy sets to the originating client IP, used instead of walking the X-Forwarded-For chain. Cloudflare recommends reading CF-Connecting-IP (or True-Client-IP on Enterprise plans) rather than X-Forwarded-For, because Cloudflare appends to an inbound X-Forwarded-For instead of overwriting it, while these headers always carry exactly one address. This header is only read when the immediate peer is a trusted proxy, so trustedProxies (or MODELENCE_TRUSTED_PROXIES) must also be configured with the proxy’s addresses. Without that, a direct caller could set the header themselves and choose their own rate-limit identity. When the peer is untrusted or the header is absent, the IP falls back to the normal trust proxy resolution. Example // Behind Cloudflare, with Cloudflare's published ranges trusted: clientIpHeader: 'cf-connecting-ip' | packages/modelence/src/app/securityConfig.ts:209 |
frameAncestors? | string[] | Additional origins allowed to embed this app in an iframe. The app’s own origin ('self') is always included automatically. When not set, only same-origin framing is allowed. When set, X-Frame-Options is omitted since it cannot express multiple origins. | packages/modelence/src/app/securityConfig.ts:67 |
hsts? | boolean | HstsConfig | Sends Strict-Transport-Security, telling browsers to reach this host over HTTPS only. true uses max-age=31536000 (one year) without subdomains; pass an object to tune it. Off by default: browsers cache the policy for maxAge, so enabling it on a host that still needs plain HTTP (or includeSubDomains on a domain with HTTP-only subdomains) is hard to undo. Browsers ignore the header on plain-HTTP responses, so it is safe to leave on in local development. | packages/modelence/src/app/securityConfig.ts:103 |
noSniff? | boolean | Sends X-Content-Type-Options: nosniff, so browsers refuse to run a response as a script or stylesheet unless its Content-Type says so. Defaults to true; set false to omit the header. | packages/modelence/src/app/securityConfig.ts:117 |
referrerPolicy? | ReferrerPolicy | false | Value of the Referrer-Policy header. Defaults to 'strict-origin-when-cross-origin', which sends only the origin to other sites and nothing on HTTPS→HTTP navigations. Set false to omit the header. | packages/modelence/src/app/securityConfig.ts:110 |
scriptSrc? | string[] | Sources allowed to load scripts, sent as the CSP script-src directive. The app’s own origin ('self') is always included automatically, so an empty list yields script-src 'self' — the strictest policy that still runs the app’s bundle. When not set, no script-src directive is sent and scripts are unrestricted. Setting it is the main defence against injected scripts reading the session token, which the client keeps in localStorage. Each entry is a single CSP source: a host (https://js.stripe.com), a scheme (https:), or a single-quoted keyword or hash ('unsafe-eval', 'sha256-...'). Unquoted or unrecognised keywords, *, 'none', 'strict-dynamic', static nonces, and entries holding more than one source throw at startup. Inline scripts in index.html (analytics snippets, theme bootstrapping) are blocked unless allowed by hash. Outside production (NODE_ENV !== 'production'), 'unsafe-inline' is added (and hashes dropped, since they would disable it) so the Vite dev server’s inline React Refresh preamble can run; test the policy against a production build. | packages/modelence/src/app/securityConfig.ts:91 |
trustedProxies? | string | string[] | IP addresses or CIDR ranges of reverse proxies that are allowed to supply the client IP through X-Forwarded-For. This uses Express’s trust proxy address syntax, which also supports the named ranges loopback, linklocal, and uniquelocal. For backward compatibility, all proxy addresses are trusted when neither this option nor MODELENCE_TRUSTED_PROXIES is set. Configure one of them in production so only addresses that cannot be reached directly by untrusted clients are trusted. Once configured, connectionInfo.ip is resolved by walking the proxy chain from the app toward the client and stopping at the first untrusted address. This keeps a caller from choosing its rate-limit identity by prepending a forged X-Forwarded-For value. Example trustedProxies: ['loopback', '10.0.0.0/8'] | packages/modelence/src/app/securityConfig.ts:185 |