hypertor 0.2.0

Tor HTTP client and onion service library with Python bindings
Documentation

hypertor πŸ§…

The Tor network library for Rust and Python β€” consume AND host onion services with the simplicity of reqwest and axum.

CI Crates.io Documentation License: MIT MSRV


Why hypertor?

Most Tor libraries only do one thing: make requests. hypertor does both:

Component Purpose Similar To
TorClient Make HTTP requests over Tor reqwest, httpx
OnionService Host .onion services axum, FastAPI

Production-Ready Security β€” All features wired to real arti APIs:

Feature Purpose arti API
πŸ›‘οΈ Vanguards Guard discovery protection VanguardConfigBuilder::mode()
⚑ Proof-of-Work DoS protection (Equi-X) OnionServiceConfigBuilder::enable_pow()
🚦 Rate Limiting Intro point flooding protection rate_limit_at_intro()
πŸ” Client Auth Restricted service discovery RestrictedDiscoveryConfigBuilder
πŸŒ‰ Bridges Censorship circumvention TorClientConfigBuilder::bridges()
πŸ”Œ Pluggable Transports Traffic obfuscation TransportConfigBuilder

Quick Start

Rust β€” Client

use hypertor::{TorClient, Result};

#[tokio::main]
async fn main() -> Result<()> {
    // Create client (connects to Tor network)
    let client = TorClient::new().await?;
    
    // Make requests just like reqwest
    let resp = client
        .get("http://duckduckgogg42xjoc72x3sjasowoarfbgcmvfimaftt6twagswzczad.onion")?
        .send()
        .await?;
    
    println!("Tor Check: {}", resp.text()?);
    Ok(())
}

Rust β€” Server

use hypertor::{OnionApp, ServeResponse};

#[tokio::main]
async fn main() -> hypertor::Result<()> {
    let app = OnionApp::new()
        .get("/", || async { ServeResponse::text("Hello from .onion!") })
        .get("/health", || async { 
            ServeResponse::text(r#"{"status":"ok"}"#)
                .with_header("Content-Type", "application/json")
        });
    
    // Start the hidden service
    let addr = app.run().await?;
    println!("πŸ§… Live at: {}", addr);
    Ok(())
}

Python β€” Client

import asyncio
from hypertor import AsyncClient

async def main():
    async with AsyncClient(timeout=60) as client:
        # Check our Tor IP
        resp = await client.get("https://check.torproject.org/api/ip")
        print(f"Tor IP: {resp.json().get('IP')}")
        
        # Access an onion service
        resp = await client.get(
            "http://duckduckgogg42xjoc72x3sjasowoarfbgcmvfimaftt6twagswzczad.onion"
        )
        print(f"Status: {resp.status_code}")

asyncio.run(main())

Python β€” Server (FastAPI-style)

from hypertor import OnionApp

app = OnionApp()

@app.get("/")
async def home():
    return "Welcome to my .onion service!"

@app.post("/api/echo")
async def echo(request):
    data = await request.json()
    return {"received": data}

@app.get("/users/{user_id}")
async def get_user(user_id: int):
    return {"id": user_id, "name": "Alice"}

if __name__ == "__main__":
    app.run()  # πŸ§… Service live at: xyz...xyz.onion

Installation

Rust

[dependencies]
hypertor = "0.4"
tokio = { version = "1", features = ["full"] }

Python

pip install hypertor

TLS Backends

hypertor defaults to rustls for security reasons:

Backend Default Security Notes
rustls βœ… Best Consistent TLS fingerprint, pure Rust, memory-safe
native-tls Good Uses OS TLS stack, platform-specific fingerprints

Why rustls is the default

For anonymity-focused applications, TLS fingerprinting is a real threat. Different TLS libraries produce different fingerprints based on cipher suite ordering, extensions, and timing. Using native-tls means:

  • Linux: OpenSSL fingerprint
  • macOS: SecureTransport fingerprint (also has Tor stream compatibility issues)
  • Windows: SChannel fingerprint

This leaks your operating system to any observer. With rustls, you get the same fingerprint on all platforms, making it harder to distinguish users.

Additionally, rustls is:

  • Memory-safe: Pure Rust, no C library vulnerabilities
  • Auditable: Easier to verify for security
  • Isolated: Not affected by compromised system CA stores

If you need native-tls for specific compatibility:

[dependencies]
hypertor = { version = "0.4", default-features = false, features = ["client", "native-tls"] }

Features

πŸ”Œ TorClient β€” HTTP Client

use hypertor::{TorClient, IsolationLevel};
use std::time::Duration;

// Builder pattern for full control
let client = TorClient::builder()
    .timeout(Duration::from_secs(60))
    .max_connections(20)
    .isolation(IsolationLevel::PerRequest)  // Fresh circuit per request
    .follow_redirects(true)
    .build()
    .await?;

// POST with typed JSON
let resp = client.post("http://api.onion/users")?
    .json(&User { name: "Alice".into() })
    .send().await?;

// Query parameters
let resp = client.get("http://api.onion/search")?
    .query(&[("q", "rust"), ("page", "1")])
    .send().await?;

πŸ§… OnionApp β€” Hidden Service Framework

use hypertor::{OnionApp, OnionAppConfig, ServeRequest, ServeResponse, get};
use std::time::Duration;

let config = OnionAppConfig::new()
    .with_port(80)
    .with_timeout(Duration::from_secs(30))
    .with_key_path("/var/lib/myapp/keys");  // Persist .onion address

let app = OnionApp::with_config(config)
    .get("/", || async { ServeResponse::text("Home") })
    .post("/api/data", |req: ServeRequest| async move {
        let body = req.text().unwrap_or_default();
        ServeResponse::text(&format!(r#"{{"echo":{}}}"#, body))
    })
    .route("/health", get(|| async {
        ServeResponse::text(r#"{"status":"healthy"}"#)
            .with_header("Content-Type", "application/json")
    }));

let addr = app.run().await?;  // Returns "abc...xyz.onion"

πŸ” Security Configuration

hypertor provides security presets that configure real arti hardening features:

use hypertor::security::{SecurityLevel, ServiceSecurityConfig};
use hypertor::onion_service::OnionServiceConfig;

// Security presets for onion services
let standard = ServiceSecurityConfig::standard();   // Basic protection
let enhanced = ServiceSecurityConfig::enhanced();   // PoW + rate limiting
let maximum = ServiceSecurityConfig::maximum();     // Full hardening

// Or configure manually with fluent API
let config = OnionServiceConfig::new("my-service")
    .with_pow()                        // Proof-of-Work (Equi-X)
    .pow_queue_depth(16000)            // Queue depth
    .rate_limit_at_intro(10.0, 20)     // Rate: 10/s, burst: 20
    .max_streams_per_circuit(100)      // Stream limit
    .vanguards_full()                  // Full vanguards
    .num_intro_points(5);              // High availability

πŸŒ‰ Censorship Circumvention (China, Iran, Russia)

use hypertor::{TorClientBuilder, VanguardMode};

let client = TorClientBuilder::new()
    // Multiple bridges for redundancy
    .bridge("obfs4 192.0.2.1:443 FINGERPRINT cert=... iat-mode=0")
    .bridge("obfs4 192.0.2.2:443 FINGERPRINT cert=... iat-mode=0")
    // Pluggable transport binary
    .transport("obfs4", "/usr/bin/obfs4proxy")
    // Full vanguards for hostile networks
    .vanguards(VanguardMode::Full)
    .build()
    .await?;

πŸ”„ Stream Isolation

Keep different activities on separate Tor circuits:

use hypertor::{TorClient, IsolationToken};

let client = TorClient::new().await?;

// Create isolated sessions
let banking = IsolationToken::new();
let browsing = IsolationToken::new();

// These use the same circuit (banking identity)
client.get("http://bank.onion")?.isolation(banking.clone()).send().await?;
client.get("http://bank.onion/transfer")?.isolation(banking.clone()).send().await?;

// This uses a different circuit (browsing identity)
client.get("http://news.onion")?.isolation(browsing.clone()).send().await?;

⚑ Resilience Features

use hypertor::{
    CircuitBreaker, BreakerConfig,
    AdaptiveRetry, AdaptiveRetryConfig,
    TokenBucket,
};

// Circuit breaker - fail fast when service is down
let breaker = CircuitBreaker::new(BreakerConfig {
    failure_threshold: 5,
    reset_timeout: Duration::from_secs(30),
    ..Default::default()
});

// Adaptive retry - learns optimal behavior
let retry = AdaptiveRetry::new(AdaptiveRetryConfig {
    max_attempts: 3,
    min_delay: Duration::from_millis(100),
    max_delay: Duration::from_secs(10),
    ..Default::default()
});

// Rate limiting
let limiter = TokenBucket::new(100, 100.0);  // 100 req/sec

πŸ“Š Observability

use hypertor::{TorMetrics, export_metrics};

let metrics = TorMetrics::new();

// Record operations
metrics.record_request("GET", 200, 1.5, 100, 5000);
metrics.record_circuit_build(true, 2.0);

// Export Prometheus format
let prometheus_text = metrics.export();
// # HELP hypertor_http_requests_total Total HTTP requests
// # TYPE hypertor_http_requests_total counter
// hypertor_http_requests_total{method="GET",status="200"} 1

πŸŒ‰ Bridge Support (Censorship Circumvention)

use hypertor::{TorClientBuilder, VanguardMode};

// Configure bridges and transports via builder
let client = TorClientBuilder::new()
    .bridge("obfs4 192.0.2.1:443 FINGERPRINT cert=CERT iat-mode=0")
    .transport("obfs4", "/usr/bin/obfs4proxy")
    .vanguards(VanguardMode::Full)
    .build()
    .await?;

οΏ½ SOCKS5 Proxy

Run a local SOCKS5 proxy to route ANY application through Tor:

use hypertor::{Socks5Proxy, ProxyConfig};
use std::net::{IpAddr, Ipv4Addr, SocketAddr};

// Start SOCKS5 proxy on localhost:9050
let proxy = Socks5Proxy::with_defaults();
proxy.run().await?;  // Runs on 127.0.0.1:9050

Then use with any SOCKS5-compatible tool:

# curl
curl --socks5-hostname 127.0.0.1:9050 https://check.torproject.org/api/ip

# Python requests
proxies = {'http': 'socks5h://127.0.0.1:9050', 'https': 'socks5h://127.0.0.1:9050'}
requests.get('https://example.com', proxies=proxies)

# wget, git, ssh, browsers...

οΏ½πŸ”Œ WebSocket over Tor

use hypertor::websocket::TorWebSocket;

// Connect to WebSocket over Tor
let mut ws = TorWebSocket::connect("ws://chat.onion/ws").await?;

// Send and receive messages
ws.send_text("Hello, Tor!").await?;
let msg = ws.recv().await?;

πŸ“‘ HTTP/2 Support

use hypertor::http2::{Http2Connection, Http2Config};

// HTTP/2 multiplexing over Tor
let mut conn = Http2Connection::client(Http2Config::default());
let stream_id = conn.create_stream()?;
conn.send_headers(stream_id, headers, false)?;
conn.send_data(stream_id, body, true)?;

Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                        YOUR APPLICATION                          β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  TorClient          β”‚  OnionService       β”‚  OnionApp (serve)   β”‚
β”‚  (HTTP over Tor)    β”‚  (host .onion)      β”‚  (axum-like API)    β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                         hypertor core                            β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚
β”‚  β”‚ Circuits β”‚ β”‚ Security β”‚ β”‚Resilienceβ”‚ β”‚ Observability    β”‚   β”‚
β”‚  β”‚ Pooling  β”‚ β”‚ Vanguardsβ”‚ β”‚ Retry    β”‚ β”‚ Metrics/Tracing  β”‚   β”‚
β”‚  β”‚ Isolationβ”‚ β”‚ PoW/Auth β”‚ β”‚ Breaker  β”‚ β”‚ Health Checks    β”‚   β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                     arti-client 0.38 (Tor)                       β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Module Overview

Category Modules
Core client, serve, onion_service, config, error, body
Security security (presets, vanguards, PoW, rate limiting)
Networking pool, isolation, circuit, proxy (SOCKS5), dns, doh, tls
Resilience retry, breaker, rotation, adaptive, backpressure
Performance cache, dedup, ratelimit, queue, prewarm, batch
Protocols http2, websocket
Observability observability, prometheus, tracing, health, metrics

Examples

Basic Client Usage

cargo run --example basic_usage

Security Features Demo

cargo run --example security_features

SOCKS5 Proxy

cargo run --example socks_proxy
# Then: curl --socks5-hostname 127.0.0.1:9050 https://check.torproject.org/api/ip

Metrics

Metric Value
Source Files 51
Lines of Code ~26,900
Unit Tests 207
Integration Tests 26
Security Tests 58
Doc Tests 1
Total Tests 292
arti Version 0.38
Rust Edition 2024
MSRV 1.85

Run all tests:

cargo test                     # All 292 tests
cargo test --test security     # Security tests (58)

Security Considerations

What hypertor Protects Against

Threat Protection Implementation
Guard discovery Vanguards (Lite/Full) VanguardConfigBuilder::mode()
DoS attacks Proof-of-Work (Equi-X) OnionServiceConfigBuilder::enable_pow()
Intro flooding Rate limiting rate_limit_at_intro()
Stream flooding Stream limits max_concurrent_streams_per_circuit()
IP exposure Bridges + transports TorClientConfigBuilder::bridges()
Unauthorized access Client authorization RestrictedDiscoveryConfigBuilder
Secret leaks Zeroize on drop SecretKey with ZeroizeOnDrop

What hypertor Does NOT Protect Against

  • ❌ Application-level data leaks (your code)
  • ❌ Timing attacks from your application logic
  • ❌ Malware on your system
  • ❌ Compromised exit nodes (for clearnet access)

Development

# Run tests
cargo test --lib

# Run clippy
cargo clippy --lib -- -D warnings

# Build Python wheel
maturin develop --features python

# Run example
cargo run --example basic_usage

# Serve docs locally
just docs

Documentation


⚠️ Disclaimer

This software is provided for educational and research purposes only.

  • No Anonymity Guarantee: While hypertor leverages the Tor network via arti, no software can guarantee complete anonymity. Your operational security practices, threat model, and usage patterns significantly impact your privacy.
  • No Warranty: This software is provided "as is" without warranty of any kind. The authors are not responsible for any damages or legal consequences arising from its use.
  • Legal Compliance: Users are solely responsible for ensuring their use of this software complies with all applicable laws and regulations in their jurisdiction.
  • Not Endorsed by Tor Project: This is an independent project and is not affiliated with, endorsed by, or sponsored by The Tor Project.
  • Security Considerations: Always review the Security Guide before deploying in production.

License

MIT License. See LICENSE for details.

Contributing

Contributions welcome! Please open issues and pull requests on GitHub.