This file is binding for all agents and contributors. Every rule here exists because a real violation could be catastrophic. This system manages real capital. A leaked IB password = real money at risk.
Credentials never go in source code. Ever.
Not in docker-compose.prod.yml. Not in Python source. Not in SQL seeds.
Not in Grafana dashboard JSON. Not even temporarily. Not even for testing.
All secrets live in /opt/pantheon/.env on the VPS and are injected at
container start via env_file. The .env file is .gitignored and never
committed.
| Credential | Where it lives | How services access it |
|---|---|---|
ANTHROPIC_API_KEY |
/opt/pantheon/.env |
Injected into pantheon_zeus via env_file |
SUPABASE_URL |
/opt/pantheon/.env |
Injected into pantheon_zeus, pantheon_hermes, pantheon_dashboard |
SUPABASE_SERVICE_ROLE_KEY |
/opt/pantheon/.env |
Backend only — Zeus, Dashboard, Hermes. Never in frontend. |
SUPABASE_ANON_KEY |
/opt/pantheon/.env |
Grafana datasource only (read-only) |
SUPABASE_DB_PASSWORD |
/opt/pantheon/.env |
Never in source — injected into any direct-DB service |
IB_PASSWORD |
/opt/pantheon/.env |
Injected into pantheon_ibgateway as TWS_PASSWORD |
IBC_TOTP_SECRET |
/opt/pantheon/.env |
Injected into pantheon_ibgateway as TWS_2FA_SECRET_XML |
ZEUS_API_KEY |
/opt/pantheon/.env |
HTTP bearer for /run, /halt, /alert endpoints |
HERMES_API_KEY |
/opt/pantheon/.env + Railway env vars |
Icarus → Hermes authentication |
TELEGRAM_BOT_TOKEN |
/opt/pantheon/.env |
Argus alert bot — never log this |
TELEGRAM_CHAT_ID |
/opt/pantheon/.env |
Target chat — treat as sensitive (can be scraped) |
UPSTASH_REDIS_REST_TOKEN |
/opt/pantheon/.env + Railway env vars |
Shared Zeus/Hermes Redis cache |
GRAFANA_ADMIN_PASSWORD |
/opt/pantheon/.env |
Grafana admin login |
CLOUDFLARE_API_TOKEN |
/opt/pantheon/.env |
DNS + CDN management |
FRED_API_KEY |
/opt/pantheon/.env |
Macro data fetches |
IB_USERNAME |
/opt/pantheon/.env |
IB account login — not a password but treat as PII |
Nothing in this table belongs in a .py, .yml, .json, .sql, or .md source file.
The production compose file lives at infra/hetzner/docker-compose.prod.yml
and is committed. It must never contain real credential values.
# ✅ CORRECT — reference from env_file
services:
ibgateway:
env_file: /opt/pantheon/.env
environment:
- TWS_USERID=${IB_USERNAME}
- TWS_PASSWORD=${IB_PASSWORD}
# ❌ WRONG — hardcoded value in compose file
services:
ibgateway:
environment:
- TWS_PASSWORD=MyRealPassword123The env_file directive reads from /opt/pantheon/.env on the server.
The local .env at repo root is for local development only and is also
.gitignored.
# ✅ CORRECT — read from environment at runtime
import os
api_key = os.environ["ANTHROPIC_API_KEY"]
ib_password = os.getenv("IB_PASSWORD")
# ❌ WRONG — will be caught by pre-commit hook
api_key = "sk-ant-api03-..."
ib_password = "MyRealPassword"The SUPABASE_SERVICE_ROLE_KEY is backend-only. It must never appear in:
- Dashboard frontend code (
dashboard/frontend/) - Any API response body
- Log output (mask it:
key[:8]...)
Migration files in infra/supabase/ are committed. They must never contain:
- Real API keys or tokens
- Seed data with real user PII
- Hardcoded passwords in function bodies
-- ✅ CORRECT — no credentials, references only role names
GRANT SELECT, INSERT ON public.signals TO service_role;
-- ❌ WRONG — would expose a real token in git history
INSERT INTO config (key, value) VALUES ('api_key', 'sk-ant-api03-...');Every CREATE TABLE needs explicit GRANTs or PostgREST returns 403.
Use infra/supabase/TEMPLATE_migration_NNN.sql as your starting point.
Interactive Brokers credentials control real money. Extra caution:
IB_PASSWORDandIBC_TOTP_SECRETare never logged — mask them everywhere- Paper vs live ports — Zeus connects to port
4002(paper). Live port4001is wired but disabled. Never changeIB_PORTto live in prod without an explicit Vault unlock + human confirmation mock_execution— whentrueinconfig/settings.json, Ares skips the actual IBKR order submission. Always verify this flag before pushing config changes- Vault money never moves autonomously — Zeus is hard-blocked from touching the Vault account regardless of what any signal or LLM says
When a credential is compromised or needs rotation:
# Generate at the service provider, then update the VPS:
ssh root@187.124.14.81 "nano /opt/pantheon/.env"
# Edit the key, save, then restart affected services:
docker compose -f /opt/pantheon/docker-compose.prod.yml up -d --no-deps zeus hermes# 1. Rotate in Supabase Dashboard → Settings → API → Rotate service_role key
# 2. Update VPS:
ssh root@187.124.14.81 "nano /opt/pantheon/.env" # update SUPABASE_SERVICE_ROLE_KEY
# 3. Update Railway env vars for Hermes (Railway Dashboard → Service → Variables)
# 4. Restart zeus + hermes:
docker compose -f /opt/pantheon/docker-compose.prod.yml up -d --no-deps zeus hermes dashboard# 1. Change at IBKR Account Management
# 2. Update VPS:
ssh root@187.124.14.81 "nano /opt/pantheon/.env" # update IB_PASSWORD
# 3. Restart ibgateway (will re-authenticate with new password):
docker compose -f /opt/pantheon/docker-compose.prod.yml up -d --no-deps ibgateway# Generate new key:
python3 -c "import secrets; print(secrets.token_hex(32))"
# Update VPS .env, then restart zeus:
ssh root@187.124.14.81 "nano /opt/pantheon/.env"
docker compose -f /opt/pantheon/docker-compose.prod.yml up -d --no-deps zeus
# Update n8n workflow HTTP node headers to use new key
# Update local .env (dev only) — never commitTreat it as compromised immediately. Rotate first, purge second.
# 1. Rotate the credential NOW (see above) — this makes the exposed value useless
# 2. Purge from git history:
pip install git-filter-repo
git filter-repo --replace-text <(echo "EXPOSED_VALUE==>REDACTED") --force
git push origin --force --all
# 3. Force all collaborators to re-clone or hard-reset:
git fetch origin && git reset --hard origin/mainInstalled at .git/hooks/pre-commit. Install after cloning:
bash scripts/install-hooks.shBlocks commits containing:
- Anthropic API keys (
sk-ant-api03-) - Supabase service role keys (
sb_secret_) - IB/IBKR passwords (pattern-matched against known format)
- Upstash tokens (
AAIgprefix, base64 Redis tokens) - Telegram bot tokens (
\d+:AAHpattern) - TOTP secrets (base32 strings > 20 chars in credential context)
- Real
.envfiles (allows.env.example)
Run on the server to verify all required keys are present before deploying:
python3 scripts/check_env.pyIf you are an AI agent (Zeus, Icarus, Claude, etc.) working on this codebase:
-
Never write a credential value into any tracked file. Use
os.environreferences in Python,${VAR_NAME}substitution in compose files. -
Never log credential values. If you must log a key for debugging, log only the first 8 characters:
key[:8] + "...". -
Never move Vault money. The Vault account is read-only to all automated agents. There is no override.
-
Never change
IB_PORTto a live port without explicit human confirmation in the chat. Paper = 4002. Live = 4001. -
Before pushing, run
bash scripts/install-hooks.shonce, then the pre-commit hook runs automatically on everygit commit. -
If you generate a new credential (API key, token, secret), write it to:
/opt/pantheon/.envon the VPS (via SSH)- Railway env vars (via dashboard) for Hermes/Railway services Never to any file that gets committed.
| Credential | Status | Last rotated |
|---|---|---|
ANTHROPIC_API_KEY |
✅ Active | — |
SUPABASE_SERVICE_ROLE_KEY |
✅ Active (sb_ format) | — |
IB_PASSWORD |
✅ Active | — |
IBC_TOTP_SECRET |
✅ Active | — |
ZEUS_API_KEY |
✅ Active | — |
HERMES_API_KEY |
✅ Active | — |
TELEGRAM_BOT_TOKEN |
✅ Active | — |
UPSTASH_REDIS_REST_TOKEN |
✅ Active | — |
CLOUDFLARE_API_TOKEN |
✅ Active | — |