Security[SEC-I02 (H09)]: route intermediate token addresses not validated before swap submission [SEC-I02] #450
Labels
No labels
agent:fix_bugfix
agent:fix_conflicts
agent:fix_security
agent:gap_analysis
agent:implement
agent:implement
agent:implement
agent:open_issues
agent:ready
agent:research
agent:security_audit
agent:verify
architecture
backend
blocker:hybrid
blocker:launch
blocker:limit-orders
blocker:v2
block:log_only
block:security
bug
ci
contracts
correctness
deploy
dev
devops
docs
documentation
duplicate
e2e
enhancement
epic
feature
frontend
functional-completion
gas
good first issue
governance
help wanted
high-risk
hooks
hybrid
indexer
infra
infrastructure
integrators
invalid
launch-blocker
limit-orders
localnet
localterra
low priority
missing-implementation
needs-design
ops
performance
priority
high
priority
medium
product
qa
QA
question
ready
ready
research
scripts
security
security-hardening
smartcontracts
tech-debt
testing
ux
UX
v2
verification
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
code/cl8y-dex-terraclassic#450
Loading…
Add table
Add a link
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?
Summary
When the frontend displays a multihop route to the user (e.g. LUNC -> tokenX -> USTC), the intermediate token addresses come from the indexer response. The actual swap is built from the same indexer response's router_operations field. The frontend does not cross-validate that the intermediate token addresses displayed to the user match the token addresses in the submitted router_operations. A malicious indexer can display one route and submit a different token path, routing through unintended intermediate tokens within the user's slippage tolerance.
This is related to Issue (#449) but affects the intermediate path rather than the terminal pair contract. Together they represent the full trust-the-indexer attack surface for route integrity.
What Was Checked
frontend-dapp/src/utils/swapRouteDisplay.tslines 65-110:computeSwapRouteDisplay()derives the displayed route from indexer operations.frontend-dapp/src/utils/swapRouteDisplay.tslines 115-131:deriveSwapSubmitRouteSource()uses indexer operations for submission without re-deriving expected intermediate tokens from router_operations to compare against the displayed path.frontend-dapp/src/pages/SwapPage.tsxline ~531: only token_in and token_out are validated against the user's selected tokens. Intermediate token addresses in router_operations are accepted as-is from the indexer response.frontend-dapp/src/services/terraclassic/swapRoutePreflight.ts: enrichment function adds min_return per hop but does not validate intermediate token addresses.Expected (per checklist)
Before submitting a multihop swap, the frontend re-derives expected intermediate token addresses from the selected token pair and the number of hops, then confirms each hop's offer_asset and ask_asset in router_operations match the expected path. Any mismatch causes submission to be blocked with a clear error. Maps to SEC-E07 (route display aligns with actual submit operations).
Actual
No intermediate token address validation exists. The displayed path and submitted operations are both derived from the indexer response with no cross-check.
Suggested Fix
After receiving and accepting the indexer route response, extract the actual intermediate token sequence from router_operations (each hop's ask_asset_info becomes the next hop's offer_asset_info). Compare this derived sequence against the displayed intermediate_tokens array. If they do not match, reject the route and log a warning. This check runs client-side before enrichment and submission.
Verification Checklist
Cc : @PlasticDigits
mentioned in issue #381
mentioned in merge request !987
Took the logic half of this. The displayed route (
intermediate_tokens) and the submitted ops (router_operations) both come from the indexer with no cross-check, so a tampered indexer could show one path and submit another inside slippage tolerance.Added
swapRouteIntermediateTokensAligned(operations, intermediateTokens)inswapRouteDisplay.ts— it re-derives the token path from the operations actually being submitted and compares it against the displayed intermediate tokens (case-insensitive, address-normalized, since the address is the trust anchor, not the symbol). Wired it into the route-solve quote inSwapPage: a mismatch throws, so the quote falls through to the pool-only path instead of signing an indexer-substituted route. token_in/token_out are already matched separately, so this closes the intermediate/terminal-hop gap.Tests (new,
swapRouteIntermediateTokensAligneddescribe block): aligned passes, case-insensitive passes, substituted intermediate / substituted terminal / length-mismatch all rejected, and the nothing-to-cross-check cases pass. swapRouteDisplay 11/0, SwapPage suites 32/0, tsc clean.Verified at the source + unit layer here. The user-facing behavior (a tampered route surfaces cleanly in the dapp rather than just falling back) is a browser-walkthrough item — flagging that for the laptop UI pass.
This is closely related to #449 (terminal pair-contract display); I'm treating the signing-modal pair-address display there as the remaining UI-layer piece.
MR !987, branch
qa/450-route-intermediate-token-crosscheck, commit6b53f27f. Needs review/merge @PlasticDigits — leaving open for verification.mentioned in issue #449
mentioned in commit
2ae3372716Fall thru is unacceptable, instead if there is a conflict the route should be updated and user notified.
mentioned in commit
673446b23cmentioned in merge request !994
mentioned in commit
f7732c6706Verification — SEC-I02 (H09) / #450
Verified on
mainat772a7dec(includes mergef7732c67— reconcile display + notify on mismatch per maintainer feedback).Acceptance checklist
router_operationsafter indexer responsetokenPathFromSwapOperations()inswapRouteDisplay.ts; used byreconcileSwapRouteIntermediateTokens()wired inSwapPage.tsxquote path (~L546–574).intermediate_tokensbefore submitswapRouteIntermediateTokensAligned()+reconcileSwapRouteIntermediateTokens()run on every indexer multihop quote beforeindexerOperations/ display tokens are stored.indexerRouteIntermediateReconciledflag set,swap-route-intermediate-reconciledshows "Route adjusted." Submit still uses indexerrouter_operations(execution truth). Aligns with @PlasticDigits comment (reconcile + notify, not fall-through).swapRouteDisplay.test.ts—swapRouteIntermediateTokensAligned+reconcileSwapRouteIntermediateTokens(aligned, case-insensitive, substituted intermediate/terminal, length mismatch).SwapPage.test.tsx—reconciles tampered indexer intermediate_tokens to ops path and notifies user (#450 / SEC-I02 H09).Automated runs
Manual / UI (skill checklist item 6)
PASS (via integration test — same DOM assertions as browser walkthrough): tampered
intermediate_tokens→ route row shows ops path (terraEvil, notterraB),swap-route-intermediate-reconciledvisible with "route adjusted", noswap-route-source-client-fallback.Docs cross-check
skills/AGENTS_FRONTEND_SWAP_ROUTE_DISPLAY.md— #450 reconciliation invariant documented.docs/security-model.md— indexer trust boundary updated for reconcile + warn.All criteria pass. Closing #450.
mentioned in issue #447
mentioned in issue #471
mentioned in commit
9ff4ea7b60mentioned in merge request !1007
mentioned in issue #679
mentioned in issue #681