Security: explorer URL builder interpolates tx hash and address directly with no sanitization tests [SEC-E10] #430

Closed
opened 2026-06-29 14:51:38 +00:00 by totdking · 13 comments
totdking commented 2026-06-29 14:51:38 +00:00 (Migrated from gitlab.com)
No description provided.
totdking commented 2026-06-29 14:52:32 +00:00 (Migrated from gitlab.com)

Summary

terraExplorer.ts implements getExplorerTxUrl(txHash: string) and getExplorerAddressUrl(address: string). Both functions interpolate their string argument directly into URL templates: line 44-47 returns ${base}${txHash} and line 62-64 returns ${base}${address}. No sanitization, encoding, or validation is applied to either argument. The tests in terraExplorer.test.ts only exercise valid well-formed inputs. No test passes adversarial inputs such as a javascript: prefix, <script> characters, or a path-traversal sequence. If an attacker can influence the displayed tx hash or address (e.g., through a spoofed indexer response), the builder would produce a misleading or potentially injectable URL.


What Was Checked

  • frontend-dapp/src/utils/terraExplorer.ts (read directly): getExplorerTxUrl at line 44-47 returns ${base}${txHash}. getExplorerAddressUrl at line 62-64 returns ${base}${address}. No validation applied to either argument.
  • frontend-dapp/src/utils/__tests__/terraExplorer.test.ts: all tests use SAMPLE_TX = 'C8845E73934EC9016F751B65F722DBCFBF167C7C5FC4238E5DF39437451412DB' and SAMPLE_ADDRESS = 'terra1x46rqay4d3cssq8gxxvqz8xt6nwlz4td20k38v'. Valid inputs only. No adversarial input tested.
  • No test in AddressRow.test.tsx or TerraBroadcastPendingLink.test.tsx asserts that rendered href attributes are free of unencoded dangerous characters.

Expected (per checklist)

Unit tests must verify that malicious or malformed inputs do not produce injectable or misleading explorer URLs. At minimum: a javascript: prefix in the hash must not produce a javascript: URL; special characters must be encoded or the function must return null.


Actual

The builder accepts any string and interpolates it without change. No test confirms safe behavior under adversarial input. If indexer-sourced data contains a crafted value, the resulting URL is passed unmodified to the renderer.


Evidence

  • frontend-dapp/src/utils/terraExplorer.ts lines 44-47: ${base}${txHash} with no sanitization
  • frontend-dapp/src/utils/terraExplorer.ts lines 62-64: ${base}${address} with no sanitization
  • frontend-dapp/src/utils/__tests__/terraExplorer.test.ts: SAMPLE_TX and SAMPLE_ADDRESS are valid inputs only

Suggested Fix

Add tests to frontend-dapp/src/utils/__tests__/terraExplorer.test.ts covering: a txHash prefixed with javascript:alert(1) asserting the result does not start with javascript:; a txHash containing <script> characters asserting they are encoded or the function returns null; an empty string asserting a null or clearly invalid result. If React's JSX renderer already blocks javascript: href values at render time, document this in the test with a comment explaining the defense layer and add a link-rendering assertion in AddressRow.test.tsx or TerraBroadcastPendingLink.test.tsx.


Verification Checklist

  • terraExplorer.test.ts has a test for javascript: prefix input asserting the output is not a javascript: URL
  • terraExplorer.test.ts has a test for HTML-special characters in tx hash or address
  • Link rendering tests assert the rendered href is safe under adversarial input, or a comment documents the React-layer defense
  • If the builder already rejects bad inputs, tests explicitly confirm that behavior

Labels

security, pre-launch

Cc: @PlasticDigits

### Summary `terraExplorer.ts` implements `getExplorerTxUrl(txHash: string)` and `getExplorerAddressUrl(address: string)`. Both functions interpolate their string argument directly into URL templates: line 44-47 returns `${base}${txHash}` and line 62-64 returns `${base}${address}`. No sanitization, encoding, or validation is applied to either argument. The tests in `terraExplorer.test.ts` only exercise valid well-formed inputs. No test passes adversarial inputs such as a `javascript:` prefix, `<script>` characters, or a path-traversal sequence. If an attacker can influence the displayed tx hash or address (e.g., through a spoofed indexer response), the builder would produce a misleading or potentially injectable URL. --- ### What Was Checked - `frontend-dapp/src/utils/terraExplorer.ts` (read directly): `getExplorerTxUrl` at line 44-47 returns `${base}${txHash}`. `getExplorerAddressUrl` at line 62-64 returns `${base}${address}`. No validation applied to either argument. - `frontend-dapp/src/utils/__tests__/terraExplorer.test.ts`: all tests use `SAMPLE_TX = 'C8845E73934EC9016F751B65F722DBCFBF167C7C5FC4238E5DF39437451412DB'` and `SAMPLE_ADDRESS = 'terra1x46rqay4d3cssq8gxxvqz8xt6nwlz4td20k38v'`. Valid inputs only. No adversarial input tested. - No test in `AddressRow.test.tsx` or `TerraBroadcastPendingLink.test.tsx` asserts that rendered `href` attributes are free of unencoded dangerous characters. --- ### Expected (per checklist) Unit tests must verify that malicious or malformed inputs do not produce injectable or misleading explorer URLs. At minimum: a `javascript:` prefix in the hash must not produce a `javascript:` URL; special characters must be encoded or the function must return null. --- ### Actual The builder accepts any string and interpolates it without change. No test confirms safe behavior under adversarial input. If indexer-sourced data contains a crafted value, the resulting URL is passed unmodified to the renderer. --- ### Evidence - `frontend-dapp/src/utils/terraExplorer.ts` lines 44-47: `${base}${txHash}` with no sanitization - `frontend-dapp/src/utils/terraExplorer.ts` lines 62-64: `${base}${address}` with no sanitization - `frontend-dapp/src/utils/__tests__/terraExplorer.test.ts`: SAMPLE_TX and SAMPLE_ADDRESS are valid inputs only --- ### Suggested Fix Add tests to `frontend-dapp/src/utils/__tests__/terraExplorer.test.ts` covering: a txHash prefixed with `javascript:alert(1)` asserting the result does not start with `javascript:`; a txHash containing `<script>` characters asserting they are encoded or the function returns null; an empty string asserting a null or clearly invalid result. If React's JSX renderer already blocks `javascript:` href values at render time, document this in the test with a comment explaining the defense layer and add a link-rendering assertion in `AddressRow.test.tsx` or `TerraBroadcastPendingLink.test.tsx`. --- ### Verification Checklist - [ ] `terraExplorer.test.ts` has a test for `javascript:` prefix input asserting the output is not a `javascript:` URL - [ ] `terraExplorer.test.ts` has a test for HTML-special characters in tx hash or address - [ ] Link rendering tests assert the rendered `href` is safe under adversarial input, or a comment documents the React-layer defense - [ ] If the builder already rejects bad inputs, tests explicitly confirm that behavior --- ### Labels `security`, `pre-launch` Cc: @PlasticDigits
PlasticDigits commented 2026-06-29 15:16:59 +00:00 (Migrated from gitlab.com)

mentioned in commit 7e28b26b96

mentioned in commit 7e28b26b96a88bc4b2496a3fe960758126f7d3f5
PlasticDigits commented 2026-06-29 15:17:10 +00:00 (Migrated from gitlab.com)

mentioned in merge request !958

mentioned in merge request !958
PlasticDigits commented 2026-06-29 15:18:04 +00:00 (Migrated from gitlab.com)

mentioned in commit 8a7e7adda1

mentioned in commit 8a7e7adda1fefecb2792daa7e5ed1370d0178381
Brouie commented 2026-06-29 15:20:22 +00:00 (Migrated from gitlab.com)

Verified the merged fix (!958, main at 8a7e7add). SEC-E10 is properly closed — the builders now allowlist-validate before interpolating:

  • getExplorerTxUrl: isSafeExplorerTxHash gates on /^[0-9a-fA-F]{64}$/, returns null otherwise.
  • getExplorerAddressUrl: isSafeExplorerAddress gates on isValidTerraBech32Address, returns null otherwise.

So a crafted/unvalidated arg never reaches the URL template — no interpolation of attacker input. The adversarial-input tests (#430 / SEC-E10) all pass for both builders: javascript: prefix -> null, HTML-special chars -> null, empty string -> null, path-traversal segments -> null.

One caveat so it's not misread: running the file on a host whose LCD env is 127.0.0.1 shows 2 failing tests, but they're the pre-existing terraExplorer localhost-vs-127.0.0.1 env artifact — the local LCD-fallback URL tests hardcode localhost while the env resolves 127.0.0.1. Nothing to do with #430. Proved it: re-run with VITE_TERRA_LCD_URL=http://localhost:1317 -> 15/15 green. The sanitization cases pass regardless of that env var.

Good to close from my side. @PlasticDigits — the MR's merged but the issue is still open.

Verified the merged fix (!958, main at 8a7e7add). SEC-E10 is properly closed — the builders now allowlist-validate before interpolating: - getExplorerTxUrl: isSafeExplorerTxHash gates on /^[0-9a-fA-F]{64}$/, returns null otherwise. - getExplorerAddressUrl: isSafeExplorerAddress gates on isValidTerraBech32Address, returns null otherwise. So a crafted/unvalidated arg never reaches the URL template — no interpolation of attacker input. The adversarial-input tests (#430 / SEC-E10) all pass for both builders: javascript: prefix -> null, HTML-special chars -> null, empty string -> null, path-traversal segments -> null. One caveat so it's not misread: running the file on a host whose LCD env is 127.0.0.1 shows 2 failing tests, but they're the pre-existing terraExplorer localhost-vs-127.0.0.1 env artifact — the local LCD-fallback URL tests hardcode `localhost` while the env resolves `127.0.0.1`. Nothing to do with #430. Proved it: re-run with VITE_TERRA_LCD_URL=http://localhost:1317 -> 15/15 green. The sanitization cases pass regardless of that env var. Good to close from my side. @PlasticDigits — the MR's merged but the issue is still open.
PlasticDigits commented 2026-06-29 15:28:25 +00:00 (Migrated from gitlab.com)

Verification — SEC-E10 / #430

Verified merged fix on main (MR !958). All acceptance criteria PASS.

Checklist

Criterion Result How verified
terraExplorer.test.ts rejects javascript: prefix (not a javascript: URL) PASS adversarial input (#430 / SEC-E10) cases for getExplorerTxUrl and getExplorerAddressUrl assert toBeNull() for javascript:alert(1) and prefixed variants
terraExplorer.test.ts rejects HTML-special characters in tx hash / address PASS Tests assert null for <script>-containing hash and address strings
Link rendering safe under adversarial input, or React-layer defense documented PASS TerraBroadcastPendingLink.test.tsx renders <span> (no anchor) for javascript:alert(1) tx hash; comment notes builder returns null before React href sanitization. AddressRow.test.tsx omits explorer link when getExplorerAddressUrl returns null
Builder rejection of bad inputs explicitly tested PASS Allowlist gates in terraExplorer.ts: isSafeExplorerTxHash (/^[0-9a-fA-F]{64}$/) and isSafeExplorerAddress (isValidTerraBech32Address); adversarial suites also cover empty string and path-traversal (../etc/passwd) for tx hash

Commands

bash scripts/with-node.sh --cwd frontend-dapp -- ./node_modules/.bin/vitest run \
  src/utils/__tests__/terraExplorer.test.ts \
  src/components/ui/__tests__/TerraBroadcastPendingLink.test.tsx \
  src/components/ui/__tests__/AddressRow.test.tsx

Result: 3 files, 23 tests — all green (15 in terraExplorer.test.ts).

Implementation review

  • getExplorerTxUrl / getExplorerAddressUrl validate before interpolating into URL templates; crafted input never reaches ${base}${…}.
  • No repo changes required; closing issue.
## Verification — SEC-E10 / #430 Verified merged fix on `main` (MR !958). All acceptance criteria **PASS**. ### Checklist | Criterion | Result | How verified | |-----------|--------|--------------| | `terraExplorer.test.ts` rejects `javascript:` prefix (not a `javascript:` URL) | **PASS** | `adversarial input (#430 / SEC-E10)` cases for `getExplorerTxUrl` and `getExplorerAddressUrl` assert `toBeNull()` for `javascript:alert(1)` and prefixed variants | | `terraExplorer.test.ts` rejects HTML-special characters in tx hash / address | **PASS** | Tests assert null for `<script>`-containing hash and address strings | | Link rendering safe under adversarial input, or React-layer defense documented | **PASS** | `TerraBroadcastPendingLink.test.tsx` renders `<span>` (no anchor) for `javascript:alert(1)` tx hash; comment notes builder returns null before React href sanitization. `AddressRow.test.tsx` omits explorer link when `getExplorerAddressUrl` returns null | | Builder rejection of bad inputs explicitly tested | **PASS** | Allowlist gates in `terraExplorer.ts`: `isSafeExplorerTxHash` (`/^[0-9a-fA-F]{64}$/`) and `isSafeExplorerAddress` (`isValidTerraBech32Address`); adversarial suites also cover empty string and path-traversal (`../etc/passwd`) for tx hash | ### Commands ```bash bash scripts/with-node.sh --cwd frontend-dapp -- ./node_modules/.bin/vitest run \ src/utils/__tests__/terraExplorer.test.ts \ src/components/ui/__tests__/TerraBroadcastPendingLink.test.tsx \ src/components/ui/__tests__/AddressRow.test.tsx ``` **Result:** 3 files, 23 tests — all green (15 in `terraExplorer.test.ts`). ### Implementation review - `getExplorerTxUrl` / `getExplorerAddressUrl` validate before interpolating into URL templates; crafted input never reaches `${base}${…}`. - No repo changes required; closing issue.
PlasticDigits (Migrated from gitlab.com) closed this issue 2026-06-29 15:28:26 +00:00
totdking commented 2026-06-29 15:31:48 +00:00 (Migrated from gitlab.com)

mentioned in issue #381

mentioned in issue #381
PlasticDigits commented 2026-06-30 02:29:20 +00:00 (Migrated from gitlab.com)

mentioned in issue #424

mentioned in issue #424
PlasticDigits commented 2026-07-12 07:45:45 +00:00 (Migrated from gitlab.com)

mentioned in issue #478

mentioned in issue #478
PlasticDigits commented 2026-08-17 03:45:37 +00:00 (Migrated from gitlab.com)

mentioned in issue #541

mentioned in issue #541
PlasticDigits commented 2026-08-19 01:03:03 +00:00 (Migrated from gitlab.com)

mentioned in issue #570

mentioned in issue #570
PlasticDigits commented 2026-08-26 03:07:39 +00:00 (Migrated from gitlab.com)

mentioned in issue #656

mentioned in issue #656
PlasticDigits commented 2026-08-26 04:15:27 +00:00 (Migrated from gitlab.com)

mentioned in issue #664

mentioned in issue #664
Sign in to join this conversation.
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#430
No description provided.