Security Overview
Ramonyx is designed to be secure by default: limits apply before your code runs, and security primitives ship in the framework instead of being copy-pasted between apps.
This page describes mitigations, not guarantees. Security claims without executable tests are not made (see the repository's evidence policy). Report vulnerabilities privately via SECURITY.md.
Defaults that apply to every app
| Control | Default |
|---|---|
| Request body limit | 1 MiB → 413 (checked before routing, including streamed bodies) |
| Handler/loader/render timeout | 10 s → 503, with request.signal aborted |
| Concurrent requests | 100 → 503 + Retry-After (load shedding) |
| SSR document size | 2 MiB → 500 |
| Client bundle boundary | node:* imports and *.server.ts modules are rejected/stubbed at build time |
All limits are configurable per app (createHandler({ maxBodyBytes, timeoutMs, maxConcurrent })).
Security primitives
import {
cspNonce,
csrfProtection,
rateLimit,
securityHeaders,
} from "@ramonyx/security";
export default [
securityHeaders(), // CSP (with nonce), nosniff, referrer policy, frame-ancestors
cspNonce(), // per-request nonce injected into the hydration payload
csrfProtection(), // double-submit cookie (Web Crypto HMAC, constant-time compare)
rateLimit({ limit: 120, windowSeconds: 60 }), // per client; identity from the adapter
];- CSP nonce — the nonce travels with the hydration payload, so inline scripts carry the same
nonce the policy allows. No
'unsafe-inline'for scripts. - CSRF — double-submit cookie + header, signed with Web Crypto; compare in constant time.
trustProxyis opt-in, so a spoofedX-Forwarded-Forcannot evade rate limits by default. - Sessions — cookie parsing/serialization with safe defaults (HttpOnly, SameSite=Lax, Secure),
HMAC-signed values, and a small session helper (
read/commit/clear). - Outbound fetch —
guardedFetch(from@ramonyx/security/ssrf) rejects private/loopback/ link-local targets, validates every redirect hop, and pins the resolved IP so DNS rebinding cannot redirect a connection to an internal address. - Input validation — Standard Schema at the trust boundary (params, query, body, form); invalid
input returns
400with field-levelissuesbefore your handler runs. - Output safety — React escaping everywhere; raw HTML requires an explicit
dangerouslySetInnerHTML; server errors never leak stack traces in production.
Server/client boundary
Server-only code belongs in *.server.ts modules. The build:
- rejects
node:*imports in page/layout modules (with a message pointing at the file), - replaces
*.server.tsimports in the client bundle with a poison-pill stub, - scans the client output for leaked Node built-ins.
A secret placed in a plain shared module can still reach the client — keep secrets in
*.server.ts or server-only loaders and pass only what the UI needs.
Runtime (experimental)
The Rust/V8 runtime is experimental. Its permission model is deny-all by default
(--allow-read|write|net|env opt-in), with worker/process isolation, resource quotas, and an
OS-level sandbox (Landlock) for plugins. It is not part of the production promise yet.
Dependency & supply chain
- Lockfile committed;
pnpm audit --audit-level highin CI and weekly. - New versions must be at least 24 hours old before adoption (
minimumReleaseAge), reducing exposure to compromised fresh releases. - npm releases are published from verified tarballs with provenance (build → verify → publish), guarded by tag↔version checks.
Limitations (honest)
- Rate limiting and load shedding are process-local; multi-instance deployments need a shared store.
- CSP is wired through
@ramonyx/security; if you replace it, you own the policy. - CSRF protection covers double-submit verification; hidden-field verification for non-JS form flows is not implemented.
- The experimental runtime has not had an external security audit.