Security model

Principle: never trust the client. The browser and the Unity Editor are untrusted; every permission is enforced on the server, for every HTTP request and every realtime message.

Identity & credentials

Credential Used by Properties
Session cookie skein_session Website 256-bit random, stored as SHA-256, HttpOnly, SameSite=Lax, Secure in production, 30-day sliding expiry, revocable.
CSRF token skein_csrf Website Double-submit token required on every cookie-authenticated mutation + Origin check.
Access token (JWT, HS256, 15 min) Unity Editor Bound to a refresh-token family; checked against the family on every request, so revocation is immediate.
Refresh token (opaque, rotating) Unity Editor Single-use. Re-use of a rotated token revokes the whole family (theft detection).
Realtime ticket (Ed25519 JWT, 60 s) Unity Editor → realtime Signed by the API's private key; realtime nodes only hold the public key. Single-use (jti consumed in Redis) to prevent replay.
API key sk_… CI / tools Project-scoped, role-limited (viewer/developer), SHA-256 stored, shown once, revocable, optional expiry.
  • Editor sign-in uses the OAuth 2.0 device authorization grant: the user approves a short code in a browser where they're already signed in; the editor never sees a password.
  • Passwords: argon2id (19 MiB, t=2, p=1). Minimum 10 characters. Unknown-account logins still verify a dummy hash (no timing oracle) and return the same error.
  • Brute force: per-IP rate limits on auth routes (10/min) and per-account lockout (10 failures → 15 min).
  • Email verification is required to invite others, accept invitations and purchase plans. Password reset tokens are single-use, 1 h, and revoke every session.
  • OAuth (GitHub, Google, Discord) uses PKCE + state bound to a cookie; accounts are linked only by provider id or a provider-verified email.

Authorization & tenant isolation

  • Tenants are organizations; projects belong to organizations. Effective project role = max(explicit project role, implicit role from org owner/admin).
  • All project-scoped endpoints go through one function (requireProjectAccess) that returns 404 for both missing and inaccessible resources, so ids can't be probed. UUIDs are validated before reaching SQL.
  • The permission matrix lives in packages/shared/src/permissions.ts and is unit-tested. Nobody can grant a role above their own; only owners can touch owners; a project always keeps an owner.
  • Realtime: identity and role come from the signed ticket. Viewers can't lock or mutate. Mutations require the lock in the same atomic Redis script that applies them. Previews are relayed only for the lock holder. Force-unlock requires owner/admin and is audited.
  • Membership changes are pushed to realtime nodes immediately: removed users are disconnected; downgraded users lose their locks.

Input validation & abuse prevention

  • Every API input and every realtime frame is validated with zod schemas (types, ranges, finite numbers, id formats, sizes).
  • Limits: HTTP body 256 KB (snapshots up to MAX_SNAPSHOT_MB), WebSocket frame 64 KB, selection/lock batch sizes, string lengths.
  • Per-connection token buckets on the realtime server; repeated abuse closes the connection.
  • Browser WebSocket connections must come from an allow-listed Origin (prevents cross-site WebSocket hijacking).
  • Snapshot uploads must be gzip of a Unity YAML scene; storage keys are generated server-side; scenePath must be Assets/….unity.

Data protection

  • Secrets come from environment variables / your secret manager; .env is git-ignored; nothing is hardcoded.
  • Logs are structured and redact authorization, cookie, set-cookie, passwords, tokens and tickets. Request bodies are not logged.
  • Audit log records sign-ins, failed sign-ins, membership and role changes, invitations, project/session lifecycle, snapshots, API keys, billing changes and force-unlocks.
  • Billing: plans are applied only from Stripe webhooks verified with the signing secret, processed idempotently, and re-fetched from Stripe (never from the browser).
  • Database constraints (foreign keys, checks, unique indexes) back up application rules.

Reporting

Security issues: security@skein.dev. Please don't open public issues for vulnerabilities.