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.

  • mainthe code currently trading live. Nothing unvalidated ever lands here
  • dev — work on the next version. Where you validate with paper trading and backtests
  • feature/strategy-name — new strategy experiments. Merge into dev if 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 .gitignore before 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.

댓글

이 블로그의 인기 게시물

한국투자증권 KIS API로 실시간 시세 받기 (WebSocket 실전)

파이썬으로 업비트 API 연동하기 — 시세 조회부터 주문까지 기초

Go로 자동매매 신호봇 프레임워크 설계하기