AAlphaBot
← Blog

Hard-rejecting withdraw-enabled API keys: the technical deep-dive

2026-05-06 · ~7 minute read

Most trading platforms accept API keys with withdraw permission enabled. They show a yellow warning banner, ask you to type “I understand”, and store the key anyway. We do not do that. If your key has withdraw permission, we refuse it at onboarding and never store it. This post explains why, how, and what the alternative actually costs you.

The threat model in one paragraph

Any system that holds a withdraw-enabled API key is one credential leak away from emptying user accounts. The leak does not have to be a database breach — it can be a compromised employee laptop, a misconfigured backup, a side-channel in a third-party dependency, or a process-memory dump landing in an unencrypted log. Every one of those has happened to companies larger than us. The only durable defense is: do not have the keys to anyone's funds in the first place.

How detection works

On the first authenticated call after the user pastes a key, we make an exchange-specific permission-introspection call. Most major venues expose either:

  • A dedicated “query API key info” endpoint that returns the permission set as a structured field, or
  • An account-info endpoint where the key's permission scope is included as part of the response payload.

We look for any flag that grants withdraw, internal transfer, sub-account transfer out, or universal transfer out — venues use different names for the same idea. If any of those are present, we discard the secret in memory immediately, return a structured error to the UI, and never write the key to our database.

Why hard-reject instead of warn

Warnings get clicked through. We have watched the data on this for years across multiple products. The base rate of users typing “I understand” without reading a single word above it is somewhere north of 80%. A warning that 80% of users ignore is not a security control; it is a liability shield.

Hard rejection is the opposite. It puts the friction in the right place: the user goes back to the exchange, creates a new key with trade permission only, comes back, and pastes that one. The whole loop costs the user maybe two minutes. It costs the attacker — in the worst-case credential leak scenario — the entire account.

What the warn-instead-of-reject path actually costs you

The warn-and-store pattern looks user-friendly until you ask: what does the worst-case look like? You get one hostile employee, one supply-chain compromise, one side-channel in a transitive dependency, one mis-scoped log, and the answer is that withdraw-enabled keys are exactly as good as the weakest person and the weakest piece of code that ever touched them.

Hard-rejecting flips that calculus. Even in a worst-case full database compromise, the attacker walks away with read+trade keys. Trade-only keys can do damage — they can blow positions, churn fees, dump open positions into illiquid pairs — but they cannot withdraw. The funds stay on the exchange. That is not zero risk, but it is the risk you signed up for when you connected an automated trading agent in the first place.

What we still cannot defend against

We are not pretending hard rejection is a complete security posture. It is one control. The full set is documented on our security page: KMS envelope encryption with per-user data keys, decrypted secrets that live only in process memory and only for the duration of an API call, append-only hash-chained audit log, CI-tested cross-user isolation, and a vulnerability disclosure program. Hard-rejecting withdraw permission is just the most visible piece of that stack — and the easiest one to verify yourself, because you can watch us refuse a withdraw-enabled key in real time during onboarding.