Platform
Security & Identity
Last updated April 20, 2026
Norena handles real exchange credentials and executes real financial transactions. This page documents exactly how credentials are protected, how user identity is represented, and how strategy execution is isolated.
Security Overview
API keys and secrets are encrypted before being stored. The plaintext never touches the database.
Decrypted credentials are used for a single request and immediately cleared from memory.
Strategy code runs in an isolated VM with no filesystem, network, or process access.
Every database row is owned by a user ID. You can only access your own projects, trades, and wallets.
Wallet Encryption
When you save a wallet with your Binance API key and secret, the credentials are encryptedbefore being written to the database. The encrypted ciphertext is stored inapi_key_enc and api_secret_enc columns. The plaintext values are never stored in the database.
Encryption uses AES-256-GCM with a server-side encryption key stored as an environment variable, separate from the database. This means:
- A database dump alone is not sufficient to recover API keys â the encryption key is required.
- The encryption key and database credentials are stored separately and have independent access controls.
- Ciphertext is authenticated (GCM) â tampered ciphertext will fail decryption and log a security warning rather than silently producing garbage output.
Credential Lifecycle
The full lifecycle of a credential from save to use:
- Save: User enters API key + secret in the wallet form. The browser sends them over HTTPS. The server encrypts them immediately and stores only the ciphertext.
- At rest:
api_key_encandapi_secret_enccolumns hold AES-256-GCM ciphertext. No plaintext exists in the database. - Fetch: When a live-mode project is due, the runner queries the wallet row by ID, verifying it is owned by the project's owner.
- Decrypt: The runner calls
decryptString()on each column. If decryption fails, the error is logged and the tick falls back to paper mode. - Use: The plaintext API key and secret are passed directly to the Binance SDK for a single connectivity check and/or MARKET order.
- Zero: Immediately after use, the plaintext strings are overwritten with empty strings in memory. They are never logged, serialized, or transmitted beyond the Binance API call.
User Identity Model
User identity in Norena is represented by a UUID â the Supabase Auth user ID. This ID is the primary foreign key linking every piece of user data:
| Resource | Ownership column | Effect |
|---|---|---|
| Projects | owner_id | Only the owner can read, run, or modify the project. |
| Wallets | owner_id | Only the owner's wallets are decrypted at runtime. The runner verifies ownership before fetching credentials. |
| Trades | user_id | All trade rows carry the owner's user ID. Queries are always scoped by user ID. |
| Logs | user_id | Same scoping. Log entries cannot be read by other users. |
| Positions | owner_id (via project) | Positions are project-scoped; projects are user-scoped. |
Row-level security policies in the database enforce this ownership model at the query layer â not just at the application layer. Even if a bug in application code attempted to read another user's data, the database would return empty results.
The user ID is a UUID generated by Supabase Auth on account creation. It is stable for the lifetime of the account. All strategies, runs, trades, and wallets are permanently associated with this ID.
Sandbox Isolation
Strategy code submitted by users is compiled and executed in a restricted Node.jsvm sandbox. The sandbox provides these guarantees:
- No filesystem access â
fs,path, and all Node built-in modules are unavailable. - No network access â
fetch,http,https,net, anddnsare unavailable. Strategy code cannot make outbound requests. - No process access â
process,require,import, and all module system functions are unavailable. - No prototype pollution â
Array,Object,Math,JSON, andDateare frozen. Strategy code cannot mutate these globals. - Null-prototype global â the sandbox global has no prototype chain, preventing access to any inherited globals not explicitly whitelisted.
- Dual timeout â a synchronous timeout (via
vm.Script) and an async deadline (viaPromise.race) both enforce a 5-second hard limit per tick.
Per-user strategy isolation is enforced by the database ownership model, not the sandbox. Each project runs with the owner's user ID embedded in the broker context, ensuring that positions, logs, and trades are always written to the correct user's records.
Data Isolation
All user data is isolated at the database level using Supabase Row Level Security (RLS) policies. These policies mean that:
- API calls from the frontend are scoped to the authenticated user's session â no user can read or write another user's data.
- The backend runner uses a service role key but always scopes queries by
owner_id = project.owner_id, enforcing the same isolation explicitly in application code. - Wallet decryption explicitly verifies
owner_id = p.owner_idbefore returning credentials â a wallet cannot be used by a project it does not belong to.
User Recommendations
These are steps you should take on your end to maximize security:
| Action | Why |
|---|---|
| Enable only Spot & Margin Trading on your API key | Norena never needs withdrawal permissions. Granting them unnecessarily increases risk. |
| Whitelist the Norena server IP on your Binance API key | Restricts your key to only be usable from Norena's infrastructure, even if the key were somehow leaked. |
| Use a dedicated API key for Norena | Isolates Norena's access from other API keys you may use for manual trading or other applications. |
| Start with small position sizes in live mode | Limits exposure while verifying the strategy behaves correctly with real execution. |
| Monitor the Logs tab for credential errors | Decryption failures, connectivity errors, and rejected orders are all logged. Catching them early prevents unexpected behavior. |
| Rotate API keys periodically | Re-save your wallet after generating a new Binance API key. The old key is immediately replaced. |