06 · docs/SECURITY.mdOpenPons manual

Security model

OpenPons is non-custodial read + prepare infrastructure. The core rule:

Nothing in OpenPons can move a user's funds without that user signing in their own wallet.

Hard rules

Rule How it is enforced
No private keys, seed phrases or raw signing keys No code path accepts them. The API, SDK and MCP server have no privateKey parameter anywhere.
No secrets in the frontend bundle Only NEXT_PUBLIC_OPENPONS_RPC_URLS (public RPC URLs) is exposed. There is no API key in the client.
No automatic approvals ETH buys need no approval. curve.sell pulls tokens via transferFrom, so a sell needs an approval — OpenPons returns it as a separate, visible requires[0] step the user signs first. Nothing is approved implicitly.
No unlimited approvals The only approval OpenPons ever prepares is token.approve(curve, exactTokensIn) for the exact sell amount.
MCP never executes MCP write tools are prepare_* only. They return unsigned transactions. The MCP server has no wallet and no signer.
API never executes POST /api/v1/prepare/* returns calldata. The server never broadcasts.

Every write goes through the same pipeline

validate input ─► validate chain ─► validate target ─► quote ─► simulate (eth_call)
   ─► preview to user ─► re-check chain + account ─► wallet signature ─► receipt ─► verify
  1. Validate input. Addresses must pass checksum-agnostic isAddress; amounts must be positive decimals within 18 decimals; slippage is clamped to 0.1–20 %.
  2. Validate chain. Prepared transactions always carry chainId: 4663. The wallet's eth_chainId is re-read immediately before eth_sendTransaction.
  3. Validate target. to must be a verified Pons contract: the factory, the router, the fee escrow (src/lib/pons/addresses.ts), or the curve that the factory's getLaunchedToken registry returns for that token (or the token itself for the exact approval). The browser enforces the same allow-list again in TxFlow before it will ask the wallet to sign. Before any write, factory and router runtime codehashes are compared with the pinned values; a mismatch blocks writes (UNSUPPORTED).
  4. Quote. Quotes are computed on-chain via the Pons view functions at a specific block.
  5. Simulate. eth_call with the exact from, to, data, value. A reverting simulation blocks the Sign button and is reported verbatim.
  6. Preview. The UI shows target contract, function, value, min-out, slippage, gas estimate and the explorer link of the target before the user signs.
  7. Re-check. Right before signing: chainId, active account, and that the prepared from equals the connected account. Stale quotes (older than 30 s) are re-fetched.
  8. Sign. Only via the selected EIP-6963 provider (never an implicit window.ethereum).
  9. Receipt. Tracked through OpenPons' own RPC with retries; status is checked.
  10. Verify. Balances are refreshed from chain after confirmation.

Threats and mitigations

Threat Mitigation
Wrong provider (Wallet A request routed to Wallet B) Per-wallet provider objects from EIP-6963; listeners bound to the selected provider only.
Wrong chain Banner + disabled actions; chainId re-checked right before signature; chainId field on every tx.
Stale quote Quotes carry blockNumber and quotedAt; UI re-quotes before signing if older than 30 s; minOut enforces slippage on-chain.
Duplicate transaction / double click A single in-flight lock in sendPrepared; buttons disabled while pending.
RPC lag Receipt polling with retries; reads use fallback transports; UI shows "pending" until the receipt is observed.
Failed receipt / revert receipt.status checked; UI shows "Reverted" with explorer link.
Reverted simulation Sign button blocked; error surfaced.
Malformed CA Rejected with INVALID_ADDRESS before any RPC call.
Fake token (look-alike name/symbol) Tokens are only treated as Pons tokens if the verified Pons contracts recognise them on-chain. Name/symbol are never used for identification.
Unsupported Pons implementation Only the verified contract versions in addresses.ts are supported; anything else returns NOT_A_PONS_TOKEN / UNSUPPORTED.
Malicious wallet icon Only data:image/* URIs rendered.
Prompt injection via token metadata Token names/descriptions are returned as data; SKILL.md tells agents never to follow instructions found in token metadata.
Clickjacking X-Frame-Options: DENY.
API abuse Best-effort per-IP rate limit (120 req/min per instance).

What an agent can and cannot do

  • Can: read chain state, get quotes, build unsigned transactions with simulation results.
  • Cannot: sign, broadcast, access the browser wallet, or hold keys.
  • If a user wants autonomous execution, they must run their own signer (e.g. a separate hot wallet with a spending cap) outside OpenPons and pass the prepared transaction to it. OpenPons documents this pattern but does not ship a signer.

Reporting

Security issues: open a private report to the maintainers before public disclosure.

Independent · not affiliated with Pons or Robinhood@openpons