Skip to main content

0012. A Session Key is not trade-only on-chain; its scope rests on Polymarket’s relayer

  • Status: Proposed
  • Date: 2026-09-30

Context

The app signs with a Polymarket Session Key for the Deposit Wallet (ADR 0009). Gate item 5 (security finding P3-01, F4) needs evidence that a leaked key can only trade. The docs say “A Session Key cannot withdraw funds from the Deposit Wallet” (https://docs.polymarket.com/trading/session-keys.md). The questions in secrets-and-logging.md §7 were answered from the verified wallet source, read-only eth_calls to a public Polygon RPC (block 94703939), the docs and @polymarket/client 0.11.0. Nothing was signed or sent.

Decision

  • Trade-only does not hold on-chain. The wallet contract gives a Session Key almost the owner’s power: it can sign wallet batches that call any contract except the wallet and its beacon, and isValidSignature accepts its signature over any digest. The only barrier to moving funds is that Polymarket’s relayer is the sole submitter of batches. Whether the relayer refuses session-signed batches that transfer or approve tokens is not public: UNVERIFIED, and not determinable from public sources.
  • Gate item 5 stays failed and the live run stays blocked until one of the options in Gate 5 decision is recorded.

Gate 5 decision

Security recheck condition C1 (findings-phase-3-recheck.md). The live-verification runbook checks this section before arming (pre-flight PF1). The options, strongest first:
  1. Polymarket statement. Polymarket states in writing that the relayer refuses session-signed batches other than session-key management. Item 5 passes.
  2. Owner relayer test. From a permitted location, outside this repo and with the official SDK, the owner submits a session-signed batch that moves 1 base unit of pUSD to the owner, and the relayer refuses it. Item 5 passes for the relayer as it behaves on the test date. This app does not build that request (CLAUDE.md: no relayer features).
  3. Accepted risk. The user and the tech lead accept in writing that a leaked key can lose everything in the Deposit Wallet, bounded by the cap in ADR 0013. Item 5 is then Accepted risk, which holds only while item 6 (the cap) holds. It covers the attended live run at the ADR 0013 stage 1 cap, not raising the cap.
The live run (ADR 0013 stage 1) needs any one of the three. Normal operation with a working balance of 100kto100k to 500k (ADR 0013 stage 2) needs option 1 or 2. The tech lead recommends option 2, and option 3 for the attended run only if the test cannot be done. Decision: pending the user; to be raised when all phases are complete.

Evidence

Beacon 0x7A18…c3a: implementation() returns 0xf7f27c29e60fe6325bef8da7f93250353d2e3294, verified as src/DepositWallet.sol (Sourcify). Wallets from before 2026-06-29 run 0x58CA…B1eB, the same design. Factory implementation: 0x528c…abd7. Audits: Polymarket/contract-security.

Consequences

  • A leaked Session Key can lose the whole Deposit Wallet balance, pUSD and outcome tokens, if the relayer accepts a session-signed transfer batch. Even if it does not, the key can sign orders at any price and trade the balance away to an attacker’s orders (threat T36). Either way the working-balance cap (ADR 0013) is the bound.
  • Our signer allowlist (signing/typeddata.go) and lint:scope stop this app from signing a batch or permit. They do not help once the key has leaked.
  • Response to a suspected leak: revoke through polymarket.com or the SDK as the owner; if the relayer is unavailable, pause and revoke through the emergency path after an hour.
  • The session-key runbook, the threat model and secrets-and-logging §7 should cite this record in place of the docs sentence.
  • Detection is not guaranteed (P3F-04). F5 flags outgoing transfers that the Data API reports as activity, but whether a raw session-signed pUSD.transfer batch appears in /v2/activity at all is UNVERIFIED. An unexplained drop in cash is checked from Phase 4.

Alternatives considered

  • Treat the docs sentence as sufficient. Rejected: the contract source contradicts it as a contract property.
  • Encrypted keystore holding the owner key (PLAN §7 fallback). Rejected: the owner key has strictly more power, and the owner key must never be on the app host.