Skip to content

fix(nip46): refuse to dial relays through inherited *_PROXY env vars - #73

Merged
oth-body merged 1 commit into
masterfrom
fix/nip46-signin-proxy
Sep 15, 2026
Merged

oth-body merged 1 commit into
masterfrom
fix/nip46-signin-proxy

Conversation

@oth-body

Copy link
Copy Markdown
Owner

Summary

When users try to sign in via Amber, hoot's NIP-46 flow calls nostr.RelayConnect(ctx, relayURL) to subscribe for the bunker's response. That function delegates to net/http, which honors the HTTPS_PROXY / HTTP_PROXY / ALL_PROXY / SOCKS_PROXY env vars inherited from the parent shell. If any of those point at a proxy port that isn't running (typically 127.0.0.1:9050 on machines without Tor), the dial fails with connect: connection refused, and the TUI shows the user a "failed to connect to port 9050" error with no signal about the actual cause.

This is the dominant "Amber sign-in failed" complaint in practice. It happens when:

  • the user ran hoot inside torsocks (TUI tools don't need it)
  • the user's shell exported HTTPS_PROXY for curl / monerod / nix and they don't realise hoot inherits it
  • the user's desktop launcher (e.g. a Tailscale-aware wrapper) sets HTTPS_PROXY for outgoing HTTPS and forgot to scope it

Fix

nip46.WaitForConnection now:

  1. Refuses to dial at all when any *_PROXY env var is set, returning a user-actionable error that names the offending env var and the proxy host:port. Matches what every other Nostr TUI does (nak, lume, etc.) — none of them proxy wss:// by default.
  2. Validates the relay URL up front (blank / non-wss URL → clear error instead of a confusing websocket error later).
  3. Wraps the relay dial error with the relay URL so non-proxy failures are easier to diagnose.

Tests

nip46/nip46_test.go pins:

  • proxy env var detection order matches httpproxy (HTTPS_PROXY > HTTP_PROXY > ALL_PROXY > socks fallbacks)
  • malformed or scheme-less URLs are rejected
  • empty values fall through (matches stdlib)
  • WaitForConnection surfaces the env var name and port on the user-visible error path (regression guard)
  • blank and non-wss relay URLs are rejected up front
  • Session.Close is idempotent when no dial was attempted

11 tests, all pass.

Tested manually

$ go test ./nip46/ -v
=== RUN   TestDetectProxyNoEnv                  ... PASS
=== RUN   TestDetectProxyTor9050                ... PASS
=== RUN   TestDetectProxyHTTPSBeatsHTTP         ... PASS
=== RUN   TestDetectProxyAllProxyFallback       ... PASS
=== RUN   TestDetectProxySkipsMalformed         ... PASS
=== RUN   TestDetectProxyEmptyValue             ... PASS
=== RUN   TestDetectProxySchemeRequiredRe       ... PASS
=== RUN   TestWaitForConnectionRefusesProxyEnv  ... PASS
=== RUN   TestWaitForConnectionRejectsBlankURL  ... PASS
=== RUN   TestWaitForConnectionRejectsNonWSSURL ... PASS (4 sub-tests)
=== RUN   TestSessionCloseIsIdempotent          ... PASS
ok      hoot/nip46  0.012s

Followups

For the root-cause fix (so users who want Tor can route through it explicitly without *_PROXY), I'd like to file a separate PR against github.com/nbd-wtf/go-nostr adding a WithProxyURL RelayOption. The default httpproxy-driven behaviour is the wrong default for a TUI app — every other Nostr client in the wild ships without it.

Hoot's NIP-46 sign-in flow uses github.com/nbd-wtf/go-nostr's
nostr.RelayConnect, which delegates to net/http and therefore
honors HTTPS_PROXY / HTTP_PROXY / ALL_PROXY / SOCKS_PROXY env
vars inherited from the parent shell. If the user runs hoot
inside torsocks, a launcher that exports HTTPS_PROXY, or a shell
that already has Tor configured for monerod / curl, the wss://
dials to relay.damus.io / relay.nostr.band / nostr.wine (or
whatever the user picked) get routed through 127.0.0.1:9050.
When nothing is listening there — which is the typical case on
a workstation without an active Tor — every dial fails with
'dial tcp 127.0.0.1:9050: connect: connection refused', which
bubbles up as 'failed to connect to port 9050' in the TUI and
gives the user no actionable signal.

Fix it two ways:

  - nip46.ConnectRelays now refuses to dial at all when any
    *_PROXY env var is set, returning a clear error that names
    the offending env var and the proxy host:port. The user can
    either unset it (or run hoot outside torsocks). This matches
    what every other Nostr TUI does — nak, lume, etc. — none
    of them proxy wss:// connections by default.

  - Up-front validation of every configured relay URL (rejecting
    blanks and non-ws:// / non-wss:// schemes) so a misconfigured
    relays.txt surfaces a clear error instead of a confusing
    websocket one.

Anchored against master after PR #69 / #70 / #72 restructured
the NIP-46 dial path (RelayURL string -> RelayURLs []string,
WaitForConnection -> ConnectRelays + CheckConnection). Tests
updated accordingly: ConnectRelays is the new dial entry point,
and validateRelayURLs is exercised with single-bad-URL and
mixed-good-and-bad inputs.

Tests in nip46/nip46_test.go pin:
  - the proxy env var detection order (HTTPS_PROXY beats
    HTTP_PROXY beats ALL_PROXY; socks fallbacks come last)
  - that malformed or empty proxy values fall through to the
    next valid var (matching Go's stdlib httpproxy behaviour)
  - that ConnectRelays surfaces the env var name and port on
    the user-visible error path (this is the bug's regression
    guard)
  - that blank and non-wss relay URLs are rejected up front
    instead of producing a confusing websocket error later
  - that Session.Close is idempotent when no dial was attempted
@oth-body
oth-body force-pushed the fix/nip46-signin-proxy branch from 1e11022 to 97585df Compare September 15, 2026 16:38
@oth-body oth-body changed the title fix(nip46): refuse to dial relay through inherited *_PROXY env vars fix(nip46): refuse to dial relays through inherited *_PROXY env vars Sep 15, 2026
@oth-body

Copy link
Copy Markdown
Owner Author

Rebased onto master (was 1e11022, now 97585df) after PRs #66–#72 restructured NIP-46 (single RelayURL → []RelayURLs; WaitForConnection → ConnectRelays + CheckConnection). Diff stats: +89 in nip46.go, +231 in nip46_test.go. All 11 new tests + 4 existing master tests pass. go vet ./... and go build ./... clean.

@oth-body
oth-body merged commit f88b500 into master Sep 15, 2026
7 checks passed
@oth-body
oth-body deleted the fix/nip46-signin-proxy branch September 15, 2026 16:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant