Skip to content

Mainnet safe-flip hardening: boot guards + honest discovery surfaces#3

Open
Sarthib7 wants to merge 5 commits into
v1from
feat/mainnet-hardening
Open

Mainnet safe-flip hardening: boot guards + honest discovery surfaces#3
Sarthib7 wants to merge 5 commits into
v1from
feat/mainnet-hardening

Conversation

@Sarthib7

Copy link
Copy Markdown
Collaborator

What

Prepares the relay for a mainnet (Tempo Allegro, chain 4217, USDC.e) cutover without touching payment logic. PR #1 added the NETWORK switch; this PR makes flipping it safe:

Boot guards (fail-closed on real money) — on NETWORK=mainnet the server now refuses to boot when:

  • TEMPO_RECIPIENT is still the built-in demo address (real USDC.e would settle to it)
  • MPP_SECRET_KEY is still the dev default
  • TEMPO_RECIPIENT_PRIVATE_KEY address ≠ TEMPO_RECIPIENT (every cooperative close would fail the on-chain payee check — silently, per request)

plus a loud warning when no payee key is set (settlement degrades to payer timeout-reclaim). Testnet behavior is unchanged.

Honest discovery surfaces/llms.txt is now generated from the live chain config and /SKILL.md gets a live-network banner, so what agents read can never contradict the 402 challenge. The static skill/landing/llms files no longer claim "testnet only, no real money" unconditionally; they state both networks and teach agents to verify via GET /network.chainId. currencyName now matches the on-chain symbol() (USDC.e, verified by eth_call).

Ops docs.env.example now lists exactly the env vars the code reads (dropped UPSTREAM_URL/UPSTREAM_API_KEY/PRICE_PER_CALL which nothing reads; added TEE_ENDPOINT etc.), documents the guards and the stablecoin-gas payee funding requirement. render.yaml carries the production recipient and keeps the payee key dashboard-only.

Verified

  • tsc --noEmit clean; full boot matrix exercised locally:
    • mainnet + demo recipient → exit 1 ✅ · mainnet + dev secret → exit 1 ✅ · mainnet + mismatched payee key → exit 1 ✅
    • mainnet + valid env → serves 4217/USDC.e on /, /llms.txt, /SKILL.md, /openapi.json
    • testnet default → byte-for-byte prior behavior, zero guard hits ✅
  • Mainnet chain facts verified against reality: eth_chainId → 4217 on rpc.tempo.xyz; USDC.e symbol()/decimals() at 0x20C0…8b50USDC.e/6; matches the official tokenlist.

Deliberately not in this PR (known follow-ups)

  • Store.memory() voucher state is still in-process — restart before settlement loses revenue (mppx ships redis/upstash stores + settlementSchedule)
  • SDK/CLI/MCP still hardcode pathUSD labels and have no network option beyond the NETWORK env var; no client↔server chain cross-check
  • Landing/Pages copy beyond the factual claims

Cutover (after merge — dashboard, not this repo)

NETWORK=mainnet + random MPP_SECRET_KEY + owner TEMPO_RECIPIENT + TEE_ENDPOINT + payee key (funded with a little USDC.e — Tempo gas is paid in stablecoin), then an explicit deploy; the service has never had a push-triggered deploy, so use render deploys create.

Sarthib7 added 4 commits July 14, 2026 16:18
Refuse to boot on NETWORK=mainnet with the demo TEMPO_RECIPIENT, the dev
MPP_SECRET_KEY, or a payee key that mismatches the recipient (every
cooperative close would fail the on-chain payee check). Warn loudly when
no payee key is set. /llms.txt is now generated from the live chain
config and /SKILL.md gets a live-network banner, so agent discovery can
never contradict the 402 challenge. currencyName follows the on-chain
symbol (USDC.e); demo agent messages are network-aware.
The static agent/human surfaces claimed 'testnet only, pathUSD, no real
money' unconditionally — on a mainnet deployment they would tell paying
agents the opposite of the truth. State both networks, teach agents to
verify the live chain via GET / -> network.chainId, and keep the
testnet verified-runs sections labeled as historical evidence.
Drop vars no code reads (UPSTREAM_URL/UPSTREAM_API_KEY/PRICE_PER_CALL),
add the ones it does (TEE_ENDPOINT, EXPECTED_MEASUREMENT, CHUNK_COUNT,
MAX_DEPOSIT, MAX_BODY_BYTES, LOG_LEVEL), document the mainnet boot
guards and stablecoin-gas payee funding. render.yaml: production
recipient address + keep the payee key dashboard-only.
Adversarial review (24-agent, live-reproduced) found the guards checked
exact strings only:
- secret guard now rejects the .env.example placeholder, anything under
  24 chars, and both known dev strings (challenge provenance is HMAC
  over this secret — a public value makes it forgeable)
- recipient guard now trims and requires a valid address, so empty,
  '0x…'-placeholder and whitespace-padded values fail closed
- NETWORK is normalized (trim+lowercase) and CLOSED: unknown values
  throw instead of silently running testnet; boot log states
  network/chainId/currency
- SKILL.md banner injection falls back to after-frontmatter/top if the
  H1 anchor ever drifts (the REAL MONEY notice must never vanish)
- leftover unconditional pathUSD/testnet claims fixed: SKILL.md
  frontmatter + troubleshooting (and the nonexistent --account flag),
  dynamic llms.txt verified-runs block, landing hero mock, #verified
  lede, footer explorer links, CLI/MCP labels now use the live
  currencyName
- .env.example: MAX_DEPOSIT documented as read by BOTH sides (server
  suggestedDeposit in 402 challenges), CHUNK_COUNT 'up to N ticks'
@Sarthib7

Copy link
Copy Markdown
Collaborator Author

Adversarial review pass complete (24 agents, every finding live-reproduced before being accepted): 19 confirmed findings, all fixed in 579054d.

Highlights — all three were real, boot-reproduced bypasses of the original guards:

  1. Secret guard missed the .env.example placeholderMPP_SECRET_KEY=change-me-to-a-long-random-string booted on mainnet with a public repo string as the HMAC challenge secret (forgeable challenge provenance). Now: denylist of both known placeholders + 24-char minimum.
  2. Recipient guard was equality-only — empty string, the literal 0x… template placeholder, and the demo address with a trailing space all booted. Now: trim + isAddress() + demo-address check.
  3. NETWORK was case-sensitive with silent testnet fallback — a typo'd flip no-oped with zero warning. Now: normalized (trim+lowercase) and closed — unknown values crash the boot; the listening log states network/chainId/currency.

Full re-verification after the fixes: 6 fatal scenarios + uppercase-MAINNET normalization + garbage-value crash + testnet regression, all green. tsc --noEmit clean.

@Sarthib7

Copy link
Copy Markdown
Collaborator Author

Staging verification (repo protocol step 4b) — PASSED on this branch.

Full paid E2E on Tempo Moderato testnet against the real Phala enclave, run from the feat/mainnet-hardening worktree at 579054d:

  • Server: NETWORK=testnet, TEE_ENDPOINT = the live Phala TDX enclave → booted tdx-live, ready:true; boot log now states network/chainId/currency.
  • Client: fresh faucet-funded wallet, temprouter infer with a synthetic leaked-key prompt → DCAP pre-pay gate ✅ → 402 → MPP session → 1 chunk streamed & paid (0.0002 pathUSD) → post-pay receipt ✅ → decrypted locally.
  • Cooperative close landed on-chain for the first time (payee key configured): payee nonce 0→1, balance delta exactly +0.0002 revenue − ~0.000589 close fee. evidence.md §5 previously recorded that no close had ever settled.

⚠️ Economics finding for cutover: with CHUNK_COUNT=1 and PRICE_PER_UNIT=0.0002, the close fee (~0.0006) exceeds per-session revenue — every 1-chunk mainnet session nets ≈ −0.0004 USDC.e. Before mainnet: raise PRICE_PER_UNIT, meter more chunks per session, or batch settlement (mppx settlementSchedule).

evidence.md gains the 2026-07-14 staging run: paid E2E on the hardening
branch against the real enclave, and the first on-chain cooperative
close (payee nonce 0->1, +0.0002 revenue - ~0.000589 close fee), with
the per-session economics warning. README Status/TODO/config table now
reflect verified mainnet readiness and the remaining cutover steps.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant