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 (
405otherwise). Content-Type must be recognized (415otherwise). - Dispatch uses
Object.hasOwn(no__proto__/constructorwalks). - 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 anerrorschema. Plain throws →-32603Internal error. Payload / Schema decode →-32602. Schema-tagged Fail →-32000. Unknown method →-32601. Clients receive these asErrorinstances (ActionError/SchemaDecodeError/ActionFailure) withmessage,_tagandcode.
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.