Skip to content

fix: enforce widget token list restrictions#7834

Open
fairlighteth wants to merge 6 commits into
developfrom
deepsec/widget-token-list-restrictions
Open

fix: enforce widget token list restrictions#7834
fairlighteth wants to merge 6 commits into
developfrom
deepsec/widget-token-list-restrictions

Conversation

@fairlighteth

@fairlighteth fairlighteth commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

What changed

  • Enforce field-specific sellTokenLists and buyTokenLists for favorite and persisted recent tokens.
  • Filter stored recents through the current field scope before hydration and before applying the recent-item limit.
  • In bridge mode, require favorites to satisfy both the field policy and provider support; keep permitted but unsupported recents visible and disabled.
  • Preserve virtual widget lists, including customTokens, while curated-only mode is active.
  • Reuse one scoped-list signal for token search and recent-token policy.

Why

  • Closes the DeepSec finding Favorite tokens bypass widget sell/buy token-list restrictions and the review follow-up for persisted Recent tokens.
  • Favorites and recents are shortcuts, not escape hatches around integrator token allowlists.
  • Keeps unrestricted custom-token recents working while enforcing configured widget policies.
  • This work was split from fix: fail closed for RWA geoblocking #7621 so widget allowlist behavior can be reviewed separately from RWA/geoblock hardening.
  • This PR intentionally does not close the separate DeepSec finding Bridge token search ignores widget-scoped token list restrictions; raw bridge search still needs its own field-specific intersection.

QA Testing

Preview URL QA:

  • Widget configurator branch preview: configure distinct sell and buy token lists and confirm favorites outside each list do not appear in that selector.
  • Persisted recents: select a token while it is allowed, tighten the matching sell or buy list, reopen the selector, and confirm the out-of-scope Recent entry is absent.
  • Cross-chain output: select a destination chain and confirm a bridge-supported favorite outside buyTokenLists stays hidden. A permitted Recent token without route support remains visible but disabled.
  • Swap branch preview: remove widget list restrictions and confirm normal recent-token behavior remains unchanged.

Developer verification:

  • useRecentTokens.test.tsx covers unrestricted fallback hydration, scoped filtering, and filtering before the recent-item limit.
  • useTokensToSelect.test.ts covers sell, buy, shared, wrong-side, and cross-chain scope selection.
  • useTokenListContext.test.ts covers forwarding restricted and unrestricted recent-token policy.
  • tokensVirtualListUtils.test.ts covers disabled bridge recents while provider support is loading or unavailable.
  • The focused frontend suites pass 42/42 tests.
  • tokensToSelectAtom.test.ts and tokenListsStateAtom.test.ts cover field-scoped selection and retaining virtual widget lists in curated-only mode.

Reviewer note:

  • Curated-only virtual-list behavior is covered at the state level because reproducing the geo-driven mode reliably from a public preview is location-dependent.

Preview URLs

Surface URL
swap-dev - branch preview URL https://swap-dev-git-deepsec-widget-token-list-restrictions-cowswap-dev.vercel.app
explorer-dev - branch preview URL https://explorer-dev-git-deepsec-widget-token-list-r-2a1f10-cowswap-dev.vercel.app
cowfi - branch preview URL https://cowfi-git-deepsec-widget-token-list-restrictions-cowswap.vercel.app
storybook - branch preview URL https://storybook-git-deepsec-widget-token-list-rest-7e2b12-cowswap-dev.vercel.app
widget-configurator - branch preview URL https://widget-configurator-git-deepsec-widget-token-f42503-cowswap-dev.vercel.app
sdk-tools - branch preview URL https://sdk-tools-git-deepsec-widget-token-list-rest-5fb9a6-cowswap-dev.vercel.app

Summary by CodeRabbit

  • Improvements
    • Token search and selection now better follow field-specific and widget-defined list restrictions, including scoped “allowed recent” hydration.
    • Recent tokens are filtered more precisely to match the current selection scope.
    • Favorite tokens are constrained to what’s currently selectable, with more consistent behavior in bridge mode.
    • Virtual custom widget token lists remain available when curated-list-only mode is enabled.
  • Tests
    • Expanded coverage for restricted recent tokens, scoped favorite filtering, and curated-list-only virtual list behavior.

@vercel

vercel Bot commented Jul 9, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
cowfi Ready Ready Preview Jul 13, 2026 1:31pm
explorer-dev Ready Ready Preview Jul 13, 2026 1:31pm
storybook Ready Ready Preview Jul 13, 2026 1:31pm
swap-dev Ready Ready Preview Jul 13, 2026 1:31pm
widget-configurator Ready Ready Preview Jul 13, 2026 1:31pm
2 Skipped Deployments
Project Deployment Actions Updated (UTC)
cosmos Ignored Ignored Jul 13, 2026 1:31pm
sdk-tools Ignored Ignored Preview Jul 13, 2026 1:31pm

Request Review

@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 6de2e8a4-396f-426e-a1e0-7a8ad48cd12a

📥 Commits

Reviewing files that changed from the base of the PR and between 07fdbf7 and 440ce99.

📒 Files selected for processing (2)
  • apps/cowswap-frontend/src/modules/tokensList/hooks/useRecentTokens.test.tsx
  • apps/cowswap-frontend/src/modules/tokensList/hooks/useRecentTokens.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/cowswap-frontend/src/modules/tokensList/hooks/useRecentTokens.test.tsx
  • apps/cowswap-frontend/src/modules/tokensList/hooks/useRecentTokens.ts

Walkthrough

Token list selection now applies widget and field-scoped restrictions to visible and favorite tokens, filters persisted recent tokens through allowed token keys, exposes restriction state via context, and preserves virtual widget lists in curated-only mode.

Changes

Token list availability and selection

Layer / File(s) Summary
Virtual list availability
libs/tokens/src/state/tokenLists/tokenListsStateAtom.ts, libs/tokens/src/state/tokenLists/tokenListsStateAtom.test.ts
Curated-only list filtering retains virtual widget list sources, with coverage for the resulting state.
Selectable token and favorite scoping
apps/cowswap-frontend/src/modules/tokensList/state/tokensToSelectAtom.ts, apps/cowswap-frontend/src/modules/tokensList/hooks/useTokensToSelect.ts, apps/cowswap-frontend/src/modules/tokensList/**tests*
Selectable tokens come only from selected lists; visible and favorite tokens are filtered by bridge visibility and field-scoped lists, while recent-token permissions and restriction metadata are returned.
Recent-token hydration filtering
apps/cowswap-frontend/src/modules/tokensList/hooks/useRecentTokens.ts, apps/cowswap-frontend/src/modules/tokensList/hooks/useHydratedRecentTokens.ts, apps/cowswap-frontend/src/modules/tokensList/containers/SelectTokenWidget/hooks/useRecentTokenSection.ts, apps/cowswap-frontend/src/modules/tokensList/utils/recentTokensStorage.ts, apps/cowswap-frontend/src/modules/tokensList/hooks/useRecentTokens.test.tsx
Recent-token hooks accept allowed tokens, convert them to keys, exclude disallowed persisted entries during hydration, and test filtering before the item limit.
Context and search restriction wiring
apps/cowswap-frontend/src/modules/tokensList/hooks/useTokenListContext.ts, apps/cowswap-frontend/src/modules/tokensList/hooks/useTokenListContext.test.ts, apps/cowswap-frontend/src/modules/tokensList/containers/TokenSearchResults/index.tsx
Token list context exposes restriction state and passes allowed recent tokens through; search results consume the context flag directly.

Estimated code review effort: 4 (Complex) | ~45 minutes

Suggested reviewers: limitofzero, elena-zh

Sequence Diagram(s)

sequenceDiagram
  participant Widget
  participant useTokensToSelect
  participant TokenListContext
  participant useRecentTokens
  participant useHydratedRecentTokens
  participant TokenSearchResults
  Widget->>useTokensToSelect: provide field and scoped list parameters
  useTokensToSelect->>TokenListContext: return visible tokens, allowed recent tokens, and restriction state
  TokenListContext->>useRecentTokens: pass allowed recent tokens
  useRecentTokens->>useHydratedRecentTokens: pass allowed token keys
  useHydratedRecentTokens->>useHydratedRecentTokens: filter persisted recent tokens
  TokenListContext->>TokenSearchResults: expose scoped restriction state
  TokenSearchResults->>TokenSearchResults: apply search result scoping
Loading

Poem

A rabbit hops through token rows,
While scoped lists guide the flows.
Recent crumbs are checked with care,
Virtual lists remain in there.
“Filter neatly!” the bunny sings.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately summarizes the main change: enforcing widget token list restrictions.
Description check ✅ Passed The description covers the summary, motivation, QA steps, and background, even though it doesn't use the exact template headings.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch deepsec/widget-token-list-restrictions

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 9, 2026

Copy link
Copy Markdown

Deploying explorer-dev with  Cloudflare Pages  Cloudflare Pages

Latest commit: 440ce99
Status: ✅  Deploy successful!
Preview URL: https://b645cd73.explorer-dev-dxz.pages.dev
Branch Preview URL: https://deepsec-widget-token-list-re.explorer-dev-dxz.pages.dev

View logs

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 9, 2026

Copy link
Copy Markdown

Deploying swap-dev with  Cloudflare Pages  Cloudflare Pages

Latest commit: 440ce99
Status: ✅  Deploy successful!
Preview URL: https://7484a1a3.swap-dev-5u6.pages.dev
Branch Preview URL: https://deepsec-widget-token-list-re.swap-dev-5u6.pages.dev

View logs

@fairlighteth fairlighteth left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI Review (Codex GPT-5, worked 9m): latest follow-up is fixed; no new PR-scope findings

Review completed. I found no new non-duplicate comments worth posting.

Rechecked

  • Favorites are restricted to the current sellTokenLists/buyTokenLists; bridge favorites must also be provider-supported.
  • Persisted recents outside the field scope are removed before hydration and before applying the item limit.
  • Unrestricted custom recents remain available, while unsupported bridge recents remain visible but disabled.
  • Curated-only mode retains virtual widget lists such as customTokens.

Result: Fixed. The previous favorite and persisted-recent bypasses are no longer reproducible on head b5e97d550.

Review scope and related context
  • Reviewed all 13 changed files; there are no review threads, inline comments, or human reviews to deduplicate.
  • Targeted verification passed: 42/42 frontend tests and 8/8 token-library tests.
  • Test, lint, typecheck, agent harness, deployment, and security checks pass.
  • Cypress fails the same four #do-trade-button timeouts on this head and exact base c03a2d33f, so it is not attributable to this PR.
  • DeepSec PR mode used GitHub’s exact 13-file set. Helper exit: 0; underlying scan exit: 1; artifact: /tmp/deepsec-comment-pr-7834-review-2018074.md.
  • DeepSec’s only finding is the raw bridge-search restriction already documented and explicitly deferred in the PR body, so it is not repeated.

Generated using the pr-review skill from the CoW Protocol skills repo.

Generated using the security-review skill from the CoW Protocol skills repo.

@fairlighteth

Copy link
Copy Markdown
Contributor Author
Browser QA passed: scoped favorites, persisted recents, and unrestricted fallback

Outcome

  • Checked scenarios:
    • Field-scoped favorites: Gemini sell scope retained only DAI; Optimism buy scope retained DAI, USDC, USDT, WBTC, and WETH, while out-of-list defaults stayed out of each Favorite section.
    • Persisted recents: GNO appeared while unrestricted, disappeared after the sell scope tightened to Gemini without being removed from storage, and reappeared when that scope was removed.
    • Cross-chain output shortcuts: for Base to Ethereum with Gemini buy scope, DAI remained a Favorite while bridge-supported USDC/USDT stayed out; permitted but unsupported APE remained in Recent as disabled with a no-route tooltip and a no-op click.
    • Unrestricted swap control: GNO remained in Recent after reloading the regular swap preview with no widget token-list restriction.
  • Primary evidence: the structured capture records token-list membership, browser responses, recent storage invariance, bridge-provider support, and DOM disabled state.
  • Not checked / follow-up:
    • ⚠️ Connected wallets/signing, Firefox/mobile, geo-driven curated-only mode, additional bridge pairs, and the explicitly deferred raw bridge browse/search restriction.

Run details

  • Source: PR head b5e97d55040849535240920d5fcc3b003b7024ba via the widget configurator preview and swap preview
  • Environment: Linux; Chromium 150.0.7871.100; en-US
  • Wallet: disconnected; wallet state is irrelevant to selector visibility
  • AI assistance: Codex orchestrated Playwright execution, artifact collection/inspection, and drafted this note from observed results.

Artifacts

Sell scope: Gemini retains DAI; persisted out-of-scope GNO is absent.

Chromium sell selector restricted to the Gemini token list

Buy scope: Optimism favorites remain; out-of-list COW is absent.

Chromium buy selector restricted to the Optimism token list

Bridge intersection: DAI survives both scopes; unsupported permitted APE remains disabled in Recent.

Chromium bridge output selector showing scoped DAI favorite and disabled APE recent

Unrestricted control: persisted GNO remains in Recent after reload.

Chromium unrestricted swap selector showing GNO in Recent after reload
Bridge transport caveat

The PR preview's barn BFF bridge proxy returned 403 Unauthorized. The bridge run injected the current public Bungee Base-to-Ethereum destination-token response into that blocked request. The selector behavior is from the PR preview; provider transport is fixture-backed and labeled as such in the evidence.

Generated using the pr-qa skill from the CoW Protocol skills repo.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
apps/cowswap-frontend/src/modules/tokensList/hooks/useRecentTokens.ts (1)

38-41: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider renaming buildFavoriteTokenKeys for reuse with allowed tokens.

buildFavoriteTokenKeys is now called for both favorite tokens (line 37) and allowed tokens (line 39). The function name implies it's specific to favorites, but it's a generic token-key Set builder. A more descriptive name like buildTokenKeySet would improve clarity at both call sites.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/cowswap-frontend/src/modules/tokensList/hooks/useRecentTokens.ts` around
lines 38 - 41, Rename buildFavoriteTokenKeys to a generic name such as
buildTokenKeySet, update its definition and all call sites for both favorite and
allowed tokens, and preserve the existing behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@apps/cowswap-frontend/src/modules/tokensList/hooks/useRecentTokens.ts`:
- Around line 38-41: Rename buildFavoriteTokenKeys to a generic name such as
buildTokenKeySet, update its definition and all call sites for both favorite and
allowed tokens, and preserve the existing behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 260de7d7-da8e-4dc0-9342-dfd9702561ed

📥 Commits

Reviewing files that changed from the base of the PR and between 1ab0190 and dacbdb7.

📒 Files selected for processing (13)
  • apps/cowswap-frontend/src/modules/tokensList/containers/SelectTokenWidget/hooks/useRecentTokenSection.ts
  • apps/cowswap-frontend/src/modules/tokensList/containers/TokenSearchResults/index.tsx
  • apps/cowswap-frontend/src/modules/tokensList/hooks/useHydratedRecentTokens.ts
  • apps/cowswap-frontend/src/modules/tokensList/hooks/useRecentTokens.test.tsx
  • apps/cowswap-frontend/src/modules/tokensList/hooks/useRecentTokens.ts
  • apps/cowswap-frontend/src/modules/tokensList/hooks/useTokenListContext.test.ts
  • apps/cowswap-frontend/src/modules/tokensList/hooks/useTokenListContext.ts
  • apps/cowswap-frontend/src/modules/tokensList/hooks/useTokensToSelect.test.ts
  • apps/cowswap-frontend/src/modules/tokensList/hooks/useTokensToSelect.ts
  • apps/cowswap-frontend/src/modules/tokensList/state/tokensToSelectAtom.test.ts
  • apps/cowswap-frontend/src/modules/tokensList/state/tokensToSelectAtom.ts
  • libs/tokens/src/state/tokenLists/tokenListsStateAtom.test.ts
  • libs/tokens/src/state/tokenLists/tokenListsStateAtom.ts

@fairlighteth

Copy link
Copy Markdown
Contributor Author

Addressed CodeRabbit's naming nit in 07fdbf72f: renamed buildFavoriteTokenKeys to buildTokenKeySet and updated all favorite/allowed-token call sites. Focused recent-token tests pass (21/21), and file-level ESLint passes.

return acc
}, new Set())

const favoriteTokenAddresses = hideFavoriteTokens

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was done on purpose in #7254, won't it add the bug again?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for checking, I dug into this to find out.

#7254 intentionally made favorites an exception to sell/buy list filtering so they matched Active token-list behavior.

This PR deliberately supersedes that part: following the DeepSec finding, field-specific sell/buy lists are treated as integrator allowlists, so favorites cannot bypass them. The separate #7254 raw-search fix remains intact: scoped search still uses only the field-scoped allTokens and removes inactive, blockchain, and external results. hideFavoriteTokens also remains enforced upstream, and we have targeted coverage for an out-of-scope favorite being excluded.

So answering your question, my understanding is that this PR intentionally removes the exception for favorites. This means favorites outside the active token list will be hidden again, like before #7254. But search is still limited to the allowed tokens, so the separate search bug fixed in #7254 does not come back.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants