Selenium Tor
A Selenium extension for Tor Browser.
# before_all
require 'selenium_tor'
= Selenium::WebDriver::Options.tor
.args << '-headless'
@driver = Selenium::WebDriver.for :tor, options:
When you are done don't forget to quit the driver. Failure to do so may leave orphaned tor processes.
# after_all
@driver&.quit
Once you have a driver instance:
@driver
# => instance_of Selenium::WebDriver::Tor::Driver
@driver.get 'https://check.torproject.org'
@driver.title
# => "Congratulations. This browser is configured to use Tor."
_why?
I can use Firefox with Selenium and set a SOCKS proxy to use the Tor network, so why the need?
The above approach will hide your IP, but there is a good chance your browser's unique or near-unique fingerprint may be logged by site owners. Subsequent visits could identify you. A primary aim of this project is to enable Selenium to leverage Tor Browser's unique anonymity characteristics - in particular its resistance to browser fingerprinting. The primary aim is to ensure Selenium Tor site visits leave an identical fingerprint to the thousands of regular Tor Browser users.
Installation
Install the gem and add to the application's Gemfile by executing:
$ bundle add selenium_tor
If bundler is not being used to manage dependencies, install the gem by executing:
$ gem install selenium_tor
Dependencies and configuration
Tor Browser. The latest latest stable ('release') version of Tor Browser is supported. The latest alpha release is also supported if it is newer than the latest stable release. Support for older versions is periodically dropped - see the CHANGELOG.
As with Firefox browser, geckodriver needs to be installed and in your PATH. Selenium Manager is disabled for security reasons, so you are responsible for ensuring the correct (latest) version of geckodriver is installed.
The gem needs to know the location of the Tor Browser Bundle (TBB). The Tor Browser download package archive must be extracted and the root TBB directory (named "tor-browser") placed somewhere on your system. By default it is assumed to be in the current user's HOME directory. An alternative location can be set via the env var TOR_BROWSER_ROOT_DIR - e.g. export TOR_BROWSER_ROOT_DIR=/home/<user>/Downloads. The Tor Browser binary location is automatically set by reference to this directory, so there is no need to do this:
= Selenium::WebDriver::Options.tor
.binary = '/some/path/to/tor_firefox_binary' # UNNECESSARY
# => '/some/path/to/tor_firefox_binary'
The location of the TBB is not expected to change during code execution.
Tor Selenium is tested on Linux only right now.
Usage
require 'selenium_tor'
# => false
Tor Browser is based on Firefox, so for usage please read the Selenium docs for Firefox browser.
If the tor network is inaccessible for any reason a Selenium::WebDriver::Error::TimeoutError will result.
A separate tor process is used for each driver. Failure to call Driver#quit after code execution may leave orphaned tor processes.
Tor options
In addition to the regular Firefox options, a :tor_opts key may be passed to an instance of Tor::Options with a hash of tor options. All valid tor client options are recognized - see man tor. Snake case key symbols may be used if these can be coerced to valid camel case key strings - e.g. :control_port becomes 'ControlPort'. Additionally, a :tor_opts timeout value may be set with the :timeout key. This overrides the default time allowed for the tor process to bootstrap (10 seconds).
Multiple driver instances
Running multiple tor processes requires that each uses different ports for SocksPort (and ControlPort, if used). From version 2.0 the SocksPort is auto selected, so no configuration is required. An example using the Parallel gem:
require 'parallel'
def driver
= Selenium::WebDriver::Options.tor # no needs to pass SocksPort in :tor_opts from version 2.0
.args << '-headless'
Selenium::WebDriver.for :tor, options:
end
@drivers = [driver, driver]
::Parallel.all?(@drivers, in_threads: @drivers.size) do |driver|
driver.get 'https://check.torproject.org'
end
@drivers.each(&:quit)
# => truthy
Each Driver instance uses a separate data directory for torrc and other files. Starting from version 1.7 cached files created upon first connection to the tor network are copied into subsequent data dirs. This substantially reduces the time subsequent Drivers take to connect to the tor network.
Tor Browser specific functionality
You can get ~~and set~~ the security level (shield icon in TB) with the Driver#security_level method. 4 is 'Standard', 2 is 'Safer', 1 is 'Safest'.
@driver.security_level
# => 4
With the 14.5.4 release the ability the change the security level within a session was removed. To start a browser with a non-default security level (which is 4) set the preference "browser.security_level.security_slider" value in Tor::Options - e.g: options.add_preference('browser.security_level.security_slider', 1).
You can get a new circuit for the current page ('New Tor circuit for this site' on TB's application menu):
@driver.new_circuit_for_site # new circuit for the current page domain
# => nil
Miscellaneous
You can get and set Driver preferences dynamically like this. Caveat emptor.
@driver.pref['browser.download.dir']
# => ''
@driver.pref['browser.download.dir'] = '/home/user/Downloads'
@driver.pref['browser.download.dir']
# => '/home/user/Downloads'
The Selenium::WebDriver::Tor namespace is used for Driver, Options, Profile and all tor-specific classes. Otherwise Selenium's Selenium::WebDriver::Firefox namespace is used.
Remote functionality is not tested, but may be if a suitable Tor Browser Docker container becomes available.
A number of constants are set during driver initialization based upon values found in the Tor Browser Bundle (TBB) root directory - see below. These include:
Tor = Selenium::WebDriver::Tor
Tor::TBB_DIR # path to the TBB root directory
Tor::TBB_BROWSER_DIR # path to the 'Browser' directory in the above
Tor::TBB_BINARY_PATH # path to the firefox binary
Tor::TBB_TOR_BINARY_PATH # path to the bundled tor binary
Tor::TBB_PROFILE_DIR # path to the default profile directory
Tor::TBB_EXTENSIONS_DIR # path to the 'extensions' directory in the above
Tor::TBB_VERSION # the version installed, e.g. "13.0.1", note: driver.capabilities.browser_version returns the Firefox version Tor Browser is based on
# => instance_of String
Patching libxul.so
A configuration option to patch the libxul.so packaged with Tor Browser is provided. This has the effect of preventing websites from fingerprinting Selenium-driven Tor Browser via a JS call to navigator.webdriver:
@driver_class = Selenium::WebDriver::Tor::Driver
@driver_class.libxul_patched? # is Tor Browser's libxul.so patched?
# => false
@driver_class.patch_libxul # patch libxul.so
@driver_class.libxul_patched?
# => true
@driver_class.unpatch_libxul # revert the patch and restore the libxul.so packaged with Tor Browser
# => 0
The patcher uses the Bsdiff gem.
Exceptions
Tor-specific webdriver functionality raises Tor::Error or a subclass. Tor::Error itself subclasses WebDriver::Error::WebDriverError
Known issues
Known issues are recorded here.
Testing
~~After checking out the repo, run bin/setup to install dependencies.~~
$ bundle exec rake
If you find driver instantiation failing with port bind failure error messages ( these include "Address already in use. Is Tor already running?") check you have no other tor processes running with conflicting ports. With timeout errors, you may also want to check the Tor network is actually up, it isn't always..
Development
You can run bin/console for an interactive prompt that will allow you to experiment.
To install this gem onto your local machine, run bundle exec rake install.
Contributing
Bug reports and pull requests are welcome on GitLab at https://gitlab.com/matzfan/selenium-tor. For a pull request please checkout a suitably named feature branch before making and submitting changes.
License
The gem is available as open source under the terms of the MIT License.