Profile

Identifier Verified
Vitor Pamplona banner
Vitor Pamplona avatar

Vitor Pamplona

_@vitorpamplona.com
npub1gcxzte5zlkncx26j68ez60fzkvtkm9e0vrwdcvsjakxf9mu9qewqlfnj5z
Signature Valid

Nostr's Chief Android Officer - Amethyst/Brainstorm

Recent Notes (10)

10
KIND 1 / NOTES
Note
Thanks for the runtime testing on the last revision. We reviewed revision `3a7be89` in depth, and we can't merge it in this shape. The individual bugs are below, but most of them come from one design choice, so the direction comes first.

## Why not this shape

**It replaces the browser's whole network stack.** On both Android and desktop, every `.bit` request is intercepted and refetched with our own OkHttp client: name resolution, TCP, TLS checks, redirects, cookies, headers and streaming all get rebuilt in the app. Each of those ends up weaker than the browser's own, and most of the findings below are one of those layers.

**The TLS check can't deliver what it appears to.** The TLSA record that the certificate check pins to comes from a single ElectrumX server, and nothing verifies it (quartz has no SPV proofs yet). Whoever runs that server, or controls the path to it, can return their own TLSA record and IP, and the page loads as if it were authenticated. Until name data is verified (SPV proofs, or several independent servers that must agree), HTTPS `.bit` browsing can't promise more than "an ElectrumX server said so". That's fine for a NIP-05 label; it's not fine for a page you log into.

**It's two features in one PR.** One is `.bit` support. The other is a new in-app web browser for desktop. Desktop has no in-app browser for regular sites; links open in the system browser. This PR builds one on top of the napplet session (`NappletLaunch.fromWebUrl` makes a pseudo-napplet with `authorPubKey = "web"`) and sends every desktop link and web app there. That's a product change of its own, and it shouldn't arrive inside a `.bit` PR. It also means plain web pages now run in the session type built for signed napplets, with its bridge still installed.

**Napplets don't need this.** Napplets (NIP-5D) and nSites (NIP-5A) are addressed by pubkey and fetched by content hash from relays and Blossom, and webxdc apps are meant to have no network at all. On Android, hooking `NappletBrowserActivity` / `NappletBrowserService` makes sense, because the Browser tab lives in `nappletHost`. But this PR also hooks the real napplet, nSite and webxdc hosts (`NappletHostActivity`, `NappletHostService`) with no profile gate. The only place `.bit` means anything is the Browser tab.

## What we would accept

1. **`.bit` → nSite.** Namecoin records can already carry a Nostr pubkey (that's how `.bit` NIP-05 works). Resolve `x.bit` to its pubkey with the existing resolver and open that pubkey's nSite in the existing sandbox. Content is verified by hash, so it needs no TLS, no DANE and no interception. This is the change we'd most like to see.
2. **A handoff for everything else.** If a `.bit` URL has no pubkey, say it's a Namecoin site and offer to open it in the system browser (ncdns, Tor Browser).
3. **Full `.bit` browsing in the Browser tab, later, and only once name data is verified.** Keep the browser's network stack and replace only two things: name resolution, through the proxy seam the Browser tab already has (`WebViewProxyPolicy`), and the certificate decision, through `onReceivedSslError` / CEF `onCertificateError` checked against TLSA. No request interception.
4. **Leave desktop link handling alone.** Keep `DesktopBrowser.open` for ordinary links. An in-app desktop browser is a separate discussion.
5. **Layering.** The resolver for Namecoin's domain format (import/delegate/map, with a global lookup budget) belongs in quartz, next to the existing Namecoin code. The app should go through `NamecoinServices` so the user's custom servers, Core RPC and Tor server list are honoured. The `:napplet` process should ask the main process over IPC rather than hard-coding the public servers.

## Findings on `3a7be89`

For reference, if any of this code is reused:

**Security**
- **Lookup explosion.** In `NamecoinBitResolver.kt` (around line 190), each `map` branch starts a fresh cycle set (`linkedSetOf()`), so a name can import itself from every entry. Only the merge depth limit of 4 stops it, and the whole tree is parsed eagerly. A 513-byte value with 24 self-importing labels causes about 8.3M serial ElectrumX lookups for one visit, each inside `runBlocking`. One malicious link hangs the browser and floods the public servers. The user doesn't even need to visit a `.bit` site: interception is per request, so any ordinary page that embeds `<img src="https://evil.bit/">` triggers it.
- **Stale records over Tor.** In Tor mode, records (including TLSA) are cached for the life of the process (`NamecoinBitOkHttp.kt:87-88`). A rotated or revoked key is still accepted until restart.
- **Tor sends the name to the exit.** In Tor mode without a `_tor` record, the `ip` records are never used, and the `.bit` hostname goes to the Tor exit to resolve. Plain `http://x.bit` content is whatever the exit returns.
- **Name Constraints are accepted but not enforced** (`NamecoinDaneTaValidator.kt:102-109`). A sub-CA constrained to `shop.x.bit` can issue a certificate for `www.x.bit`, and it's accepted.
- **Failed lookups fall back silently.** A failed `delegate` lookup makes the resolver use the local records the delegation was meant to replace, and a failed `import` drops its `_tor`. A flaky or hostile server can choose the outcome.
- **No profile gate on Android.** The transport runs for every profile, including napplets and webxdc (`NappletHostActivity.kt:677`, `NappletHostService.kt:663`), so CSP is the only barrier.
- **Requests that bypass the interception:** WebSockets, service workers and downloads. They reach system DNS, or the Tor exit when Tor is on.
- **Stack traces in pages.** On desktop, failure pages embed full stack traces under the site's origin (`NamecoinBitCefTransport.kt:299`), where page JS can read them.

**Breakage**
- **Desktop: one slow site freezes every Chromium window.** Bodies are read with blocking calls on CEF's shared IO thread (60s timeout over Tor). It also starts a new thread per subresource, and a cancelled request leaks its connection. `ChromiumMediaSession` on main shows how to read off that thread.
- **Desktop: all links now open in the in-app Chromium.** `DesktopAppPlatform.kt:226` replaces `DesktopBrowser.open` for every link and web app; with no login or no Chromium, the user gets a "site failed" toast. `onBeforePopup` loads every `window.open` in place, which breaks OAuth popups. This is a change to normal link handling unrelated to `.bit`.
- **Redirects.** `x.bit` → `www.x.bit` returns a 502. Same-host redirects serve the body under the original URL, so relative links break, and after http→https the page keeps the http origin.
- **Cookies and headers.** On Android, cookies and logins can't work through interception, multi-value headers are joined with commas on both platforms (which breaks `Set-Cookie`), and a 3xx with no `Location` (such as a 304) throws.
- **Video seeking on desktop.** `skip()` isn't overridden, so CEF cancels every range request.
- **Settings ignored.** The user's ElectrumX servers, Core RPC and Tor server list are ignored, so browsed names leak to public servers.

**Tests and housekeeping**
- **Fail-closed paths untested.** `NamecoinBitTrustManager` and `BitHostnameVerifier` have no tests. In the chain validator, the validity, CA-flag, key-usage and path-length checks can each be deleted with every test still passing. `derivedClientDoesNotRewriteOriginalBitUrl` never uses the client it builds.
- **Hardcoded strings.** User-visible English text is hardcoded; it belongs in `commonsUI` compose resources.
- **Bouncy Castle.** `bctls` is pinned inline instead of in `libs.versions.toml`.
- **Base.** The PR is 332 commits behind main and conflicts in `CefNappletSession.kt`.

A smaller PR for item 1 (`.bit` → nSite) would be very welcome.
Note
Still early days for Amethyst desktop. Let us know if you see anything weird.
Note
Too many Cody's on nostr...
Note
Trust is also quite expensive at scale. To me the filter will always be more expensive than signal because now the cost of creating content is zero. Trust-based agents can simplify some of their work, but they still need to constantly adapt to all the tricks content providers are building.
Note
Agents are the best ad blockers
Note
Lots of people just put in their outbox relays :( because clients don't offer any other relay list.
Note
You disable it by doing the right thing and not putting it on your outbox setting. The broadcast relay list is a separate event kind made just for this.
Note
It works as long as you put on the Broadcast relay section on Amethyst. If you put on the others, it will make things slower since this relay doesn't reply to reqs.
Technical details allowlist: verified allowed
typepubkey
canonical/npub1gcxzte5zlkncx26j68ez60fzkvtkm9e0vrwdcvsjakxf9mu9qewqlfnj5z
allowlistverified allowed
verificationID Verified · Signature Valid
pubkey460c25e682fda7832b52d1f22d3d22b3176d972f60dcdc3212ed8c92ef85065c
nameVitor Pamplona
nip05_@vitorpamplona.com
aboutNostr's Chief Android Officer - Amethyst/Brainstorm
recent notes fetched10
relays queriedwss://purplepag.es, wss://nos.lol, wss://relay.damus.io, wss://relay.primal.net, wss://relay.nostr.org.tr, wss://relay.emre.xyz