Skip to main content
Glama
README.md
<div align="center">

# 🧅 torproxy

[![GitHub Release](https://img.shields.io/github/v/release/hapheus/torproxy?style=flat-square&color=blue)](https://github.com/hapheus/torproxy/releases)
[![Docker Pulls](https://img.shields.io/docker/pulls/hapheus/torproxy?style=flat-square)](https://hub.docker.com/r/hapheus/torproxy)
[![Docker Image Size](https://img.shields.io/docker/image-size/hapheus/torproxy/latest?style=flat-square)](https://hub.docker.com/r/hapheus/torproxy)
[![GitHub Stars](https://img.shields.io/github/stars/hapheus/torproxy?style=flat-square)](https://github.com/hapheus/torproxy/stargazers)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=flat-square)](LICENSE)

[![PR Quality & Smoke Gate](https://github.com/hapheus/torproxy/actions/workflows/pr-gate.yml/badge.svg)](https://github.com/hapheus/torproxy/actions/workflows/pr-gate.yml)
[![Daily Live Image Verification](https://github.com/hapheus/torproxy/actions/workflows/daily-image-verification.yml/badge.svg)](https://github.com/hapheus/torproxy/actions/workflows/daily-image-verification.yml)
[![OSV-Scanner](https://github.com/hapheus/torproxy/actions/workflows/osv-scanner.yml/badge.svg)](https://github.com/hapheus/torproxy/actions/workflows/osv-scanner.yml)
[![CodeQL Analysis](https://github.com/hapheus/torproxy/actions/workflows/codeql.yml/badge.svg)](https://github.com/hapheus/torproxy/actions/workflows/codeql.yml)
[![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/hapheus/torproxy/badge)](https://scorecard.dev/viewer/?uri=github.com/hapheus/torproxy)
[![OpenSSF Best Practices](https://img.shields.io/cii/percentage/15828?style=flat-square&logo=linuxfoundation&label=OpenSSF%20Best%20Practices)](https://www.bestpractices.dev/projects/15828)

[Quick Start](#quick-start) · [Transparent Gateway](#transparent-tor-gateway) · [Model Context Protocol (MCP)](#-model-context-protocol-mcp) · [Use Cases](#-when-to-use-torproxy) · [Architecture](#architecture) · [REST API](docs/api.md) · [Configuration](docs/configuration.md) · [Examples](examples/) · [Contributing](CONTRIBUTING.md)

</div>

**A production-grade, leak-proof Tor HTTP and SOCKS5 proxy packaged for Docker.**
Route entire container stacks without touching application code, equip AI coding assistants (Claude Desktop, Cursor) with native Tor tools via MCP, and rotate circuits dynamically with a single REST call.

- 🛡️ **Zero Clearnet Leaks:** Fail-closed netfilter kill-switch & Privoxy fail-safe — never leaks your real IP on connection drops.
- 🚀 **Transparent Tor Gateway:** Tunnel any container's TCP & DNS via `network_mode: "service:torproxy"` — zero app config changes.
- 🤖 **Native AI Agent MCP Server:** Built-in Model Context Protocol server (SSE & Stdio) for Claude Desktop, Cursor, and Antigravity.
- 🔄 **Dynamic Circuit & Country Control:** Switch identities (`SIGNAL NEWNYM`) and select exit node countries on the fly via REST.
- 🔒 **Defense in Depth:** Runs strictly as an unprivileged user (`torproxy`), with ephemeral cookie authentication for Tor control.

---

## Quick Start

Get up and running with a verified Tor circuit in seconds:

### 1. Launch torproxy

Clone and run via Docker Compose:
```sh
docker compose up -d
```
*Or launch directly via Docker Run without cloning:*
```sh
docker run -d --name torproxy --restart unless-stopped \
  -p 127.0.0.1:8118:8118 -p 127.0.0.1:9050:9050 -p 127.0.0.1:8080:8080 \
  hapheus/torproxy:latest
```

### 2. Verify Tor connectivity
```sh
# Verify HTTP proxy (returns {"IsTor": true, "IP": "..."})
curl -x http://127.0.0.1:8118 https://check.torproject.org/api/ip

# Verify SOCKS5 proxy with remote DNS resolution
curl --socks5-hostname 127.0.0.1:9050 https://check.torproject.org/api/ip

# Check live connection status via REST API
curl -s http://127.0.0.1:8080/status | jq .
```

> [!IMPORTANT]
> **Leak-Proof Guarantee (Fail-Closed):** If Tor disconnects or crashes, Privoxy immediately returns `503 Service Unavailable` and the Netfilter kill switch drops outgoing packets. Your real IP will **never** silently leak to clearnet under network failures.

> **Security Note:** In `docker-compose.yml`, all ports bind strictly to `127.0.0.1` by default. Neither the proxy listeners nor the management API require authentication; do not expose them to untrusted networks.

<details>
<summary><b>⚡ Advanced: Dynamic Identity Rotation & Country Routing (click to expand)</b></summary>

```sh
# Request a fresh Tor identity without restarting the container:
curl -X POST http://127.0.0.1:8080/reconnect

# Switch exit country dynamically on reconnect:
curl -X POST "http://127.0.0.1:8080/reconnect?country=DE"

# Switch with strict exit country enforcement via JSON payload:
curl -X POST http://127.0.0.1:8080/reconnect \
  -H "Content-Type: application/json" \
  -d '{"country": "AT", "strict_nodes": true}'

# Or launch with a default country at startup:
TORPROXY_COUNTRY=DE docker compose up -d
```
</details>

---

## 🎯 When to Use torproxy

Understanding where `torproxy` excels helps you pick the right tool for the job:

- ✅ **Ideal For:**
  - **Automated Web Scraping & Data Extraction:** Distribute requests and bypass IP rate limits with dynamic circuit rotation.
  - **AI Agent Automation (MCP):** Allow Claude Desktop, Cursor, and agent frameworks to fetch web and `.onion` pages safely.
  - **Zero-Friction Container Privacy:** Tunnel third-party containers (crawlers, search engines, microservices) with zero code modifications.
  - **Geolocation Verification:** Test localized web experiences and CDN geo-routing across different exit countries (`TORPROXY_COUNTRY=DE,AT,US`).
  - **Self-Hosted Privacy Stacks:** Route upstream search queries (e.g. SearXNG) privately.
- ⚠️ **Not Designed For:**
  - **High-Bandwidth / Latency-Critical Traffic:** Tor is not designed for 4K video streaming, voice calls, or real-time gaming.
  - **Bypassing Dedicated Anti-Bot Protection:** Major CDNs (e.g., Cloudflare, Akamai) often challenge or block known public Tor exit nodes.

---

## Architecture

```text
Host Client / Dependent Containers
        │
        ├── HTTP Requests ────────► Privoxy (:8118)
        │                             │ (forward-socks5t)
        ├── SOCKS5 Requests ──────────┼───────────────► Tor Daemon (:9050)
        │                             │                   │
        └── REST Control API ─────► API Server (:8080)   │ (Cookie Auth)
                                      │                   │
                                      └─ ControlPort ─────┘ (:9051 loopback)
                                                          │
                                                          ▼
                                                  Tor Onion Network
                                                          ▼
                                                    Target Website
```

---

<a id="transparent-tor-gateway"></a>
## 🚀 Transparent Tor Gateway

Route **any** existing Docker container's entire outbound TCP and DNS traffic automatically through Tor — **without modifying your applications or adding proxy flags**.

```yaml
services:
  torproxy:
    image: hapheus/torproxy:latest
    pull_policy: always
    cap_add:
      - NET_ADMIN
    environment:
      TORPROXY_GATEWAY_MODE: "true"
      TORPROXY_COUNTRY: ${TORPROXY_COUNTRY:-}
    restart: unless-stopped

  # Attach any application container directly to torproxy's network namespace:
  my-app:
    image: curlimages/curl:latest
    network_mode: "service:torproxy"
    depends_on:
      torproxy:
        condition: service_healthy
    command: ["-s", "https://check.torproject.org/api/ip"]
```

### Why use Transparent Gateway mode?
- **Zero Application Changes:** No need to configure `HTTP_PROXY`, `ALL_PROXY`, or browser arguments.
- **Tor DNSPort (:9053):** All DNS requests on port 53 (UDP & TCP) are automatically resolved via Tor to prevent DNS leaks.
- **Fail-Closed Netfilter Kill Switch:** All direct clearnet traffic is blocked. If Tor stops or disconnects, connections fail closed immediately.
- **Process-Scoped WAN Bypass:** Only the dedicated unprivileged `tor` user may reach the WAN (`owner --uid-owner`); the API, Privoxy, and attached app containers cannot bypass the kill switch.
- **IPv6:** Tor may bootstrap over IPv6 (its own exception); every other process remains blocked — no IPv6 clearnet leaks.
- **UDP Protection:** Unsupported non-DNS UDP traffic (e.g. QUIC/HTTP-3) is blocked with ICMP port-unreachable, ensuring fast fallback to TCP without hangs.
- See the ready-to-run [Transparent Gateway Example](examples/transparent-tor-gateway/) for details.

---

## 🤖 Model Context Protocol (MCP)

`torproxy` includes a built-in, zero-dependency **Model Context Protocol (MCP)** server on port `2501` (SSE) and via Stdio (`torproxy mcp`), allowing AI agents (Claude Desktop, Cursor, Antigravity) to manage Tor circuits and execute anonymized web requests directly.

### Claude Desktop Integration (`claude_desktop_config.json`)
Connect Claude Desktop to your running `torproxy` container via Stdio without exposing host ports:
```json
{
  "mcpServers": {
    "torproxy": {
      "command": "docker",
      "args": ["exec", "-i", "torproxy", "torproxy", "mcp"]
    }
  }
}
```

### Cursor & Antigravity Integration (`.cursor/mcp.json`)
Connect via HTTP/SSE:
```json
{
  "mcpServers": {
    "torproxy": {
      "url": "http://127.0.0.1:2501/sse"
    }
  }
}
```

Or use the root-level `.mcp.json` manifest auto-detected by Cursor and [cursor.directory](https://cursor.directory) on workspace open.

### Smithery.ai Integration (`smithery.yaml`)
Install directly with zero configuration via [Smithery.ai](https://smithery.ai):
```bash
npx -y @smithery/cli install hapheus/torproxy --client claude
```

### Key Tools Available to Agents
- `torproxy_fetch`: **Zero-config fetch** — execute HTTP/HTTPS or `.onion` requests strictly routed through Tor (no local curl or host proxy setup needed).
- `torproxy_status`: Retrieve live bootstrap progress, egress IP, and GeoIP metadata.
- `torproxy_get_ip`: Query the current verified Tor exit IP.
- `torproxy_reconnect`: Request a new Tor identity (`SIGNAL NEWNYM`), optionally changing exit country (`country="DE"`).
- `torproxy_health` & `torproxy_diagnostics`: Automated operational health and circuit diagnostics.

See [docs/mcp.md](docs/mcp.md) for full configuration details, authentication options, and tool schemas.

---

## Available Interfaces and Ports

| Interface | Default Port | Internal Service | Description |
| --------- | ------------ | ---------------- | ----------- |
| **HTTP Proxy** | `8118` | Privoxy | HTTP/HTTPS forward proxy routing traffic to Tor. Fails closed (500/503) if Tor is disconnected. |
| **SOCKS5 Proxy** | `9050` | Tor | Direct Tor SOCKS5 listener. Supports remote DNS resolution via `socks5h://`. |
| **REST API** | `8080` | Python REST API | JSON management and health endpoint. Circuit rotation and connection management. |
| **MCP Server (SSE)** | `2501` | Python MCP Server | Model Context Protocol server (SSE) for AI agents (Cursor, Antigravity). |
| **TransPort** *(Gateway Mode)* | `9040` | Tor | Transparent TCP proxy redirect listener when `TORPROXY_GATEWAY_MODE=true`. |
| **DNSPort** *(Gateway Mode)* | `9053` | Tor | Transparent DNS resolver listener when `TORPROXY_GATEWAY_MODE=true`. |

---

## REST API Reference

The lightweight API server on port `8080` manages circuits and monitors health without requiring clients to implement the Tor Control Protocol.

| Endpoint | Method | Status Code | Purpose | Example Response |
| -------- | ------ | ----------- | ------- | ---------------- |
| `/status`, `/` | `GET`, `POST` | `200` | Connection status and current exit IP. | `{"status":"connected","ip":"198.51.100.42"}` |
| `/ip` | `GET` | `200`, `503` | Current verified Tor exit IP. Returns 503 while bootstrapping. | `{"ip":"198.51.100.42"}` |
| `/health`, `/v1/health` | `GET` | `200`, `503` | Structured diagnostic health state across Tor, proxies, and egress. | `{"status":"healthy","tor":{"bootstrapped":true,"progress":100},"proxies":{"socks5":true},"egress":{"verified":true,"ip":"..."}}` |
| `/diagnostics`, `/v1/diagnostics` | `GET` | `200` | Operational telemetry (uptime, reconnect counts, bootstrap progress). | `{"uptime_seconds":120,"reconnect_total":2,"tor":{"bootstrapped":true}}` |
| `/metrics`, `/v1/metrics` | `GET` | `200` | Prometheus exposition format metrics for scrapers. | Plaintext Prometheus metrics. |
| `/reconnect` | `POST` (or `GET`) | `200`, `400` | Request new circuit (`SIGNAL NEWNYM`). Supports `?country=DE` or JSON body. Clears IP cache. | `{"action":"reconnect","status":"success","exit_country":"DE"}` |
| `/disconnect` | `POST` (or `GET`) | `200` | Pause Tor network traffic (`DisableNetwork=1`). Fails closed. | `{"action":"disconnect","status":"success"}` |
| `/connect` | `POST` (or `GET`) | `200`, `400` | Resume Tor network traffic (`DisableNetwork=0`). Supports `?country=DE` or JSON body. | `{"action":"connect","status":"success"}` |

> **HTTP Methods:** `POST` is recommended for state mutations (`/reconnect`, `/disconnect`, `/connect`). Legacy `GET` calls remain fully supported for backward compatibility. Mutations presented with a foreign browser `Origin`/`Referer` are rejected with `403 Forbidden` (CSRF protection); `curl`/script calls without those headers are unaffected.
> **Authentication (Recommended):** Unauthenticated by default for backward compatibility. Setting `TORPROXY_API_TOKEN` (or `TORPROXY_API_KEY`) protects all endpoints with `Authorization: Bearer <token>` or `X-API-Key: <token>`.
> Detailed schema definitions are documented in [docs/api.md](docs/api.md).

---

## Container Health Checks

`torproxy` includes a ready-aware Docker health check (`docker/healthcheck.sh`). A container reports `healthy` only when:
1. Privoxy and Tor daemons are running under non-root user.
2. Tor bootstrap is 100% complete (`bootstrapped=done`).
3. SOCKS5 listener on port 9050 passes greeting handshake.
4. Egress IP is confirmed through Tor (`IsTor: true`).

In Docker Compose, dependent services can wait for genuine Tor readiness using `service_healthy`:

```yaml
services:
  app:
    image: my-scraper:latest
    depends_on:
      torproxy:
        condition: service_healthy
```

---

## Configuration

Default settings work out-of-the-box with zero configuration. You can customize runtime behavior using environment variables in `.env` or Docker Compose:

| Variable | Default | Purpose |
| -------- | ------- | ------- |
| `TORPROXY_COUNTRY` | *(unset)* | Restrict exit nodes to specific countries (e.g. `DE`, `AT`, `US`, `DE,AT`). |
| `TORPROXY_STRICT_NODES` | `0` | When `1`/`true`, reject circuits if configured country exit nodes are unavailable. |
| `TORPROXY_GATEWAY_MODE` | `0` | Enable Transparent Tor Gateway mode (`TransPort`, `DNSPort`, netfilter kill switch). Requires `cap_add: [NET_ADMIN]`. |
| `TORPROXY_API_TOKEN` | *(unset)* | Bearer token / API key securing REST API port `8080`. Recommended for production. |
| `TORPROXY_GEOIP_AUTO_UPDATE` | `0` | Opt-in: periodically download and refresh Tor's GeoIP database from Tor Project GitLab. |

> 📖 **Full Configuration Reference:** See [docs/configuration.md](docs/configuration.md) for custom listening ports (`TORPROXY_LISTEN_PORT`, `TORPROXY_SOCKS_PORT`, `TORPROXY_API_PORT`), address binding rules, and GeoIP update parameters.

---

## Shipped Examples

Ready-to-run use cases with hardened configurations:

| Example | Purpose | Guide |
| ------- | ------- | ----- |
| **Transparent Tor Gateway** | Route any container's entire TCP & DNS traffic automatically through Tor without proxy settings. | [View Example](examples/transparent-tor-gateway/) |
| **Hardened Production** | Read-only rootfs, dropped capabilities, and memory-backed tmpfs. | [View Example](examples/hardened-production/) |
| **Browserless Chromium** | Headless browser automation and scraping routed through Tor. | [View Example](examples/browserless-chromium-via-tor/) |
| **CRW Web Scraper** | High-performance webpage-to-Markdown scraper via Tor proxy. | [View Example](examples/crw-web-scraper-via-tor/) |
| **SearXNG Metasearch** | Private, self-hosted search engine routing upstream queries via Tor. | [View Example](examples/searxng-via-tor/) |

---

## Security Model and Trust Boundaries

- **Non-Root Execution**: The container init starts as root only to drop privileges — every daemon runs as an unprivileged user (`tor`, `privoxy`, `torproxy`). In gateway mode the netfilter WAN bypass is scoped to the dedicated `tor` UID, so the API/Privoxy cannot reach the WAN directly (ISSUE-03).
- **Authenticated ControlPort**: Tor's control port (`127.0.0.1:9051`) requires ephemeral filesystem cookie authentication (`CookieAuthentication 1`).
- **Fail-Closed Guarantee**: If Tor disconnects or crashes, Privoxy drops traffic and returns HTTP 503 instead of leaking to clearnet.
- **DNS Leak Prevention**: To prevent local DNS leaks, always use remote DNS resolution with SOCKS5 (`curl --socks5-hostname 127.0.0.1:9050` or `socks5h://`).
- **Threat Model**: See [SECURITY.md](SECURITY.md) for vulnerability reporting procedures, threat boundaries, and anonymity limitations.
- **Hardening Guide**: See [docs/hardening.md](docs/hardening.md) for read-only rootfs and dropped Linux capabilities.

---

## Project Documentation

- [docs/compatibility.md](docs/compatibility.md) — Backward compatibility contract and frozen interfaces.
- [docs/api.md](docs/api.md) — Exhaustive REST API specification and response schemas.
- [docs/configuration.md](docs/configuration.md) — Configuration guide and startup validation rules.
- [docs/hardening.md](docs/hardening.md) — Container runtime hardening, read-only rootfs, and security recommendations.
- [docs/releases.md](docs/releases.md) — Semantic versioning policy, upgrade paths, and rollback procedures.

---

## Contributing

We welcome contributions! Please review our [Contributing Guidelines](CONTRIBUTING.md) and [Code of Conduct](CONTRIBUTING.md#core-engineering-principles).
All PRs must maintain backward compatibility, include hermetic unit tests, and maintain >= 90% test coverage.

---

## License

Released under the [MIT License](LICENSE).

---

## 💝 Support the Project

If you find this project helpful, consider showing some support:

- ☕ Buy me a coffee: Send a tip via [PayPal](https://paypal.me/hapheus).
- ❤️ Charity Donation: Donate to [Österreichische Krebshilfe (Austrian Cancer Aid)](https://www.krebshilfe.net/) or any local animal welfare organization.
- 🎯 Dream Support: I would love some tickets for the World Darts Championship at Ally Pally (Alexandra Palace) – hopefully, I will make it there one day!