feat(chat): session-wallet security — SIWE domain binding, withdraw, contract-wallet guard#762
Open
bussyjd wants to merge 3 commits into
Open
feat(chat): session-wallet security — SIWE domain binding, withdraw, contract-wallet guard#762bussyjd wants to merge 3 commits into
bussyjd wants to merge 3 commits into
Conversation
withdraw: the session wallet signs a direct EIP-3009 transferWithAuthorization for its full balance back to the connected wallet, which submits it (and pays the dust of gas) — the session wallet never needs ETH. This is the recovery path removing reveal-key promised, and it makes offer hostname changes a drain-and-refund instead of a fund-stranding event. connect() now getCode-guards the account: ERC-1271 smart-contract wallets sign non-deterministically, so the session wallet this page derives could never be recovered — refuse with an explanation instead of stranding funds through normal use. EIP-7702 delegated EOAs (code 0xef0100…) still sign with the EOA key and are allowed. Implemented by grok-4.5 lane from spec; diff reviewed, tests re-run. Claude-Session: https://claude.ai/code/session_014YjPMViNrZ7zBVgUQzwEKk
The session-key message was prose: it named the domain, but nothing enforced it — personal_sign has no origin binding, so a phishing site could request the byte-identical message and derive the victim's session wallet from the signature. Formatted as strict EIP-4361, wallets (MetaMask, Rabby) detect the SIWE grammar, compare the message domain to the requesting origin, and interpose a deceptive-site warning on mismatch — the domain check moves from user attention to wallet enforcement. Every field is static by design: the signature is key material (keccak256 of it derives the session wallet), never a server-replayable proof, so a rotating nonce protects nothing here while determinism is what lets users recover their session by re-signing. Address is EIP-55 checksummed via an in-page keccak loop (verified against the EIP-55 spec vectors); Chain ID comes from the 402 challenge, so sessions are per-chain, matching where the funds actually live. Message v1 was retired after its only funded session was swept via the new withdraw flow. Implemented by grok-4.5 lane from spec; diff reviewed, checksum executed against reference vectors, tests re-run. Claude-Session: https://claude.ai/code/session_014YjPMViNrZ7zBVgUQzwEKk
fund / withdraw / explorer become a slim inline row of 22px icon buttons (Feather-style inline SVGs, no icon dependency — the CSP is same-origin only), mirroring the 402 page's canonical .icon-btn shape and hover; the session address gains a click-to-copy button reusing that page's copy glyph verbatim. Tooltips + aria-labels carry the labels the icons dropped. Implemented by grok-4.5 lane from spec; diff reviewed, tests re-run. Claude-Session: https://claude.ai/code/session_014YjPMViNrZ7zBVgUQzwEKk
bussyjd
added a commit
that referenced
this pull request
Jul 16, 2026
Built-in browser chat widget for agent-type offers (base of SIWE stack #762).
bussyjd
added a commit
that referenced
this pull request
Jul 16, 2026
Session-wallet security: EIP-4361 SIWE domain binding, withdraw path, contract-wallet guard, session-strip icon row.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #752 (base is its branch, so the diff shows only these 2 commits; retarget to main when #752 merges).
Hardens the chat widget's session-wallet mechanism against the two ways users could actually lose funds, and adds the recovery path:
keccak256(personal_sign(message));personal_signhas no origin binding, so with a prose message a phishing site could request the byte-identical text and derive the victim's session wallet. The message is now strict SIWE grammar: wallets detect it, compare the messagedomainto the requesting origin, and interpose their deceptive-site warning on mismatch. Every field is deliberately static (constant nonce and Issued At): the signature is key material, never a server-replayable proof — replay protection is meaningless here, while determinism is what lets users recover a session by re-signing. Address is EIP-55-checksummed via an in-page keccak loop verified against the spec vectors; Chain ID comes from the 402 challenge, so sessions are per-chain like the funds themselves.transferWithAuthorizationfor its full balance back to the connected wallet, which submits it (session wallets hold no ETH). This also converts an offer hostname change from a fund-stranding event into drain-and-refund — relevant because domain-bound keys re-derive differently on a new hostname, by design. First use was a real on-chain sweep prior to the message change landing.connect()now refuses them with an explanation instead of stranding funds through normal use. EIP-7702-delegated EOAs (code0xef0100…) still sign with the EOA key and are allowed.Message v1 is retired (documented in-code); its only funded session was swept via the new withdraw flow before the cutover, so no user funds were affected.
Tests green; commits signed; live-verified (withdraw exercised with a real settled sweep on Base mainnet).
https://claude.ai/code/session_014YjPMViNrZ7zBVgUQzwEKk