Security
Apex splits into two halves connected over a signed WebSocket:
Hosted control plane (cloud.apex.fusion.dev + relay.apex.fusion.dev). Fusion runs this. It's the operator surface: dashboard, auth, audit viewer, kill-switch UI.
User-controlled engine (apex serve). You run this. It holds broker credentials, executes trades, and writes the per-decision audit chain.
Broker credentials and execution authority never leave the engine host. The control plane sees periodic snapshots and a live stream of audit events; that's the entire data surface Fusion holds.
The boundary, resource by resource
ResourceWhere it livesVisible to FusionBroker API key + secret (Alpaca, IBKR, etc.)Engine host (env var or OS keychain)NoTrade execution authorityEngineNo. Fusion never calls Broker::submit_order.Cortex audit chain (signals, gates, orders, fills)Engine SQLiteNo. The cloud sees event headers; the per-decision context graph stays local.Tenant settings (gate thresholds, market timezone, strategy params)Engine SQLiteNoEngine state snapshot (equity, position count, today's P&L, mode flag, subscribed symbols)Cached on the relay-serverYes. Sent every 10s by the engine, displayed in the cloud dashboard.Audit events (kill switches, gate rejections, lifecycle)Streamed via relay SSEYes. The event itself (severity, source, summary, tenant id, audit node id). Free-form fields payload may include structured detail the engine chose to surface.Engine registration JWTIssued by apex-cloud, validated by the relay, held by the engineYes. Fusion mints + validates the token. The token authorizes the engine to talk to the relay; it grants no broker access.Engine liveness flagFusion-managed PostgresYesApex-cloud user auth (email, magic-link tokens)Supabase AuthYes (same as any web app you sign into).
Threat model
Worst case if Fusion is compromised. An attacker with full access to the relay-server gets snapshots: per-tenant equity numbers, position counts, audit event streams. They do not get broker credentials and they cannot submit trades. An attacker with full access to apex-cloud can mint new engine-registration tokens (mitigated by short rotation cycles and operator alerts on unexpected handshakes). The worst case is still that they cannot reach your broker.
Worst case if your engine host is compromised. Whoever controls the engine host controls the broker session. That risk is identical to any setup where you hold your own broker keys. The hybrid SaaS shape doesn't make this worse than a local-only install; it adds a hosted dashboard on top.
What about the audit chain? The chain on the engine host is append-only and Cortex's primitives make rewrites detectable (every node carries parent edges and a timestamp). The cloud sees the same chain through the SSE feed; the per-decision context (which bar, which gate parameters, which sentiment score) only exists on the engine. For "why did this trade fire?" investigations, the engine host is the authoritative source.
Authentication
User → cloud dashboard: magic-link sign-in. cloud.apex.fusion.dev issues a session cookie scoped to .fusion.dev.
Cloud → relay (server-to-server): shared HMAC secret. Apex-cloud mints engine-registration JWTs using this; the relay validates incoming JWTs against the same secret. Currently HS256; RS256 with separate signing-key custody is on the upgrade path.
Engine → relay: signed registration JWT in the WS Hello frame. The relay checks signature + revocation list (an operator-flipped flag on the engine row invalidates it).
Engine → broker: whatever the broker's own auth requires. Fusion never sees the call.
Encryption in transit
Cloud dashboard ↔ user: TLS (Vercel-managed).
Cloud dashboard ↔ relay: HTTPS (Fly-managed TLS).
Engine ↔ relay: WSS over TLS.
Engine ↔ broker: HTTPS / WSS direct to the broker.
Engine ↔ market data: WSS direct to the data provider.
No plaintext channel exists anywhere in the system once deployment is complete. Local dev uses http:// and ws:// for convenience; production deployments must terminate at TLS.
Encryption at rest
Engine SQLite (/data/cortex.db, /data/apex.db on the engine host): not encrypted at rest by default. Users who need that should mount /data on a filesystem with full-disk encryption (FileVault on macOS, LUKS on Linux).
Hosted Postgres (Fusion side): encrypted at rest by platform default.
Broker credentials (engine env vars): not encrypted at rest in /etc/apex/env; the file is owned root with 0600 perms. For higher-assurance setups, integrate with your secret manager (1Password CLI, Vault, AWS Secrets Manager) and inject at runtime instead of storing on disk.
Rotation
Registration JWT. Default lifetime is one year. Rotate from the cloud dashboard's /engines view; the revoked token is rejected at the next handshake. Generate a fresh one from /connect and redeploy the engine to pick it up.
HMAC secret. Rotating invalidates every issued token; trigger fresh /connect issuance before flipping the secret in production.
Broker credentials. On the broker's side; redeploy the engine with the new env values.
Reporting a security issue
Email security@fusion.dev with a description of the issue and reproduction steps. We respond within two business days and aim to fix verified vulnerabilities within seven calendar days. We do not have a public bug-bounty program during alpha.
Outside scope
This page covers Apex specifically. The Cortex security model (which Apex inherits at the audit-chain layer) gets its own page once Cortex docs land here.