# Security defaults and application responsibilities

Redweb applies transport boundaries; it cannot decide who may read a game room, make a move, or access an account. Authenticate and authorize those actions in the application, use HTTPS/WSS, and test the deployed proxy configuration.

## Current defaults

- WebSocket upgrades with an `Origin` header require the same scheme and host as the request. Clients without `Origin` remain possible, so this is browser cross-site protection, not authentication. Configure `admission.origins` for intentionally cross-origin sockets; an explicit policy replaces the default check.
- An `admission.authenticate` hook fails closed on absent, false, empty-string, or `NaN` identities. Return a concrete principal only after verifying credentials.
- Ordinary HTTP services do not emit wildcard CORS headers. Configure `corsOptions` only for origins that should read responses. CORS is not authorization: an allowlisted foreign origin can read permitted GET responses but cannot make unsafe mutations through the default HTTP boundary. Cross-origin write APIs require an application-owned authorization and CSRF design; do not assume a CORS allowlist enables them.
- Unsafe HTTP requests with a foreign `Origin` or `Sec-Fetch-Site` other than `same-origin` or `none` are rejected, even without cookies. Cookie-bearing unsafe requests additionally need a matching `Origin` or `Sec-Fetch-Site: same-origin`; origin-less nonbrowser API requests without cookies remain valid. Services must still authorize the account and validate input.
- A trusted TLS reverse proxy can pass its exact external HTTPS origin as `publicOrigin` when it forwards to a private HTTP listener. This lets the browser's same-origin form POST pass without treating CORS as write authorization or trusting arbitrary forwarded headers. Keep the listener unreachable around the proxy; `publicOrigin` is not an authentication policy and cross-site Fetch Metadata is still rejected.
- WebSocket frames have a 1 MiB default maximum, and unordered routes allow at most 64 in-flight messages per connection; override `websocketOptions.maxPayload` or `limits.maxPendingMessages` only when a route genuinely needs more. Application-level payload and rate limits remain useful.
- An empty `SocketServer` route list opens no WebSocket endpoint. Register an echo/demo route explicitly if desired.
- A route bound to a rendered page requires a valid page session unless it explicitly authenticates raw clients in its own `admission.authenticate` hook and none of its bound pages has a page-specific authorization policy. A missing page session is never silently treated as raw. Put clients needing different page permissions on a separate socket route.
- Rendered pages set `X-Frame-Options: SAMEORIGIN`. Uploaded client filenames are reduced to safe basenames; applications must still generate their own storage keys and enforce upload size, type, and destination policy.
- A second connection from the same network address cannot evict an existing client merely because `allowDuplicateConnections` is false. Intentional replacement requires `getClientKey`, `admission.authenticate`, and the same authenticated principal on both connections. A proxy address or unverified client key alone never grants replacement. Authenticate identities with trusted credentials; header values used as test fixtures are not authentication.
- Static public paths do not serve through a symlink outside their configured root. Keep untrusted users from modifying a public directory while the server runs; a filesystem race is outside this request-time check.
- Static export refuses output directories linked outside its destination and replaces generated pages and merged public assets through temporary files, so an existing hardlink cannot overwrite another file. Do not give untrusted users write access to the output tree during a build.

Protected room grants persist until explicit leave, disconnect, replacement, clear, or shutdown. Changing a policy alone does not revoke an existing membership: invalidate credentials, then remove affected connections with `leaveRoom`/`leaveAll`. See [room authorization](ROOM_AUTHORIZATION.md).
