Drop-in client + zero-config server for Tor hidden services
Project description
🧅 tornion
Tor hidden service toolkit for Python — drop-in client + zero-config server.
Consume any .onion API, or publish your own — in 3 lines of Python.
from tornion import client, server
# Consume a .onion API
r = client.get("http://xxxxxxxxxxxxxx.onion/ping")
# Publish your own
from fastapi import FastAPI
app = FastAPI()
server.serve(app)
That's it. No Docker, no torrc, no manual tor install — tornion handles
everything: discovers or downloads the tor binary, spawns it, manages the
SOCKS proxy or the hidden service, and tears it down on exit.
✨ Features
- 🔌 Drop-in
requests—client.Sessionis a realrequests.Sessionsubclass - ⚡ Async too —
client.AsyncSessionis a realhttpx.AsyncClientsubclass (opt-in) - 🚀 Zero-config server —
server.serve(app)takes any ASGI or WSGI app - 🧠 Smart tor reuse — auto-detects an already-running tor on
:9050/:9150 - 📦 Auto-install tor — downloads the official Tor Expert Bundle on first use
- 🔄 Secure auto-update — opt-in: always run the latest tor, PGP-verified against the Tor Project's signing key
- 🎯 Framework-agnostic — FastAPI, Flask, Starlette, Django, Quart, Litestar…
- 🔑 Persistent
.onion— the address stays stable across restarts - 🪶 Lightweight client — server features are opt-in via
pip install tornion[server]
📦 Installation
# Client only
pip install tornion
# With the async client (httpx-based)
pip install tornion[async]
# With server features
pip install tornion[server]
# With secure tor auto-update (PGP-verified latest)
pip install tornion[autoupdate]
🚀 Quick start
Client — call a .onion API
from tornion import client
r = client.get("http://xxx.onion/ping")
print(r.json())
# Reusable session for multiple calls
with client.Session() as s:
s.post("http://xxx.onion/items", json={"name": "foo"})
s.get("http://xxx.onion/items/1")
Server — publish your app on a .onion
from fastapi import FastAPI
from tornion import server
app = FastAPI()
@app.get("/")
def root():
return {"hello": "from .onion"}
server.serve(app)
When you run this, tornion prints the .onion URL and blocks until Ctrl+C.
Same code works with Flask, Starlette, Django, etc. — auto-detected.
Hybrid — server that also makes outbound calls
from fastapi import FastAPI
from tornion import client, server
app = FastAPI()
@app.get("/relay")
def relay(target: str):
r = client.get(target, timeout=30)
return {"upstream": r.status_code, "body": r.json()}
server.serve(app)
🛠️ CLI
tornion install-tor # pre-download the tor binary
tornion update # update tor to the latest PGP-verified version
tornion serve myapp:app # uvicorn-style: run an app on a .onion
tornion get http://xxx.onion/ # one-shot HTTP request
tornion info # diagnostic: where's tor, what's installed
📖 Configuration & CLI reference →
🔍 How does it actually work?
tornion orchestrates a real tor binary as a subprocess. Python doesn't
implement Tor — it uses the C reference implementation (tor) under the
hood, talks to it via SOCKS5 (client side) or the hidden-service API
(server side), and shuts it down on exit.
📚 Documentation
| Topic | Doc |
|---|---|
Calling .onion APIs from Python |
docs/client.md |
| Publishing your app on a hidden service | docs/server.md |
| Env vars, CLI, paths, diagnostic | docs/configuration.md |
| Architecture, design choices, pitfalls | docs/internals.md |
| Examples (client / server / hybrid) | examples/ |
| Release history & versioning policy | CHANGELOG.md |
🧪 Try it
git clone https://github.com/LouisCourrian/tornion
cd tornion
pip install -e ".[dev]"
pytest
python examples/server_fastapi.py
✅ Status
tornion is stable as of 1.0.0. The public API of tornion,
tornion.client, and tornion.server follows
Semantic Versioning; see
CHANGELOG.md for the full versioning policy and release
history. Pin to a major version (tornion>=1.0,<2.0) and you're good.
📄 License
MIT — see LICENSE.
tornion is an independent project, not affiliated with the Tor Project.
The bundled tor binary comes from torproject.org
under 3-clause BSD. The package embeds the public Tor Browser build signing
key (tornion/assets/tor-signing-key.asc, fingerprint
EF6E286D…93298290) solely to verify auto-update downloads.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file tornion-1.4.0.tar.gz.
File metadata
- Download URL: tornion-1.4.0.tar.gz
- Upload date:
- Size: 86.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
319bd3faaa0ccf2d1bfd18cbfb7949d57d8f109a5e5624ec0a739932325b8009
|
|
| MD5 |
cd6e31c2dc285202f7ca7c7305173bab
|
|
| BLAKE2b-256 |
d786653f23e8c5b6a9cddd1b189b834910259b162029dc0fc3556c1094562066
|
Provenance
The following attestation bundles were made for tornion-1.4.0.tar.gz:
Publisher:
release.yml on LouisCourrian/tornion
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
tornion-1.4.0.tar.gz -
Subject digest:
319bd3faaa0ccf2d1bfd18cbfb7949d57d8f109a5e5624ec0a739932325b8009 - Sigstore transparency entry: 1781189065
- Sigstore integration time:
-
Permalink:
LouisCourrian/tornion@a1c03f6c9a369008946b3b0aed288650aa8de234 -
Branch / Tag:
refs/tags/v1.4.0 - Owner: https://github.com/LouisCourrian
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@a1c03f6c9a369008946b3b0aed288650aa8de234 -
Trigger Event:
push
-
Statement type:
File details
Details for the file tornion-1.4.0-py3-none-any.whl.
File metadata
- Download URL: tornion-1.4.0-py3-none-any.whl
- Upload date:
- Size: 54.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5f153614a5d991d44c2654396157734f1a58626571fda84df62ef8a42163fcb0
|
|
| MD5 |
e520a3c9bc0781f2c841d61df9f4e506
|
|
| BLAKE2b-256 |
8f01cc44d553b6e341f087a40676d460798df96aa6387f833311671788ed8d46
|
Provenance
The following attestation bundles were made for tornion-1.4.0-py3-none-any.whl:
Publisher:
release.yml on LouisCourrian/tornion
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
tornion-1.4.0-py3-none-any.whl -
Subject digest:
5f153614a5d991d44c2654396157734f1a58626571fda84df62ef8a42163fcb0 - Sigstore transparency entry: 1781189230
- Sigstore integration time:
-
Permalink:
LouisCourrian/tornion@a1c03f6c9a369008946b3b0aed288650aa8de234 -
Branch / Tag:
refs/tags/v1.4.0 - Owner: https://github.com/LouisCourrian
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@a1c03f6c9a369008946b3b0aed288650aa8de234 -
Trigger Event:
push
-
Statement type: