Get started

Secrets Management for Retail Quants: Beyond Hardcoded API Keys

Most solo traders keep exchange keys in a config file that follows the code around. Here is how to move to something that survives a public push and a bad day.

The first time I watched a key get scraped, I did not believe how fast it happened. A friend pushed a small backtesting script to a public GitHub repo to show someone a chart, forgot that the config file at the bottom of the tree still had his exchange keys in it, and within the same evening he had a login attempt from an address he had never seen. He had not tweeted the repo. He had not shared the link with more than one person. Bots crawl new public commits continuously, and there are well-known scanners that pattern-match on things that look like API keys the moment a commit lands. The window between pushing a secret and someone finding it is often measured in minutes, sometimes less.

So the honest starting point for most retail quants is that the config file with the keys in it is a loaded gun pointed at your own account, and the safety is that you just have not pushed it anywhere yet. That is not a plan. It is a habit that has not failed you yet. The good news is that fixing it is mostly mechanical, and none of it requires a security background.

Get the key out of the code first

The single most useful move is to stop typing the key anywhere a file-diff can see it. In practice that means reading secrets from the environment at runtime rather than from a variable in your source. Your code asks the operating system for an exchange key by name, and the actual value lives somewhere the repo never touches.

The everyday version of this is a local file, usually named something like .env, that holds lines of KEY=value pairs. Your program loads it on startup. The crucial part is that this file is listed in .gitignore so it never gets committed, and you keep a second file, .env.example, that has the same key names with the values blanked out. The example file is safe to commit and it doubles as documentation, so when you come back to the project in three months you know exactly which secrets it expects without having to guess.

A rule of thumb I use: if a string would let someone move money or read my positions, it should never appear in a file that git is tracking. Not in code, not in a notebook, not in a commented-out line I meant to delete. Notebooks are a common trap here because the value gets baked into the saved output cells even after you clear the code, so a key you printed once can live in the .ipynb long after you forgot about it.

gitignore hygiene and a scanner that runs before you push

Ignoring the secret file is necessary but it is not enough on its own, because the failure mode is almost always the file that is not on your list yet. A dumped credentials file, a config.local.json you created in a hurry, an export from some tool. The defense that actually catches these is a scanner that runs automatically before the commit leaves your machine.

Pre-commit scanning is the piece most solo traders skip, and it is the one that would have saved my friend. The idea is a hook that runs every time you commit and refuses the commit if it spots something that looks like a secret. Tools in this space (gitleaks and similar) know the shapes of common keys and will block on them. The important word is before. A scanner that runs in CI after you have already pushed is better than nothing, but by then the secret is already public and the only correct response is to assume it is burned and rotate it. Catching it on your own laptop, before the push, is the difference between a mild annoyance and a bad night.

Here is the short checklist I run when I set up a new trading project:

  • Add .env, *.local.*, and any credentials file pattern to .gitignore before writing a single line that touches a key.
  • Commit a blanked .env.example so the required secrets are documented without being exposed.
  • Install a pre-commit secret scanner and actually run it once against the whole history, not just new commits, because old secrets do not remove themselves.
  • Clear notebook outputs before committing, or strip them automatically with a hook.

Separate dev keys from production keys

This one gets skipped because it feels like overhead until the day it saves you. Most exchanges let you mint multiple API keys with different permission sets, and many offer a testnet or sandbox with its own separate keys entirely. Use them. The key you develop and experiment with should be scoped as tightly as it can be, ideally read-only, ideally on a sandbox where a mistake costs nothing. The key that can place live orders should be a different key, minted separately, with withdrawal permission turned off unless you have a very specific reason for it to be on.

The reason this matters is not tidiness, it is blast radius. When you are iterating on a strategy you are copying keys into scratch scripts, pasting them into a terminal, leaving them in your clipboard, running half-finished code that might do something dumb. That is exactly the environment where a key leaks or fires an unintended order. If the key that is exposed to all that risk is a read-only sandbox key, a leak is a shrug. If it is your live trading key with withdrawal rights, a leak is a wire transfer out of your account. Same amount of work to leak either one. Wildly different consequences.

IP allowlisting is the cheap extra layer on top of this. If your exchange supports restricting a key to a specific IP address, turn it on for anything that can trade. A key scraped from a public repo is far less useful to an attacker if the exchange will only honor it from your server's address. It is not a complete defense, addresses can be spoofed in some scenarios and your own IP can change, but it raises the cost of a stolen key enough to matter.

What to do the moment you think a key leaked

Assume the worst and move fast, because the scanners already have. The correct order is: revoke the key at the exchange immediately, then mint a new one, then figure out how it leaked. People get this backwards and spend twenty minutes investigating while the key is still live. Revoke first. Investigate second. A revoked key is worthless to whoever grabbed it, and you can always reconstruct the story later.

Deleting the file and pushing a new commit does not help, and this trips people up constantly. Git keeps history, so the secret is still sitting in an earlier commit that anyone can check out. Once a secret has been pushed to a public repo, the only safe assumption is that it is compromised, full stop, and rewriting history to scrub it is a distant second priority to rotating the actual credential. The credential is the thing that has value. The commit is just where it was found.

None of this is exotic. Environment injection, a scanner before the push, separate keys for separate risk levels, and a revoke-first reflex will put you ahead of most retail setups I have seen. When I built the execution side of Blockcircle, routing non-custodially across a large number of exchanges, the boring key hygiene mattered more than any clever part of the system, because the clever parts fail loudly and the key handling fails silently. You want the thing that guards your account to be dull, tested, and impossible to forget, which is exactly why it belongs in a hook that runs whether you remember it or not.

WRITTEN BY
Blockcircle Team

Financial market intelligence covering crypto, stocks, commodities, precious metals, and prediction markets.