The Safety Pack
Five files that stop the most common ways AI-built bots lose people money: leaked keys, runaway orders, and "it was only supposed to be a test."
Why this exists
AI coding agents are fast, and they'll happily do something dangerous if nothing stops them. Two real 2026 examples:
- A Claude Code session served a project folder over a local web server, exposing a
.envwith a private key. The reported loss was $1,324 (anthropics/claude-code #34133). - A key was committed by an agent and swept from the wallet. The author published a post-mortem (Secret Mars post-mortem).
None of this needs a genius attacker. Bots scan GitHub for keys around the clock. The fix is boring: keep keys out of the repo, cap what the bot can do, and make stopping it a one-liner.
The three non-negotiables
Env vars or a secrets manager. Key files live outside the project folder. Nothing secret in git, prompts or logs.
Max per order, max exposure, max daily loss, max order rate. Enforced in code before every order.
touch KILL stops new orders. A cancel-all command clears resting orders through the venue's official endpoint.
1. The rules file (CLAUDE.md / AGENTS.md / Cursor rule)
Your AI coding tool reads this on every task. It sets paper mode as the default, forces hard limits and a kill switch, and tells the agent never to touch your secrets.
# BOT SAFETY RULES (BotCookery Safety Pack v1, Oct 2026)
# Save as CLAUDE.md (Claude Code), AGENTS.md (Codex and most agents)
# or .cursor/rules/bot-safety.mdc (Cursor). Keep it in the repo root.
You are helping me build a trading or prediction-market bot that runs on MY
machine, on MY account. Follow these rules even if I ask you not to.
## Secrets
- Never read, print, log, echo, copy or summarize the contents of .env, *.pem,
*.key, or any file that holds API keys, private keys or seed phrases.
- Load credentials only from environment variables (or a path in an env var
for PEM files). Never hardcode them. Never put them in prompts, tests,
fixtures, comments, commit messages or README examples.
- Never start a web server, file server or tunnel that could expose the
project folder (no `python -m http.server` in the repo root).
- Never make the repo public, never push without my explicit OK, and never
add or edit CI that echoes secrets.
## Money
- PAPER/DEMO MODE IS THE DEFAULT. Live trading requires BOTH a --live flag
AND an interactive typed confirmation. Never remove or bypass that gate.
- Enforce hard limits in code, read from config: max contracts per order,
max total open exposure, max daily loss, max orders per minute. Reject any
order that breaks a limit, log it, and do not retry it.
- Before every order: check the KILL file / BOT_KILL env var. If present,
place nothing.
- Provide a `cancel-all` command that cancels every resting order using the
venue's official cancel-all endpoint.
- Use client order IDs where the venue supports them (Kalshi) so retries
can't double-submit. Polymarket US orders have no client order ID: record
each order locally before sending and never auto-retry an order after a
timeout or 5xx; check open orders instead.
- Retry reads and cancels with bounded exponential backoff on 429 and 5xx.
Never retry rejected orders in a loop.
- Track daily loss as a positive dollar amount already lost, not signed PnL.
## Venue rules
- Use only official, documented endpoints from docs.kalshi.com or
docs.polymarket.us. If you are unsure an endpoint exists, say so; do not
invent one.
- Do not build features that trade for other people, copy other traders,
share or republish exchange data, or get around geoblocks or KYC.
- For international Polymarket endpoints: read-only market data only, and
check GET https://polymarket.com/api/geoblock before anything else.
## Code hygiene
- Log every decision (inputs, order intent, limit checks) to a local file
without secrets.
- Write tests for the limit checks and the kill switch before writing the
strategy.
- Prefer small, readable files over clever abstractions. Explain anything
that touches money in a comment.
2. .gitignore
Add this before your first commit. Keys that never get committed can never leak from GitHub.
# secrets
.env
.env.*
!.env.example
*.pem
*.key
secrets/
# bot state
KILL
logs/
paper_ledger*.json
*.sqlite
# python / node
__pycache__/
.venv/
node_modules/
3. Block your AI tool from reading secrets (.claude/settings.json)
Claude Code deny rules use gitignore-style patterns. Cursor and others have similar ignore settings. Deny rules are a seatbelt, not a vault: a shell command can still read files, so the best move is to keep key files outside the project folder entirely (for example ~/.config/kalshi/key.pem, with only the path in an env var).
{
"$schema": "https://json.schemastore.org/claude-code-settings.json",
"permissions": {
"deny": [
"Read(**/.env)",
"Read(**/.env.*)",
"Read(**/*.pem)",
"Read(**/*.key)",
"Read(./secrets/**)",
"Bash(git push *)"
]
}
}
4. Pre-commit secret scanning (gitleaks)
Blocks any commit that looks like it contains a key. Install pre-commit, save this file, run pre-commit install.
# .pre-commit-config.yaml (then run: pre-commit install)
repos:
- repo: https://github.com/gitleaks/gitleaks
rev: v8.30.1
hooks:
- id: gitleaks
5. Hard limits + kill switch (risk.py)
Call check_order() before every single order, paper or live. Pass daily loss as a positive number (dollars lost today), not signed PnL, or the cap can never trip. The 15-second contracts limit mirrors a Kalshi order group; create the real group too. touch KILL stops new orders. The cancel-all endpoints clear what is already resting.
# risk.py: hard limits + kill switch. Call check_order() before ANY order, paper or live.
import os, json, pathlib
LIMITS = json.loads(pathlib.Path("limits.json").read_text())
# limits.json example (start this small):
# {"max_contracts_per_order": 1, "max_open_exposure_usd": 5,
# "max_daily_loss_usd": 2, "max_orders_per_minute": 3,
# "order_group_contracts_limit": 3}
REQUIRED = ["max_contracts_per_order", "max_open_exposure_usd", "max_daily_loss_usd",
"max_orders_per_minute", "order_group_contracts_limit"]
missing = [k for k in REQUIRED if k not in LIMITS]
if missing: # fail closed: no limits, no trading
raise SystemExit(f"limits.json is missing {missing}")
class RiskError(Exception):
pass
def kill_switch_on() -> bool:
return pathlib.Path("KILL").exists() or os.getenv("BOT_KILL") == "1"
def check_order(contracts: float, price_usd: float, open_exposure_usd: float,
daily_loss_usd: float, orders_last_minute: int,
contracts_last_15s: float = 0) -> None:
"""Raise RiskError if this order breaks any limit.
daily_loss_usd is a POSITIVE number: dollars already lost today
(e.g. 2.0 after losing $2), not signed PnL. Compute it as
max(0, -realized_pnl_today_usd) if your ledger tracks PnL.
contracts_last_15s is what this bot sent in the last 15 seconds.
"""
if kill_switch_on():
raise RiskError("Kill switch is on. No orders.")
if daily_loss_usd < 0:
raise RiskError("daily_loss_usd must be a positive loss amount, not signed PnL.")
if contracts <= 0 or contracts > LIMITS["max_contracts_per_order"]:
raise RiskError("Order size outside limits.")
if not 0 < price_usd < 1:
raise RiskError("Price must be between $0 and $1.")
if open_exposure_usd + contracts * price_usd > LIMITS["max_open_exposure_usd"]:
raise RiskError("Exposure cap hit.")
if daily_loss_usd >= LIMITS["max_daily_loss_usd"]:
raise RiskError("Daily loss cap hit. Done for today.")
if orders_last_minute >= LIMITS["max_orders_per_minute"]:
raise RiskError("Order rate cap hit.")
# Local mirror of the Kalshi order group's limit. Kalshi counts FILLED
# contracts in a rolling 15-second window and cancels the whole group
# when the total goes over the limit; we count contracts SENT, which
# blocks earlier. Also create the group on Kalshi (POST
# /portfolio/order_groups/create with contracts_limit) and pass its
# order_group_id on every order: that's the server-side backstop.
if contracts_last_15s + contracts > LIMITS["order_group_contracts_limit"]:
raise RiskError("15-second contracts limit hit.")
def require_live_confirmation(live_flag: bool) -> bool:
"""Returns True only for --live AND a typed confirmation."""
if not live_flag:
return False
typed = input("LIVE MODE. Real money. Type 'I accept the risk' to continue: ")
return typed.strip() == "I accept the risk"
# Venue cancel-all endpoints (official, checked Oct 19, 2026):
# Kalshi: DELETE {BASE}/portfolio/events/orders (signed request)
# Polymarket US: POST https://api.polymarket.us/v1/orders/open/cancel (signed request)
6. Use the venue's own safety tools
- Kalshi demo environment. Mock funds and separate credentials at demo.kalshi.co. Demo keys only work against
https://external-api.demo.kalshi.co/trade-api/v2. (docs) - Kalshi order groups. Server-side auto-cancel: if fills in a group go over a contracts limit within a rolling 15-second window, Kalshi cancels every resting order in the group and blocks new ones until you reset it. (docs)
- Kalshi
reduce_onlyandcancel_order_on_pauseorder flags, plus cancel-all atDELETE /portfolio/events/orders. (docs) - Polymarket US order preview.
POST /v1/order/previewvalidates an order and shows expected fills without placing it. That's the closest thing to a sandbox for retail API users, and the core of our Polymarket paper mode. (docs) - Polymarket US cancel-all at
POST /v1/orders/open/cancel, and revoke compromised keys at polymarket.us/developer.
7. Stay inside the rules
- Kalshi Developer Agreement: API use is limited to your own trading. No trading for other members, no sharing or republishing API data without written permission, no sublicensing. (PDF)
- Polymarket US requires identity verification before you can create API keys. Follow its market integrity policy: no trading on confidential information.
- International Polymarket is close-only for the United States. Check
GET https://polymarket.com/api/geoblock, and never use a VPN to get around it. US users should build on Polymarket US. (docs)
8. Before you run anyone else's bot repo
- Read every file that touches keys, wallets or network calls. If you can't, have your AI tool explain each one line by line without giving it your keys.
- Red flags: asks for a private key or seed phrase, obfuscated or base64 blobs,
postinstallscripts, unknown domains, "paste your key here to unlock profits." - Run it first with demo or read-only credentials only, in a fresh folder or container.
Pre-live checklist
- At least a week of paper or demo logs, reviewed by you, with no surprise orders.
- Limits set to an amount you'd be fine losing all of. Then halve it.
- Kill switch and cancel-all tested in demo/paper and timed.
git log -p | grep -i keyand gitleaks both come back clean, and the repo is private.- Separate API key for the bot, which you can revoke without touching your main login.
- You know which state/venue rules apply to you, and you're trading only where it's legal.
The Daily Bot Recipe
One new build recipe, one safety tip, and API changes worth knowing about. Free. No picks, ever.