Files
amethyst/commons
Vitor PamplonaandClaude Opus 4.8 6045e28830 fix(cashu): validate a token's mint URL before contacting it
`MintHttpClient` only trimmed trailing slashes — no scheme check, no host
check — and `token.mint` comes verbatim from any pasted or posted Cashu
token. Tapping Redeem on a token in someone's note therefore made the
device issue HTTP requests to an arbitrary URL: `http://127.0.0.1:<port>`,
LAN addresses, `169.254.169.254` (cloud metadata), any scheme at all —
plus it disclosed the user's IP to whoever controlled the URL.

Validation now runs in the constructor, so no caller can issue a request
before it. The rule: `https://` to a public host, or `http://` to a
`.onion` host, and nothing else. Onion mints matter — a blanket
"https only" rule would have silently broken every Tor mint.

Rejected hosts cover the private/loopback/link-local/unique-local ranges
plus CGNAT, multicast, reserved and `0/8`: none is a public unicast host,
so allowing them buys nothing and leaks reachability.

The bypasses are what make this worth care, and each has a test:
IPv4-mapped and IPv4-compatible IPv6 (`::ffff:127.0.0.1`, `::127.0.0.1`),
the full `inet_aton` spellings (`2130706433`, `0177.0.0.1`, `0x7f000001`,
`127.1`), trailing-dot hosts, and userinfo disguise
(`https://mint.example.com@127.0.0.1/`) — handled by splitting on the
LAST `@`. The host parse is hand-rolled rather than delegated to
`java.net.URI`/`HttpUrl` precisely because those normalise these forms
inconsistently.

A mint the user added to their own wallet is exempt from the host and
https rules — a self-hosted mint on a LAN is a legitimate setup, and the
threat here is a *pasted, untrusted* token pointing inward, not a mint
the user chose. The exemption never relaxes the scheme check. It is
threaded properly rather than TODO'd: the melt path passes the wallet's
known mints and marks the mint user-configured only on a match; the
wallet ops and CLI pass it directly, since those URLs are the user's own.

Refusal gets its own message rather than reusing the mint-error string,
whose wording would have misattributed our own refusal to the mint.

DNS rebinding is out of scope and noted in a comment — the check runs
pre-resolution and cannot defend against a host that resolves differently
on the second lookup.

Verified by disabling the scheme and host checks: 11 of 20 tests fail,
every rejection case among them, and every allow case still passes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-19 21:02:59 -04:00
..