Security
This guide explains how Coldtill protects money and what you, as the operator, need to do to keep it that way. The threat model lists threats and mitigations in detail, and the October 2026 audit report lists what was found and fixed.
What's at risk, and what isn't
| Funds | Who can move them |
|---|---|
| Customer deposits (EVM, Solana) | Anyone can trigger a sweep, but it can only go to your cold wallet. The server can't change that |
| Bitcoin deposits | Only you: they land directly in your own wallet, derived from your xpub |
| Your cold wallets | Only you. Coldtill never sees those keys |
| Hot wallets | The sweeper and payouts processes, after decrypting the key with your passphrase |
A complete server compromise can, at worst, spend the hot wallets' balances and tamper with payment records (for example marking an unpaid order as paid). It cannot redirect deposits. Keep the hot float small, and cross-check large orders against the chain before shipping.
Operator checklist
Before going live:
- Run on a dedicated server or VM, kept up to date (
sudo apt update && sudo apt upgrade). - Use an
https://site URL. Caddy obtains the certificate. - Only ports 80 and 443 are open (
sudo ufw allow 80,443/tcp && sudo ufw enable). Postgres is not published. - Every user has two-factor authentication. Give
project_adminonly to people trusted to approve payouts. - Use your own or paid RPC nodes for each enabled network, ideally more than one provider. See Nodes.
- Payout limits: a low auto-approve amount, plus hourly and daily limits.
- The hot wallets hold about a week of gas and payouts, no more.
-
deploy/.envanddeploy/secrets/are backed up offline, and the database is backed up daily. - SMTP works: approve a small test payout end to end.
- Your webhook handler uses
X-Coldtill-Signature-V2(or the SDK'sverifyWebhookRequest). - For extra protection, run without a stored passphrase (see Install).
Accounts and sessions
- Passwords are hashed with Argon2id (64 MiB, 3 passes). At most four hashes run at once, so login floods can't exhaust memory.
- Login is rate limited per IP address and per email: 10 attempts a minute.
- Two-factor (TOTP) is needed to log in once enabled, and a fresh code is needed for sensitive actions. Each
code works once. Five wrong codes lock two-factor checks for that user for 15 minutes; the lock is held in
memory, so it resets if the
apiprocess restarts. - Sessions use
HttpOnly,SecureandSameSite=Strictcookies. State-changing requests must come from the same origin and be JSON.
Actions that need a fresh two-factor code include:
- creating, approving or resolving payouts;
- editing the address book or payout limits;
- creating API keys;
- adding RPC nodes or lowering confirmation counts;
- assigning hot wallets, setting up deposit wallets;
- changing auto-convert settings.
Roles
| Role | Can |
|---|---|
root |
Everything, including managing admins |
admin (global) |
Manage users, hot wallets, nodes and networks, and all projects |
member (global) |
Only the projects they're added to, with that project's role |
project_admin |
Project settings, API keys, webhooks, payouts and approvals |
developer |
Create payments and view everything in the project |
read_only |
View only |
Hot wallets
- Keys are encrypted with age. Only the
sweeperandpayoutscontainers get the identity file and passphrase; theapiprocess can encrypt imported keys but never decrypt them. - Transactions are signed and stored before broadcast, so a crash can't produce a second payment.
- Nonces are reserved under a database lock; gaps are filled automatically.
- Payouts Coldtill can't account for become
needs-reviewinstead of guessing. See Payouts.
Nodes
Coldtill doesn't trust RPC nodes with keys, but it does trust them to report deposits honestly. A malicious node could report a deposit that never happened. Mitigations:
- Use nodes you run or pay for, not the public defaults. Add several, from different providers.
- Bitcoin deposits are only orphaned when every node agrees the transaction is gone.
- EVM chain IDs are signed into every transaction, and the Solana genesis hash is pinned, so a node on the wrong network can't trick the signers.
- For large orders, check the deposit on a block explorer before shipping.
Node URLs often carry API keys. They're never shown in error messages and adding a node needs two-factor.
Webhooks
- Each endpoint has its own secret, sealed in the database with the server secret.
- The v2 signature covers a timestamp, so receivers can reject replays.
- Coldtill refuses to connect to private and reserved addresses on mainnet (checked at connect time, so DNS rebinding doesn't help) and doesn't follow redirects.
Web security
- Strict content security policy on the dashboard and checkout. Neither can be framed.
- Uploaded logos are re-encoded as PNG and served with their own sandboxing policy. SVG is refused.
- The checkout only shows data for its own payment and is rate limited.
Updates and vulnerability reports
- Server admins see a banner when a newer version exists, and a red one when a security advisory affects the running version. Critical and high advisories are also emailed. See Updates and advisories.
- Advisories are published at coldtill.com/security. Security fixes are free to install even after your update year ends.
- Upgrade as described in Install.
- CI runs
govulncheck,gosec, gitleaks andnpm auditon every push. To run them yourself, see How to get Coldtill audited. - Report vulnerabilities privately to security@coldtill.com (see coldtill.com/.well-known/security.txt), not in public issues.