Skip to content

Security

Security rules for frontend development. The backend remains the security boundary; the frontend limits the exploitation surface.

  • Persistent storage (localStorage, validated product decision — see Authentication): an injected script could read the tokens; strict rotation and deduplicated refresh limit exploitation.
  • Never logged: no console.*, no error transport, no UI field.
  • No token in URLs, .env, or headers other than the bearer.
  • Everything goes through the core/api/http.ts wrapper: no raw fetch() (uniformity of bearer, refresh, error envelope, timeout).
  • Base URL configured by VITE_API_URL (relative /api/v1 default; in dev the Vite proxy relays to the dev backend). Nothing else is prefixed by the dev backend in production without validation.
  • The frontend model (core/permissions) mirrors the real backend — never invent a prefix/verb.
  • UI guards (requirePermissions, useCan) are a convenience, not protection: the backend allows or denies on every endpoint.
  • React escapes text by default. Never use dangerouslySetInnerHTML on unapproved content (backend messages included).
  • Never pass user values into request URLs without encoding — the wrapper uses fixed relative paths (no path interpolation from the user).
  • No secret in the client bundle (anything bundled is public).
  • New dependencies justified and pinned by the lockfile.
  • Devtools (Router/Query) are only mounted under import.meta.env.DEV (also stripped by the @tanstack/devtools-vite plugin at build).
  • ❌ Send tokens to the error server / monitoring.
  • ❌ dangerouslySetInnerHTML on backend or user content.
  • ❌ Reduce storage without a validated product decision.
  • ❌ Assume hiding a button removes the backend permission.