[Audit M-01] Referral registration lacks wallet-session drift protection #155
Labels
No labels
agent:fix_bugfix
agent:fix_conflicts
agent:fix_security
agent:gap_analysis
agent:implement
agent:open_issues
agents
agent:security_audit
agent:verify
area
timecurve
arena
arena-v2
audit
block:feature_review
block:security
bug
contracts
design
devops
docs
documentation
enhancement
epic
feature
frontend
indexer
low priority
megaeth
operations
ops
performance
QA
qa checklist
referrals
removal
security
smart-contracts
tech-debt
testing
time-arena
timecurve
visual-qa
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
code/yieldomega#155
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Source
Internal full-stack audit
audits/audit_full_1777967937.md(2026-05-05), finding M-01 (Medium). Commit reviewed:3b3bd0c6c501f9cf908667d304146b7b63302b87.Problem
ReferralRegisterSection.onRegisterchecks the target chain before starting, reads allowance for the React-closureaddress, optionally submitsapprove, waits for the receipt, then submitsregisterCode. Unlike TimeCurve buys (GitLab #144 /INV-BUY-SESSION-144), it does not capture and re-check the connected account and chain after each await.If the wallet account or network changes mid-flow:
address.registerCodemay register the normalized code for a different wallet than the one the closure will later cache withsetStoredMyReferralCodeForWallet(address, normalized).This is not a protocol fund-drain:
ReferralRegistry.registerCodeburns frommsg.senderand the contract mapping follows the caller. The risk is user confusion, wrong-wallet referral ownership UX, and avoidable CL8Y prompts during wallet drift.Relevant code
onRegisterawaits allowance read, approve receipt,registerCode, and receipt without session snapshots:Existing guard pattern to reuse:
Recommended fix
walletBuySessionGuardpattern (same drift message as buys for consistency, or a referral-specific message if product prefers).captureWalletBuySession(wagmiConfig)immediately after validating inputs and before the first async read/write.assertWalletBuySessionUnchangedafter allowance read, after approve receipt (if any), immediately beforeregisterCode, after register receipt, and beforesetStoredMyReferralCodeForWallet.INV-BUY-SESSION-144for referrals.Acceptance criteria
chainIdchanges between any awaited steps (same semantics as TimeCurve buy session guard).setStoredMyReferralCodeForWalletcall runs after a drift (storage stays aligned with the wallet that actually registered).Verification checklist
npm test(Vitest) passes including new/updated tests for referral registration session guard.mentioned in commit
69172a636fmentioned in commit
955228fd7fImplemented GitLab #155 (audit M-01 / referral registration wallet-session drift).
What changed
ReferralRegisterSection.onRegisternow latchescaptureWalletBuySession(wagmiConfig)after validation and before the first async step;assertWalletBuySessionUnchangedruns after eachawait(allowance read, optional approve + receipt,registerCode+ receipt).setStoredMyReferralCodeForWalletruns only after the final guard and uses the snapshot address (aligned with the signing wallet).WALLET_BUY_SESSION_DRIFT_MESSAGEas TimeCurve buys (#144) for consistency.Docs / tests
INV-REFERRAL-SESSION-155+ contributor manual QA §155;wallet-connection.md§155;skills/README.mdcross-links; VitestreferralRegisterWalletSession.test.ts.Merged:
main@955228f(pushed toorigin/main).Checklist for @brouie to verify
/referrals→ register a new code with a stable wallet on the target chain → approve (if prompted) + register succeed; share-link / local cache behave as before for that wallet.registerCodeconfirms → UI shows Could not register: …Wallet or network changed during purchase — please retry from the beginning.— no plaintext code cached under the wrongyieldomega.myrefcode.v1.<address>key.chainIdchanges mid-flow (or #95 preflight if the switch happens before guarded writes — either is acceptable UX).cd frontend && npm test— includesreferralRegisterWalletSession.test.ts.Leaving this issue open for your sign-off.
@PlasticDigits — verification PASS on
69172a6.ran the new vitest:
3/3 PASS in 633ms — capture matches preflight, abort on account switch, abort on chain switch.
source-side review of
ReferralRegisterSection.tsxconfirms all five latch points wired:INV-REFERRAL-SESSION-155 satisfied.
ready for close on your side.
@PlasticDigits — verification PASS on
69172a6(full walk).automation:
cd frontend && npm test→ 286/286 across 47 files includingreferralRegisterWalletSession.test.ts3/3.happy path on /referrals: registered
testbrouie506under anvil #1 0x70997970...79c8 cleanly. on-chain CodeRegistered event at block 64. page shows registered code, share links populate, localStorageyieldomega.myrefcode.v1.0x70997970...c518correctly cached withtestbrouie506.account switch mid-flow: walked via cancel-then-switch path — metamask blocks account switching while the signature popup is open (intentional wallet UX, also documented in #144). cancelled the registerCode popup for
clean0506under anvil #2, switched to anvil #0. result:setFormErr("Transaction was rejected in the wallet.")surfaces correctly in the Register section, no cache write happened under 0x3c44... or 0xf39Fd6..., page rebinds to new account cleanly. cache poison prevented as expected.network switch mid-flow: not walked separately — same metamask constraint as account switch makes the testable variant cancel-then-switch-chain, which mirrors the account-switch behavior above. happy to walk it explicitly if you want a separate signoff.
source-side review of
ReferralRegisterSection.tsxconfirms all five latch points are wired:INV-REFERRAL-SESSION-155 satisfied for the testable surface.
ready for close on your side.
mentioned in commit
f43ac77e85mentioned in commit
eba7c43207mentioned in issue #325