Secrets Management for Trading-Bot API Keys
A venue API key for Polymarket or Kalshi is not a password you can reset with an email link. It signs orders and, depending on scope, can move money. Treat it like account credentials, because that is what it is. Stripe puts it plainly for its own keys, and the logic carries straight over to trading venues.
This post covers the four things that actually matter for a trading bot: keeping keys out of source control, isolating them per environment, encrypting them at rest, and scoping them to the minimum permission the strategy needs. It closes with how a bring-your-own-key runtime like Banger fits the model without adding a new place for keys to leak.
Rule zero: never commit the key
The single most common way credentials leak is git. A developer adds a .env file or hardcodes a key during a debugging session and commits it. Once that reaches a public repo, automated scanners find it fast.
The scale is not theoretical. The 2024 GitGuardian State of Secrets Sprawl report (covering 2023 data) found over 12.8 million new secret occurrences on public GitHub in a single year, and notes the real total across private repos and other platforms is far higher. Palo Alto Networks Unit 42 has documented that attackers routinely harvest credentials from public repos within five minutes of exposure.
The prevention is boring and effective. Add your secrets file to .gitignore before you ever write a real value into it, and commit a template instead.
# .gitignore
.env
.env.*
!.env.example
# .env.example (committed, no real values)
KALSHI_API_KEY_ID=
KALSHI_PRIVATE_KEY_PATH=
POLYMARKET_API_KEY=
POLYMARKET_API_SECRET=
POLYMARKET_API_PASSPHRASE=A committed .env.example lets a teammate know which variables are required without exposing any real value. Note the gotcha: dotenv-style files are plain text. python-dotenv does not encrypt anything, so the .env file itself is only as safe as the machine and the .gitignore protecting it.
If a key does hit a repo, treat it as compromised the instant it lands. GitHub's own guidance is that removing the secret, pushing a new commit, or deleting the repo do not prevent exploitation. You have to revoke and reissue. Enable push protection and secret scanning so a known key format is blocked before the commit ever reaches a repository.
Isolate environments
Load keys from the process environment at startup, not from source. In Python that is os.getenv, optionally hydrated from a .env file in development by python-dotenv. Keep separate credentials per environment so a mistake in testing cannot touch a live account.
- Paper vs live: use a distinct key (or a paper-only key where the venue offers one) for anything that is not trading real size. If your runtime supports a paper mode against the live order book, run there first and keep the live key out of the process entirely until you are ready.
- Dev vs prod: never reuse a production key on a laptop. Laptops get lost, cloned to backups, and pasted into chat.
- One key per bot: sharing a key across services makes it impossible to tell which one leaked. OWASP calls this out directly. Shared secrets make identifying the source of a compromise very hard.
- Validate at startup: fail fast if a required variable is missing or malformed, so a half-configured bot never sends a live order with a fallback default.
In CI/CD, do not pass secrets in a committed .env. Inject them at runtime through the platform's secret store (GitHub Actions secrets, GitLab CI/CD variables, a cloud secrets manager) so they never persist in a file on disk.
Encrypt at rest, and prefer a real store
A plaintext .env in your home directory is fine for a solo dev on a locked machine, and honest about its limits. Anything beyond that should use a system that provides encrypted storage plus access controls: a KMS, HashiCorp Vault, AWS Secrets Manager, or an equivalent. Stripe recommends copying a freshly created secret key straight into a KMS that handles it with encryption and access controls, and the same pattern applies to venue keys.
The deeper point from OWASP is that secrets fail as a lifecycle, not as a storage choice. Ownership, access scoping, rotation, revocation, and auditability are what keep keys governable. A vault that no one rotates is just an encrypted way to hold a stale credential.
Least privilege and rotation
Scope every key to the narrowest permission the strategy needs. If the venue lets you create a key that can trade but not withdraw, use it. If it supports IP allowlists, pin the key to the box the bot runs on. The goal is to shrink the blast radius: a leaked trade-only key locked to one IP is a far smaller problem than an unrestricted one.
Rotate on a schedule, not just after an incident. Common guidance is a maximum lifetime around 90 days for long-lived API keys, with automated rotation because manual rotation does not happen consistently. Rotation under incident pressure is where mistakes get made, so practice it in a staging setup and document the revocation procedure for each venue before you need it. And never log the key. Redact credentials out of request and response logging so they do not end up in a log aggregator you forgot to lock down.
Where a bring-your-own-key runtime fits
Banger runs your strategy against your own venue accounts. It never custodies funds and never holds your keys on a server. You bring your own Polymarket or Kalshi keys, they stay in your environment, and the runtime reads them from the process at execution time. That keeps the trust boundary where it belongs: on your machine, under your .gitignore, in your vault, not in a third party's database that becomes a single juicy target.
The practical workflow lines up with everything above. Paper-trade first against the live order book, keep the live key out of the process until the strategy behaves, then run it with a declarative risk envelope: per-trade cap, daily loss stop, max open positions, and a kill switch. A leaked key is a security failure. An unbounded strategy with a valid key is a financial one, and the risk envelope is your backstop for the second class of problem.
# keys come from the environment, never from the file you commit
pip install bangertrades
export KALSHI_API_KEY_ID=... # loaded at runtime, gitignored source
banger run strategy.py --paper # validate against the live book first
# then, once it behaves, run live with your risk envelopeThe short version
- Add secrets files to .gitignore first, commit a .env.example, and turn on push protection and secret scanning.
- Load keys from the environment at runtime; keep separate keys per environment and one key per bot.
- Store production keys in a KMS or vault with encryption, access controls, and audit logs, not a plaintext file.
- Scope keys to trade-only, pin to an IP where possible, rotate on a schedule, and never log them.
- If a key leaks, revoke and reissue immediately. Deleting the commit does not undo the exposure.
Sources
- Best practices for managing secret API keys - Stripe Docs
- Secrets Management - OWASP Cheat Sheet Series
- OWASP Secrets Management & Environment Variables Best Practices | AquilaX
- State of Secrets Sprawl 2024 - GitGuardian Blog
- GitHub Secret Scanning Now Watches All Public Repos for Leaked Enterprise Keys
- Remediating a leaked secret in your repository - GitHub Docs
- OWASP secrets management cheat sheet for production systems
- API Security Best Practices - StackHawk
- The .env File: A Complete Guide to Environment Variables - env.dev
- Managing Environment Variables With dotenv - Configu
- How to Use .env Securely in DevOps Projects - DEV Community
- API key rotation best practices - CIAM Compass