Skip to content

feat: tor managed services support#1819

Open
roshii wants to merge 1 commit intoJoinMarket-Org:masterfrom
roshii:fix/hidden-service-pow
Open

feat: tor managed services support#1819
roshii wants to merge 1 commit intoJoinMarket-Org:masterfrom
roshii:fix/hidden-service-pow

Conversation

@roshii
Copy link
Copy Markdown
Contributor

@roshii roshii commented Oct 30, 2025

This PR introduces support for Tor-managed hidden services, providing a more secure and simplified way to run persistent onion services without requiring Tor control port access.

Changes

Core Implementation

Modified JMHiddenService class in src/jmbase/twisted_utils.py:

  • Added detection for tor-managed: prefixed hidden service directories
  • Implemented start_tor_managed_onion() method that polls for hostname file creation
  • Added create_filesystem_onion_ep() for better code organization
  • No Tor control port connection needed in managed mode

Daemon Integration

Updated OnionMessageChannel in src/jmdaemon/onionmc.py:

  • Added _start_listener() method for cleaner listener setup
  • Modified genesis node handling to work with managed services
  • Improved error handling and callback management

Testing

Added comprehensive test coverage:

  • test/jmbase/test_twisted_utils.py: Tests for hidden service mode detection and ephemeral service creation
  • test/jmdaemon/test_onionmc.py: Tests for listener setup and different host/port configurations

Benefits

  • Optionally eliminates the need for Tor control port access, reducing attack surface
  • Hidden services ca be configured entirely in torrc, no JoinMarket-specific setup required
  • Existing ephemeral and persistent modes continue to work unchanged
  • HiddenServicePOWDefense ready 🚀

Usage

To use Tor-managed hidden services, configure your joinmarket.cfg:

[MESSAGING:onion]
hidden_service_dir = tor-managed:/path/to/tor/hidden/service/dir

The corresponding torrc entry would be:

HiddenServiceDir /path/to/tor/hidden/service/dir
HiddenServicePort 80 127.0.0.1:8080

The code automatically detects the tor-managed: prefix and switches to managed mode, polling for the hostname file that Tor creates.

@roshii roshii force-pushed the fix/hidden-service-pow branch 3 times, most recently from 2e34935 to 7ed5cff Compare November 11, 2025 16:23
@roshii roshii marked this pull request as ready for review November 11, 2025 16:25
@roshii roshii marked this pull request as draft November 11, 2025 16:58
@roshii roshii force-pushed the fix/hidden-service-pow branch 4 times, most recently from 2981e7a to d289cac Compare November 15, 2025 16:16
@roshii roshii force-pushed the fix/hidden-service-pow branch from d289cac to 6d97bec Compare November 20, 2025 20:41
@roshii roshii marked this pull request as ready for review November 20, 2025 20:43
@m0wer
Copy link
Copy Markdown

m0wer commented Feb 10, 2026

The default values of PoW are way too high for jm-ref to handle. You probably need to play a bit with the values: https://onionservices.torproject.org/technology/security/pow/#configuring-an-onion-service-with-the-pow-protection.

For jm-ng: HiddenServicePoWQueueRate=25 HiddenServicePoWQueueBurst=250. Which are still way too high for jm-ref IMO, a target requests per second of 25 is a lot for the normal case of a maker and probably can still cause OOM and log flooding. Going down to 2 rps is probably more reasonable at the risk of timing out for some potential takers during the attack.

Apart from that, Tor PoW defense is futile if a single connection and be reused with randomized nicks, or even with the same nick since there is no rate limiting. The attacker would take more time to connect but then the attack is the same. And since it looks like the attacker just has a handful of nicks for the current attack, would still happen the same way. Because it's not that there would ever be the situation of forcing a new Tor connection unless we kick peers/connections when the rate limiting is reached.

Here is what I wrote in the TG chat:

The change is only about adding some parameters to the ephemeral hidden service creation, not to any JM logic. This enables some PoW requirements on the Tor rendezvous point level, before the connection is established. Clients from 0.4.8.0 onwards support doing the PoW automatically and transparently.

The PoW requirements is automatically adjusted to meet certain RPS and goes off whenever there's no attack. As with any DoS protection measure, the challenge is to not block legit connections. In this case, legit users would have very high connections times but once the connection is established or the attack goes away. Would have to check that this does not become a problem with the current connection timeout.

Another challenge is that the possibility of configuring PoW for ephemeral hidden services only is available in the alpha branch. Good news is that it is soon to be released and there is a release candidate already: https://forum.torproject.org/t/release-candidate-and-stable-release-0-4-8-22-and-0-4-9-4-rc/21160

But now that I think about it, this would only be half of the solution. Originally the attacker was doing the attacks through directories. Since that had some natural throttling through saturating the directories themselves, probably switched to direct connections. In JM-NG there is rate limiting per nick through directories and per connection in direct connections (to prevent nick randomization in an already established connection).

So the changes become much larger than I thought. Otherwise the attacker could just go back to attacking through directories or direct connections with nick randomization.

What's clear from this, is that the directories also need protection (and can easily add PoW requirements in current Tor stable versions since they have a fixed hidden service onion address). And should implement per connection rate limiting. And makers should do the same but need a newer Tor version for that.

All of these have been implemented and are working in jm-ng. maybe it's just a matter of enough directories and makers using it until the attacker gives up (since trying to attack all makers with enough jm-ng ones will already slow the attacker significantly until it moves to smarter approaches).

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.

2 participants