Files
amethyst/docs/manual-testing-tor.md
nrobi144 11d0a657ac fix(tor): close 8 network traffic leaks — fail-closed when Tor expected
SECURITY: With Tor ON, all HTTP traffic now routes through SOCKS proxy.
Previously only relay WebSocket connections used Tor; 8 other egress
paths created bare OkHttpClient() instances bypassing Tor entirely.

Fail-closed behavior:
- When Tor expected but bootstrapping → dead SOCKS proxy on port 1
  (requests fail instead of leaking IP)
- lateinit var crashes on misconfiguration (loud failure vs silent leak)

Leak sites fixed (all use DesktopHttpClient.currentClient()):
- AnimatedGifImage: GIF fetch
- SaveMediaAction: media downloads
- EncryptedMediaService: NIP-17 DM media
- ServerHealthCheck: Blossom server probes
- NoteActions: zap/lightning LNURL resolution
- DesktopBlossomClient: media uploads
- AccountManager: NIP-46 bunker relay connections
- Coil image loader: TODO (OkHttpNetworkFetcher import issue)

Also:
- Relay reconnect on Tor Active (prevents stale clearnet connections)
- isTorExpected() for fail-closed logic
- Tests updated for new torTypeProvider parameter

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 07:25:45 +03:00

4.2 KiB

Manual Testing: Desktop Tor Support

Prerequisites

All tools are available natively on macOS:

  • tcpdump — DNS leak monitoring
  • curl — Tor routing verification
  • lsof — find SOCKS port

Optional:

brew install tor    # For external Tor mode testing

Step 1: Launch & Find SOCKS Port

cd AmethystMultiplatform-tor-support
./gradlew :desktopApp:run > /tmp/amethyst-tor.log 2>&1 &

# Wait ~30s for Tor bootstrap, then find the SOCKS port:
lsof -i -P -n | grep java | grep LISTEN
# Look for a high port (e.g. 35607) — that's the kmp-tor SOCKS port

Step 2: Verify Tor Routing

Replace PORT with the actual SOCKS port from Step 1.

# Your real IP
curl -s https://icanhazip.com
# → e.g. 203.0.113.42

# IP through Tor
curl -s --socks5-hostname 127.0.0.1:PORT https://icanhazip.com
# → e.g. 185.220.101.xx (different from above)

# Confirm Tor Project says it's Tor
curl -s --socks5-hostname 127.0.0.1:PORT https://check.torproject.org/api/ip
# → {"IsTor":true,"IP":"185.220.101.xx"}

Step 3: Verify No DNS Leaks

# In a separate terminal:
sudo tcpdump -i any port 53 -n -l 2>/dev/null | grep --line-buffered -v "mdns"

# In the app: scroll feed, load profiles, open threads
# tcpdump should show NO relay hostname DNS queries from Java

Checklist

UI — Default State

  • App launches with shield icon visible in sidebar (bottom)
  • Shield starts gray/yellow, turns green after ~10-30s (Tor bootstrap)
  • Hover shield → tooltip shows "Tor: Connected"
  • Click shield → navigates to Settings
  • Settings → Tor section shows Internal mode selected
  • Click "Advanced..." → Full Privacy preset selected, all toggles ON

UI — Mode Switching

  • Select "Off" → shield turns gray, relays reconnect directly
  • Select "Internal" → shield yellow → green, relays reconnect via Tor
  • Select "External" → SOCKS port field appears
  • Enter port 9050 (if system Tor running) → shield turns green

UI — Advanced Dialog

  • Preset radio buttons: Only When Needed / Default / Small Payloads / Full Privacy / Custom
  • Selecting "Only When Needed" → only .onion toggle ON
  • Selecting "Full Privacy" → all toggles ON
  • Toggling individual item → preset auto-switches to "Custom"
  • Scroll dialog content → Cancel/Save buttons stay at bottom (sticky)
  • Cancel → no changes saved
  • Save → settings applied and persisted

UI — .onion Relay Badge

  • Add a .onion relay URL → badge shows "via Tor" (green) when Tor ON
  • Switch Tor OFF → badge shows "Requires Tor" (red)

UI — Persistence

  • Change to "Default" preset, close app
  • Relaunch → shows Internal + Default preset, Tor auto-connects

Network — Tor Routing Verified

  • curl -s https://icanhazip.com → real IP: _______________
  • curl -s --socks5-hostname 127.0.0.1:PORT https://icanhazip.com → Tor IP: _______________
  • IPs are different: ___
  • curl --socks5-hostname 127.0.0.1:PORT https://check.torproject.org/api/ip{"IsTor":true}: ___

Network — No DNS Leaks

  • sudo tcpdump -i any port 53 -n shows no relay hostname queries while Tor active

Build — No Regressions

  • ./gradlew :commons:jvmTest — all pass
  • ./gradlew :desktopApp:test — all pass (1 pre-existing failure in DesktopCachePipelineTest)
  • ./gradlew :amethyst:testPlayDebugUnitTest — all pass
  • ./gradlew :amethyst:compilePlayDebugKotlin — Android compiles clean
  • ./gradlew spotlessApply — formatting clean

Proof of Work Evidence

Paste terminal output of:

# 1. SOCKS port discovered
lsof -i -P -n | grep java | grep LISTEN

# 2. Tor routing confirmed
curl -s --socks5-hostname 127.0.0.1:PORT https://check.torproject.org/api/ip

# 3. IP mismatch confirmed
echo "Real: $(curl -s https://icanhazip.com) | Tor: $(curl -s --socks5-hostname 127.0.0.1:PORT https://icanhazip.com)"

# 4. Tests passing
./gradlew :commons:jvmTest :desktopApp:test :amethyst:testPlayDebugUnitTest 2>&1 | tail -5

Screenshot of:

  • Shield icon green in sidebar
  • Tor settings section showing Internal + Full Privacy
  • Advanced dialog with all toggles ON