/ section · security

Non-custodial. By construction.

We never hold your funds. We never ask for withdrawal permission. Exchange API keys are encrypted at rest with AES-256 Fernet + HMAC and rotated on request. Here is exactly how.

AES-256 Fernet HMAC-SHA256 HttpOnly cookies 2FA TOTP + backup codes Trade audit log Anomaly detection

Encryption at rest

Every exchange API key, secret, and private key is encrypted with Fernet (AES-128-CBC + HMAC-SHA256) before it touches the database. The master key lives in a secrets manager — never in the repository, never in the app image, never in logs.

Decryption happens in a memory-safe context manager (decrypted_exchange_keys()) that zeroes the plaintext buffer on exit. Sentry stack traces scrub any local variable whose name contains api_key, secret, token, private_key, or password before transmission.

Permissions we request

venuescope we requirescope we NEVER touch
Hyperliquidtrade · readwithdraw
Binance Futuresfutures_trade · readwithdraw · spot · margin
Bybit V5linear_trade · readwithdraw · transfer

Create trade-only keys on the venue, paste them once, forget about them. If a key is ever compromised, you revoke it at the exchange and your funds cannot move off.

Key rotation

Every credential row carries an encryption_key_version column. We rotate the master key on a schedule and on demand; a background task re-encrypts every record. You can also rotate per-exchange keys yourself from the settings page at any time.

Authentication

Passwords are hashed with bcrypt (cost 12). Password reset tokens are single-use and stored as a hash in Redis with short TTL. JWTs without an iat claim are rejected. Every secret comparison uses hmac.compare_digest for constant-time equality.

Two-factor

TOTP via any standard authenticator app (Authy, 1Password, Google Authenticator). Mandatory for admin accounts. Backup codes are bcrypt-hashed and single-use. Password reuse is checked against Have-I-Been-Pwned before it ever reaches the database.

Sessions

Refresh tokens

HttpOnly · Secure · SameSite=strict cookies, scoped to /api/auth. Not reachable from JavaScript, not submittable cross-origin.

WebSocket

Ticket-based auth: short-lived, one-time token exchanged for a WS connection. JWTs never appear in URLs, headers, or query strings.

Trade audit log

Every trade open, close, and modification writes to an append-only trade_audit_log table with SHA-256–hashed user IDs. The log is not mutable from application code — it exists so that you (or we, under your authorization) can reconstruct exactly what the system did and when.

Anomaly detection

A background task runs every five minutes and flags:

  • Rapid-fire trades — more than 10 opens in 5 minutes.
  • New-IP activity — any trade action from an IP not seen on your account in the last 30 days.

Alerts arrive on Telegram, email, and in-app WebSocket. Admin staff get parallel notifications for forensics. Dedup is 30 minutes so a genuine incident does not flood you.

Transport

  • HTTP/2 with HSTS preload, 2-year max-age, includeSubDomains.
  • Content-Security-Policy with no unsafe-inline, no unsafe-eval.
  • X-Frame-Options: DENY, Referrer-Policy: strict-origin-when-cross-origin, Permissions-Policy locked down.
  • Postgres connections over TLS (DATABASE_SSL_CA = CERT_REQUIRED on prod).
  • Backups encrypted with a dedicated Fernet key (BACKUP_ENCRYPTION_KEY), stored off-cluster.

Report a vulnerability

safe-harbour disclosure

Found something? Email [email protected]. We will acknowledge within 24 hours and work with you in good faith. We do not pursue researchers acting in good faith. PGP key on request.

Please do not test on accounts you do not own, do not exfiltrate user data, and do not degrade service. We run a small bounty for high-severity issues — reach out first.

// security inbox · [email protected] · full changelog at /changelog