Git Secrets Management for Trading Bots: Never Commit Your API Keys
Trading bot code carries secrets wired directly to money: exchange API keys, secrets, wallet private keys. Push one to a public repository by accident and your assets can be gone in minutes. That isn't hyperbole—bots that scrape leaked keys off GitHub are a real, running thing.
So while half this article is about branches, what comes first is how not to commit keys. The order matters.
Step 1 — Write .gitignore Before You Write Code
The thing to do right after git init isn't writing code; it's writing .gitignore. Git keeps following a file once it has started tracking it, so adding the rule later is too late.
# === Secrets — highest priority ===
config.ini
config.yaml
.env
*.key
*.pem
secrets/
keystore/
# === Runtime artifacts ===
logs/
*.log
data/
*.db
__pycache__/
.venv/
Cast the patterns wide. Block only config.ini and someday you'll create config_real.ini and sail right through. Match config*.ini instead, and carve out just the example file you intend to share.
config*.ini
!config.example.ini # leading ! = track this one as an exception
Step 2 — The Example-File Pattern
Exclude the config file entirely and there's no way to know what needs filling in on a new machine. So commit one skeleton file with the values emptied out.
# config.example.ini — the only one that goes into the repository
[API]
KEY = YOUR_API_KEY_HERE
SECRET = YOUR_API_SECRET_HERE
[TRADE]
MaxInvestPerOrder = 100000
EntryGapPct = 0.50
On a new server you run cp config.example.ini config.ini and fill in the real values. You get documentation and a safety net in one move. Better still, add a single validation line so the bot errors out immediately if YOUR_API_KEY_HERE is still sitting there at startup.
The safer approach is environment variables. The file doesn't exist at all, so you can't commit it by mistake.
import os
API_KEY = os.environ.get("EXCHANGE_API_KEY")
if not API_KEY:
raise RuntimeError("EXCHANGE_API_KEY environment variable is not set")
Never put a key in code, not even as a default value. Code like os.environ.get("KEY", "abcd1234") is a textbook leak path.
Step 3 — If You Already Committed a Key
This is the most important section in the article. Get the order wrong and your response is worthless.
The first thing to do is not a Git operation. It is revoking that key at the exchange.
The reasoning is clear. From the moment it was committed until the moment it's revoked, treat that key as already exposed. If the repository was public even briefly, a scraping bot may already have it, and GitHub caches pushed data in multiple places. No matter how cleanly you scrub the history, a key that has already been copied cannot be recalled.
The correct order:
- ① Revoke and reissue the key immediately — from the exchange dashboard. If it was a wallet private key, create a new wallet and move the assets right away
- ② Audit your activity — check for unauthorized withdrawals or orders
- ③ Only then clean up the Git history
Here's how to do ③. If you haven't pushed yet and it's the most recent commit, it's easy.
git rm --cached config.ini # untrack only; the file stays on disk
echo "config.ini" >> .gitignore
git commit --amend
If it landed several commits back, you need to rewrite history. git-filter-repo is the standard tool.
# Remove the file from the entire history
git filter-repo --path config.ini --invert-paths
# Force it to the remote (if others are collaborating, announce this first)
git push origin --force --all
A caution: force-pushing conflicts with everyone else's local repository. And as said above, cleaning history is prevention of recurrence, not incident response. Skipping the revocation and only scrubbing history is the same as doing nothing.
For prevention, you can also gate commits automatically. Wire a secret scanner such as gitleaks into a pre-commit hook and the commit containing a key is blocked outright.
A Branch Strategy for a Solo Bot
No need for corporate-grade complexity. Three branches are plenty for a bot project.
main— the code currently trading live. Nothing unvalidated ever lands heredev— work on the next version. Where you validate with paper trading and backtestsfeature/strategy-name— new strategy experiments. Merge intodevif it pans out; otherwise just throw it away
The core idea is keeping main identical to what's deployed on the live server. When the bot misbehaves at 3 AM, looking at main alone should tell you with confidence what is running right now. Once that holds, rolling straight back to the last known-good commit becomes your fastest recovery.
# Tag every version that goes live
git tag -a v1.3.0 -m "Apply dynamic grid spacing adjustment"
git push origin v1.3.0
# Something breaks? Jump straight back to that point
git checkout v1.2.0
Write commit messages about "why" rather than "what". Six months from now, fix bug will mean nothing to you. Leave the reasoning instead—Raise entry threshold to 0.5% — at 0.3% fees make it a net loss—and the log itself becomes your strategy research notebook.
Summary
- Write
.gitignorebefore the code — Git keeps following whatever it has started tracking - Exclude the config and commit only
config.example.ini; environment variables are safer still - Never put a key in code, not even as a default value
- If a key gets committed: ① revoke and reissue → ② audit activity → ③ clean history. Reorder it and it's pointless
- Deleting history is prevention of recurrence, not incident response
main(live),dev(validation),feature/*(experiments) — three branches is enough- Tag every live version so you can roll back instantly
Git is a tool for protecting your code, but used carelessly it's also a tool for immortalizing your secrets. The five minutes it takes to write one .gitignore before your first commit prevents an entire incident later.
댓글
댓글 쓰기