frontend: cross-validate route intermediate tokens vs submitted ops (#450) #987

Merged
Brouie merged 1 commit from qa/450-route-intermediate-token-crosscheck into main 2026-06-30 22:03:54 +00:00
Brouie commented 2026-06-30 19:13:31 +00:00 (Migrated from gitlab.com)

The Swap route display uses indexer intermediate_tokens while submit is built
from router_operations — both from the indexer, with no cross-check. A
malicious/compromised indexer could display one path and submit another within
slippage tolerance (SEC-I02 H09).

Add swapRouteIntermediateTokensAligned(operations, intermediateTokens) —
re-derives the token path from the submitted operations and compares it
(case-insensitive, address-normalized) against the displayed intermediate
tokens. Wired into the route-solve quote in SwapPage: a mismatch throws, so the
quote falls through to the pool-only path instead of signing an indexer-
substituted route. Aligned/empty paths are unaffected (token_in/token_out are
already matched separately).

Tests: 6 new cases (aligned, case-insensitive, substituted intermediate,
substituted terminal, length mismatch, nothing-to-check); swapRouteDisplay 11/0,
SwapPage suites 32/0, tsc clean.

Note: the signing-modal pair-address display side of the route-integrity work
(#449) still needs the browser/UI layer.

The Swap route display uses indexer `intermediate_tokens` while submit is built from `router_operations` — both from the indexer, with no cross-check. A malicious/compromised indexer could display one path and submit another within slippage tolerance (SEC-I02 H09). Add `swapRouteIntermediateTokensAligned(operations, intermediateTokens)` — re-derives the token path from the submitted operations and compares it (case-insensitive, address-normalized) against the displayed intermediate tokens. Wired into the route-solve quote in SwapPage: a mismatch throws, so the quote falls through to the pool-only path instead of signing an indexer- substituted route. Aligned/empty paths are unaffected (token_in/token_out are already matched separately). Tests: 6 new cases (aligned, case-insensitive, substituted intermediate, substituted terminal, length mismatch, nothing-to-check); swapRouteDisplay 11/0, SwapPage suites 32/0, tsc clean. Note: the signing-modal pair-address display side of the route-integrity work (#449) still needs the browser/UI layer.
Brouie commented 2026-06-30 19:13:32 +00:00 (Migrated from gitlab.com)

assigned to @Brouie

assigned to @Brouie
Brouie commented 2026-06-30 19:13:45 +00:00 (Migrated from gitlab.com)

mentioned in issue #450

mentioned in issue #450
PlasticDigits commented 2026-06-30 19:16:35 +00:00 (Migrated from gitlab.com)

Security review — MR !987

Commit reviewed: 6b53f27fea3361e4a8b7804bc397fba5edadaeac
Scope: swapRouteIntermediateTokensAligned helper + wiring in SwapPage getRouteSolve quote path; unit tests (SEC-I02 H09 / #450).

Outcome: FINDINGS: 0 medium+

Summary

This MR closes the documented display/submit split for indexer CW20↔CW20 hybrid quotes: computeSwapRouteDisplay can render intermediate_tokens while submit uses router_operations. The new helper re-derives the token path from operations via tokenPathFromSwapOperations and compares it case-insensitively to the displayed intermediate list; mismatch throws and the quote falls through to pool-only fallback instead of signing a substituted route.

Attack-path tracing (no exploitable gaps found on this diff):

  1. Primary spoof (honest intermediate_tokens, evil router_operations) — blocked: element-wise address compare fails; quote rejected.
  2. Wrong-length intermediate_tokens (falls back to hops-derived path) — still cross-checked against ops; hops vs router_operations disagreement is caught the same way.
  3. Early true when ops/intermediates absent — not exploitable on the wired path: intermediates is always materialized from token_in + hops when the guard runs; empty ops cannot satisfy swapOperationsFromIndexerResponse for multihop.
  4. enrichSwapOperationsWithHopMinReturns — only attaches min_return; offer/ask assets unchanged, so validation on opsForQuote matches submit-time ops.
  5. Other indexer entry points (postRouteSolve in SwapPage / TradeMarketOrderPanel) — unchanged and not a regression: they never set indexerIntermediateTokens; display already derives from indexerOperations only (no split-trust surface this MR targets).
  6. Submit/quote TOCTOU — existing useSubmitAlignedSimQuote keeps indexerOperations and indexerIntermediateTokens on the same debounced snapshot.

Inline threads: none (no medium+ findings).

Security review: no medium+ findings on this diff.

## Security review — MR !987 **Commit reviewed:** `6b53f27fea3361e4a8b7804bc397fba5edadaeac` **Scope:** `swapRouteIntermediateTokensAligned` helper + wiring in SwapPage `getRouteSolve` quote path; unit tests (SEC-I02 H09 / #450). **Outcome:** `FINDINGS: 0` medium+ ### Summary This MR closes the documented display/submit split for indexer CW20↔CW20 hybrid quotes: `computeSwapRouteDisplay` can render `intermediate_tokens` while submit uses `router_operations`. The new helper re-derives the token path from operations via `tokenPathFromSwapOperations` and compares it case-insensitively to the displayed intermediate list; mismatch throws and the quote falls through to pool-only fallback instead of signing a substituted route. **Attack-path tracing (no exploitable gaps found on this diff):** 1. **Primary spoof (honest `intermediate_tokens`, evil `router_operations`)** — blocked: element-wise address compare fails; quote rejected. 2. **Wrong-length `intermediate_tokens` (falls back to hops-derived path)** — still cross-checked against ops; hops vs `router_operations` disagreement is caught the same way. 3. **Early `true` when ops/intermediates absent** — not exploitable on the wired path: `intermediates` is always materialized from `token_in` + hops when the guard runs; empty ops cannot satisfy `swapOperationsFromIndexerResponse` for multihop. 4. **`enrichSwapOperationsWithHopMinReturns`** — only attaches `min_return`; offer/ask assets unchanged, so validation on `opsForQuote` matches submit-time ops. 5. **Other indexer entry points (`postRouteSolve` in SwapPage / TradeMarketOrderPanel)** — unchanged and not a regression: they never set `indexerIntermediateTokens`; display already derives from `indexerOperations` only (no split-trust surface this MR targets). 6. **Submit/quote TOCTOU** — existing `useSubmitAlignedSimQuote` keeps `indexerOperations` and `indexerIntermediateTokens` on the same debounced snapshot. **Inline threads:** none (no medium+ findings). Security review: no medium+ findings on this diff.
PlasticDigits commented 2026-06-30 22:03:54 +00:00 (Migrated from gitlab.com)

mentioned in commit 2ae3372716

mentioned in commit 2ae337271615da6c54255738fa15b3b8f47716d3
PlasticDigits (Migrated from gitlab.com) merged commit 2ae3372716 into main 2026-06-30 22:03:54 +00:00
PlasticDigits commented 2026-06-30 22:20:19 +00:00 (Migrated from gitlab.com)

mentioned in merge request !994

mentioned in merge request !994
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
code/cl8y-dex-terraclassic!987
No description provided.