Appearance
Key & secret management
Every key the platform holds, what an attacker could do with it, and how to rotate it.
Threat analysis: security model. Fund-safety guarantees: custody.
Blast-radius table
Ordered by severity.
| Key | Address | Worst case | Rotation |
|---|---|---|---|
| Relayer owner | 0x75a8b522FC3195e3a3570F11f111AC89c0D35975 | _authorizeUpgrade on 22 chains — replace the implementation and bypass every guarantee | transferOwnership, per chain |
Autopilot owner BACKEND_PRIVATE_KEY | 0x2Ed05570214f6C0F7612B580aB37C163076e0162 | UUPS-upgrade the Autopilot vault; set fees; curate routers | transferOwnership |
| DCA owner = executor | 0x5935745431D1385ae1a9CE2644fb5f5d1f140459 | UUPS-upgrade the DCA vault | transferOwnership |
TX_SIGNER / POL_TX_SIGNER | Same as relayer owner | Make the relayer .call an arbitrary target — spending the attacker's own funds | setSigner(address) — immediate, per chain |
PRIVATE_KEY_EXECUTOR | 0x5935…0459 | Force bad-slippage churn on agents' own positions, bounded by liquidity and the router allow-list | setExecutor(address) on both vaults |
PRESALE_MASTER_SEED | — | Derive presale wallets | Re-seed |
CLIPPERS_PAYOUT_PRIVATE_KEY | — | Drain the creator payout wallet | Replace |
| Orderly ed25519 keys | Per user | Trade a user's Orderly account. Cannot withdraw — that needs a fresh wallet signature | Regenerate per account |
| ZeroDev session keys | Per dApp project | Scoped operations on a user's smart account. User-revocable | User revokes |
| Provider API keys | — | Quota abuse, cost | Rotate at the provider |
| LLM keys | — | Cost | Rotate at the provider |
The three things that are wrong today
1. Owner keys are EOAs, not multisigs
0x75a8b522… holds upgrade authority over the relayer on 22 chains — every interactive trade on the platform. 0x2Ed05570… holds it over the Autopilot vault. A single compromised private key can replace either implementation and bypass every custody guarantee documented elsewhere in this site.
Moving both to multisigs is an explicit launch blocker for promoting Autopilot to users, and is overdue for the relayer.
2. PRIVATE_KEY_EXECUTOR doubles as the DCA vault owner
So a compromised executor key does not merely mean bad trades — it means upgrade authority over the DCA vault. setExecutor already exists, so separating them is a single owner transaction plus a config change. It has not been done.
3. copytrade-agent has committed live secrets
config.json in the external repo (~/Documents/code/personal-agents/copytrade-agent) contains a live Telegram bot token, a Helius API key, and a Hyperliquid API-wallet private key — committed to git history.
Rotating all three and blanking the file is step zero (CT-0) of that workstream. Nothing else there should start first. See copy trading.
Where secrets live
| Location | Contents |
|---|---|
VM /home/bisht/bz-backend/.env | Everything server-side |
VM /home/bisht/bz-agent/.env | LLM credentials, database URLs |
| Netlify env vars per site | VITE_SITE, VITE_API_URL, VITE_GA_MEASUREMENT_ID only |
| MongoDB, KMS-encrypted | Orderly ed25519 private keys |
| Nowhere | swap-sdk ships with zero embedded keys |
.env never leaves the VM, and never arrives on it
deploy.gcp.sh excludes it from the rsync in both directions. That is deliberate — but it means a new variable has to be added on the VM by hand:
bash
./scripts/deploy.gcp.sh ssh
nano ~/bz-backend/.env
pm2 restart bz-backend --update-env"The code deployed but the feature does not work" is almost always this. UNISWAP_API_KEY is the canonical case.
What cannot leak into a bundle
Only VITE_-prefixed variables reach the frontend bundle
And every one of them is public by construction. Never add a secret to a VITE_ variable, .env.ai, netlify.toml or netlify.ai.toml.
The reason swap-sdk can safely run in a browser is that anything key-bearing either goes through a backend proxy or is injected via configure() with a key that is safe client-side.
Rotation procedures
TX_SIGNER — do this immediately on suspicion
bash
# per chain, from the owner
setSigner(newSignerAddress)Then update TX_SIGNER (and POL_TX_SIGNER for chain 137) in the VM .env and restart.
Why this is urgent but not catastrophic
An attacker with TX_SIGNER can make the relayer .call an arbitrary target — but funds are pulled from msg.sender by transferFrom, so they spend their own money. The realistic damage is griefing and reputational, not theft of user balances.
setSigner exists precisely so you rotate first and investigate afterwards. Do not wait for confirmation.
Order matters: rotate on-chain first, then update .env. The reverse leaves the backend signing with a key the contract no longer accepts, so every swap fails.
PRIVATE_KEY_EXECUTOR
bash
# on the DCA vault (from its owner)
setExecutor(newExecutorAddress)
# on the Autopilot vault (from its owner)
setExecutor(newExecutorAddress)Then update .env, fund the new wallet with gas on each automation chain, and restart.
Fund the new executor before switching
Both crons stop silently when the executor has no gas — see monitoring.
Contract owner → multisig
bash
transferOwnership(multisigAddress)Per chain for the relayer (22 transactions). Verify with scripts/check-contract-owner.js afterwards.
Test the multisig can upgrade before transferring
Once ownership moves, _authorizeUpgrade requires the multisig. A misconfigured multisig means the contract can never be upgraded again.
Provider and LLM keys
Rotate at the provider, update .env, restart. No contract interaction.
For a key that also has a registered referrer (1inch's 0x5222…1D35, Squid's integrator id, OpenOcean's referrer), check whether the referrer registration is tied to the key.
Orderly ed25519 keys
Per user, regenerated through POST /defi/orderly/add-orderly-key. Encrypted at rest with AWS_KEY_ID_KMS.
This is the one key Blazpay holds on a user's behalf
It is scoped to Orderly's API — it can trade but cannot withdraw, because Orderly's withdrawal flow requires a fresh wallet signature. Still, treat models/orderlyAccount.js as sensitive data, and rotating AWS_KEY_ID_KMS means re-encrypting every stored key.
ZeroDev session keys
User-controlled, not platform-controlled
POST /v1/dapp/account/:projectId/revoke-session lets a user revoke unilaterally, after which their smart account is entirely self-controlled and Blazpay's automation stops. This is the correct posture and the UI should say so at setup, not in a footer.
IndexNow key
Not a secret — it is deliberately public at public/{key}.txt. But it has a sharp edge:
Never rotate and submit in the same deploy
IndexNow caches failed key validations for hours or days and returns 403 until they expire. public/{key}.txt must match KEY in scripts/indexnow-submit.mjs, and the file must be deployed and reachable before any submission. Current key: fbaf800f3098a10a73e20691138e2590. It has been rotated once already.
Auditing what is in use
bash
cd bz-backend
grep -rhoE 'process\.env\.[A-Z0-9_]+' --include='*.js' \
controllers services utils middleware v1 routes index.js \
| sed 's/process\.env\.//' | sort -uExclude archive/, new-backend/, microservices-backend/ and gateway/ — they are not mounted and contribute variables nothing reads.
bash
# what is actually set on the VM?
./scripts/deploy.gcp.sh ssh
grep -oE '^[A-Z0-9_]+' ~/bz-backend/.env | sortDiffing those two lists finds both unused variables and missing ones.
Hygiene rules
| Rule | Why |
|---|---|
Never commit a .env | The copytrade-agent situation is what happens when this slips |
| Never log a key, even truncated | Log lines end up in PM2 files and screenshots |
swap-sdk stays key-free | It ships to the browser |
Rotate on-chain before updating .env | The reverse breaks every swap |
| One key, one purpose | The executor/DCA-owner overlap is the counter-example |
| Fund a new signer before switching | Silent cron failure otherwise |
Emergency contact points
| Situation | First action |
|---|---|
TX_SIGNER suspected leaked | setSigner on every chain — now, not after investigating |
| Executor suspected leaked | setRouters([...], false) on Autopilot to halt all routing, then setExecutor |
| Owner key suspected leaked | pause() all three contracts if you still control the key; otherwise there is no lever — this is why multisigs matter |
| Provider key leaked | Rotate at the provider; check for quota abuse |
| A user reports an unauthorised trade | Check TradeExecuted events against the agent's policyHash; the event carries it deliberately |
Related
- Security model — the full threat analysis
- Custody — what the platform can and cannot do with funds
- Environment variables — every variable, grouped
- Contracts & upgrades — emergency levers