Skip to content

Search is only available in production builds. Try building and previewing the site to test it out locally.

Security

Security in Reactor spans three layers: infrastructure secrets, application-level row-level security, and operational access control. This guide covers all three for self-hosted and managed deployments.

flowchart TB
Client[Client / SDK] -->|JWT| API[Public API /auth /data /storage]
Admin[CLI / CI] -->|Admin Bearer| AdminAPI[/_admin/*]
API --> RLS[PostgreSQL RLS]
API --> Vault[Vault / env secrets]
AdminAPI --> Vault
LayerMechanismScope
User authJWT (access + refresh tokens)End-user API access
Data isolationPostgreSQL RLS policiesPer-row tenant/user scoping
Admin opsBearer token on /_admin/*Deploy, migrate, doctor, logs
Internal/_internal/* + shared secretCapability-to-capability (sizes 5–6 only)
SecretsVault (embedded or OpenBao)API keys, encryption keys

Generate strong, unique values for each:

Terminal window
# Admin token (min 32 chars)
openssl rand -hex 32
# Auth data key (32 bytes, base64)
openssl rand -base64 32
# Storage signing secret
openssl rand -hex 32
# Functions env encryption key
openssl rand -base64 32
# Jobs webhook secret
openssl rand -hex 32
# Vault master key (if using embedded vault)
openssl rand -hex 32

Recommended for containers and Fly.io:

Terminal window
flyctl secrets set \
REACTOR_ADMIN__TOKEN="..." \
REACTOR_AUTH__DATA_KEY="..." \
REACTOR_STORAGE__SIGNING_SECRET="..." \
REACTOR_FUNCTIONS__DATA_KEY="..." \
REACTOR_JOBS__WEBHOOK_SECRET="..."

Never commit secrets to git. Use .env locally (gitignored).

SecretRotation impactProcedure
admin.tokenBreaks CLI/CI until updatedSet new value, restart, update CI vars
auth.data_keyRequires re-encryption migrationUse auth key rotation API (planned)
storage.signing_secretInvalidates existing signed URLsRotate during low traffic; URLs expire naturally
JWT signing keysInvalidates active sessionsAuth rotates keys on schedule; clients refresh

Reactor Data enforces access control at the PostgreSQL layer. Policies reference the authenticated user’s JWT claims via current_setting('request.jwt.claims').

-- migrations/001_create_posts.sql
CREATE TABLE posts (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES auth.users(id),
title TEXT NOT NULL,
body TEXT,
published BOOLEAN DEFAULT false,
created_at TIMESTAMPTZ DEFAULT now()
);
ALTER TABLE posts ENABLE ROW LEVEL SECURITY;
-- Users read their own posts
CREATE POLICY posts_select_own ON posts
FOR SELECT
USING (user_id = (current_setting('request.jwt.claims', true)::json->>'sub')::uuid);
-- Users insert their own posts
CREATE POLICY posts_insert_own ON posts
FOR INSERT
WITH CHECK (user_id = (current_setting('request.jwt.claims', true)::json->>'sub')::uuid);
-- Users update their own posts
CREATE POLICY posts_update_own ON posts
FOR UPDATE
USING (user_id = (current_setting('request.jwt.claims', true)::json->>'sub')::uuid);
-- Anyone can read published posts
CREATE POLICY posts_select_published ON posts
FOR SELECT
USING (published = true);

Server-side functions using the service role JWT (or internal auth at size 2) bypass RLS for admin operations. Never expose service credentials to clients.

// Client SDK — subject to RLS
const { data } = await reactor.data.from('posts').select('*');
// Server function — uses service context
// RLS policies with service_role check apply instead

For org-scoped data, include org_id in JWT claims:

CREATE POLICY org_isolation ON documents
FOR ALL
USING (
org_id = (current_setting('request.jwt.claims', true)::json->>'org_id')::uuid
);

See the Multi-tenant app guide for a complete walkthrough.


All routes under /_admin/* require the admin bearer token:

Authorization: Bearer <admin.token>
EndpointMethodRisk if exposed
/_admin/deployPOSTArbitrary code/migration deployment
/_admin/migratePOSTSchema modification
/_admin/shutdownPOSTDenial of service
/_admin/logsGETInformation disclosure
/_admin/doctorGETInfrastructure fingerprinting
/_admin/versionGETLow — version info
[admin]
token = "{{ env REACTOR_ADMIN_TOKEN }}"
allow_remote = false # default: localhost only

Keep allow_remote = false. Run deploys via SSH tunnel:

Terminal window
ssh -L 8000:127.0.0.1:8000 user@vps
reactor deploy --endpoint http://localhost:8000

Terminate TLS at Caddy, nginx, Fly.io, or your CDN. Reactor serves plain HTTP internally.

Restrict origins in production:

# Per-capability CORS is configured at the server level
[server]
# Use env or reverse proxy headers for CORS in v0

Place CORS rules at the reverse proxy when possible:

api.myapp.com {
@preflight method OPTIONS
handle @preflight {
header Access-Control-Allow-Origin "https://myapp.com"
header Access-Control-Allow-Methods "GET, POST, PUT, PATCH, DELETE, OPTIONS"
header Access-Control-Allow-Headers "Authorization, Content-Type"
respond 204
}
reverse_proxy localhost:8000
}
  • Enable SSL for remote connections (sslmode=require in connection URL)
  • Use least-privilege database roles — Reactor migrations create capability-specific schemas
  • On shared cluster: per-tenant roles with CONNECTION LIMIT
  • Port 8000 (or your bind port) not publicly exposed without TLS proxy
  • PostgreSQL not publicly accessible
  • Admin endpoints blocked from internet (or IP-restricted)
  • SSH key-only access to VPS

[auth]
data_key = "{{ env REACTOR_AUTH_DATA_KEY }}"
jwt_issuer = "reactor-auth"
jwt_audience = "reactor"
access_ttl_secs = 3600 # 1 hour — shorten for high-security apps
refresh_ttl_secs = 604800 # 7 days — reduce from default 30 days
public_url = "https://api.myapp.com"
  • Enable SMTP for email verification before allowing sign-ups in production
  • Configure OAuth providers with strict redirect URI allowlists (see OAuth setup guide)
  • Monitor failed auth attempts via reactor_http_requests_total{capability="auth",status="401"}

On the Reactor.cloud shared cluster:

  • Each tenant gets database tenant_<ref> with dedicated role
  • Host-based routing resolves tenant before any query executes
  • Quotas enforce resource limits per tier
  • NATS topics are tenant-prefixed: reactor.{ref}.data.>

If tenant isolation is suspected compromised:

  1. Disable the tenant route immediately
  2. Audit logs for the project ref
  3. Escalate to security@reactor.cloud

  • All required secrets generated with cryptographic randomness
  • Secrets stored in vault, env vars, or provider secret manager — not in git
  • Admin token rotated from default/dev value
  • Separate secrets per environment (dev/staging/prod)
  • [auth] section configured — no anonymous access
  • RLS enabled on all user-facing tables
  • allow_remote = false for admin (or IP-restricted)
  • Service role credentials never shipped to clients
  • OAuth redirect URIs restricted to your domains
  • HTTPS enabled with valid certificates
  • PostgreSQL SSL enabled for remote connections
  • Firewall rules restrict direct database access
  • CORS limited to known frontend origins
  • Automated database backups configured
  • File storage backups (blobs, functions, sites)
  • /health monitored by load balancer
  • /metrics scraped by Prometheus
  • Log aggregation configured
  • Incident response contacts documented
  • Data retention policies defined
  • User deletion flow implemented (auth + data cascade)
  • Audit logging enabled for admin operations (C6@fly control plane)