Skip to content
Oxide
Esc
↑↓navigate↵open⌘Jpreview
On this page

Security

What Oxide guards by default in assets, actions, and the dev server — and what stays yours.

Oxide hardens the build output, the action endpoint, and the dev server. Authentication, CORS beyond same-origin defaults, and rate limiting stay your job.

Asset serving (Node)

The generated server serves dist/client/ (with public/ merged in). These guards are on:

Attack vector Guard
Traversal (%2e%2e/, ..%2f) __rel() rejects paths containing .. segments.
Double-slash (///etc/passwd) __rel() rejects results that still start with / after slice(1).
Null byte (%00, \0) Rejected before and after decodeURIComponent.
Absolute path (/etc/passwd) Leading / is stripped; containment is the .. / double-slash checks.
SPA fallback Unknown navigations → index.html, never a directory listing. Asset fetches for missing files stay 404.
clientDir escape Build fails if clientDir resolves outside outDir.
Hashed assets [-.][0-9a-f]{8,}.ext → long immutable cache. Other files are not cached by default.

Generated __asset uses path.join — not path.resolve — so a leading / stays under the asset root.

Server actions

  • Action modules are never bundled into the client. Imports become Effect RPC stubs. Unmarked exports are not RPC methods.
  • Treat arguments as untrusted. Prefer withSchema / action(..., { payload }) before using them.
  • Do not return secrets, tokens, or raw rows you would not show in the UI.
  • Streams use newline-delimited JSON-RPC on the same endpoint — not SSE. Frames scrub as they flush.
  • Endpoint is POST-only (405 otherwise). Content-Type must be recognized (415 otherwise).
  • Dispatch uses Object.hasOwn (no __proto__ / constructor walks).
  • Body size defaults to 1 MB (real body, not only Content-Length). Batches cap at 20 items.
  • Wire errors scrub to code + message, plus the tagged error’s fields when an action declares an error schema. Plain throws → -32603 Internal error. Payload / Schema decode → -32602. Schema-tagged Fail → -32000. Unknown method → -32601. Clients receive these as Error instances (ActionError / SchemaDecodeError / ActionFailure) with message, _tag and code.

Set sameOrigin: false only when you accept cross-origin calls and provide CORS, CSRF, and authentication. See middleware.

actions.sameOrigin defaults to true. HTTP actions require Origin and/or Sec-Fetch-Site and reject cross-site callers. WebSocket upgrades also allow requests that omit both headers when Host matches the request URL host (celld and some proxies strip those headers; localhost / 127.0.0.1 / ::1 count as the same host).

Host header

The generated Node server builds request.url from req.headers.host (same pattern as Express, Hono, Koa). If your entry uses that URL for redirects, validate the host yourself. In production your reverse proxy usually owns this.

Was this page helpful?