Skip to content

21-DOT-DEV/swift-tor

Apple Platforms Docker Builds MIT License Swift Versions Platforms

swift-tor

Swift package that embeds Tor (libtor) and provides a Swift-concurrency-first API (TorClient), plus Tor control protocol utilities (including ephemeral onion service management).

Contents

Features

  • Embedded Tor: run Tor in-process (via the libtor product)
  • High-level API: TorClient actor to start/stop Tor and observe events
  • Control protocol: TorControlClient for GETINFO, SIGNAL, ADD_ONION, DEL_ONION, etc.
  • Onion services: create/delete ephemeral v3 onion services
  • Caching: optional cacheDirectory to reuse consensus/descriptor cache across runs
  • Apple-only networking helper: URLSessionConfiguration + TorClient.makeURLSession() (guarded by canImport(CFNetwork))

Platforms

  • macOS: 15+
  • iOS: 18+
  • Linux: Ubuntu 22.04+ (via Docker)

Important

tvOS/watchOS/visionOS are not supported. Tor's codebase relies on UNIX process primitives (fork, execve, daemon, setuid) that Apple prohibits on these platforms. These restrictions are enforced at the App Store review level and would cause runtime crashes.

Installation

This package uses Swift Package Manager.

Xcode

  1. Go to File > Add Packages...
  2. Enter the package URL: https://github.com/21-DOT-DEV/swift-tor
  3. Select the desired version

Package.swift

Add the dependency:

.package(url: "https://github.com/21-DOT-DEV/swift-tor", from: "0.1.0"),

Warning

This package is pre-1.0 (SemVer major version zero). The public API is not stable and may change with any release. Pin a version using exact: to avoid unexpected breaking changes.

Then add Tor as a dependency:

.target(
    name: "MyApp",
    dependencies: [
        .product(name: "Tor", package: "swift-tor")
    ]
)

Quick Start (Basic)

Start Tor, wait for bootstrap, and get a SOCKS endpoint:

import Tor

let config = TorConfiguration.makeDefault()
let client = TorClient(configuration: config)

try await client.start()
try await client.waitUntilBootstrapped()

let socks = await client.socksEndpoint

Faster bootstraps with cacheDirectory

Tip

Reusing a cacheDirectory across runs can significantly reduce bootstrap time.

import Tor
import Foundation

let tempDataDir = FileManager.default.temporaryDirectory
    .appendingPathComponent("tor-data-\(UUID().uuidString)")
    .path

let cacheDir = FileManager.default.temporaryDirectory
    .appendingPathComponent("tor-cache")
    .path

try? FileManager.default.createDirectory(atPath: cacheDir, withIntermediateDirectories: true)

let config = TorConfiguration(
    dataDirectory: tempDataDir,
    cacheDirectory: cacheDir,
    socksPort: .ephemeral
)

let client = TorClient(configuration: config)
try await client.start()
try await client.waitUntilBootstrapped()

Apple-only: URLSession via Tor

Note

This helper requires CFNetwork and is only available on Apple platforms.

 #if canImport(CFNetwork)
 let session = try await client.makeURLSession()
 let (data, _) = try await session.data(from: URL(string: "https://check.torproject.org/api/ip")!)
 #endif

Creating a Hidden Service

Create an ephemeral v3 onion service that forwards traffic to a local server:

import Tor

// Start Tor first
let client = TorClient(configuration: .makeDefault())
try await client.start()
try await client.waitUntilBootstrapped()

// Get the control client
let control = try await client.control()

// Create an ephemeral onion service
// - Maps port 80 on the .onion to localhost:8080
// - Private key is discarded (service won't survive restart)
let service = try await control.addOnion(
    key: .newV3(discardPrivateKey: true),
    ports: [.toLocalPort(80, localPort: 8080)]
)

print("πŸ§… Hidden service running at: \(service.onionAddress)")
// e.g., "duckduckgogg42xjoc72x3sjasowoarfbgcmvfimaftt6twagswzczad.onion"

// Your local HTTP server on port 8080 is now accessible via Tor!
// Users can reach it at: http://<serviceID>.onion/

// When done, clean up
try await control.delOnion(service)
await client.stop()

Persistent Hidden Services

To create a hidden service that survives restarts, keep the private key:

// Create service and get the private key
let service = try await control.addOnion(
    key: .newV3(discardPrivateKey: false),  // Keep the key
    ports: [.toLocalPort(443, localPort: 8443)]
)

// Save service.privateKey securely for later use
let privateKey = service.privateKey!  // e.g., "ED25519-V3:base64..."

// Later, recreate the same .onion address:
let restoredService = try await control.addOnion(
    key: .providedV3(privateKey),
    ports: [.toLocalPort(443, localPort: 8443)]
)
// restoredService.onionAddress == service.onionAddress

Warning

Store private keys securely (e.g., Keychain on Apple platforms). Anyone with the private key controls the .onion address.

Demo

Run the bundled demo:

swift run TorDemo

The demo starts Tor, fetches a clearnet URL via Tor, fetches an .onion, and creates/deletes an ephemeral onion service.

Testing

Run unit tests:

swift test

Integration tests are env-gated and skipped by default:

TOR_INTEGRATION_TESTS=1 swift test --filter IntegrationTests

Roadmap

  • Linux support: βœ… complete (Phase 1)
  • Remove libbsd dependency: βœ… complete (Phase 2)
  • iOS Target Refactor: πŸ”œ planned (Phase 3)
  • Binary Size Optimization: πŸ”œ planned (Phase 3.5)

See roadmap.md for full details.

Security

For information on reporting security vulnerabilities in swift-tor, see SECURITY.md. For other 21-DOT-DEV projects, see the organization Security Policy.

Caution

Tor can't "fix" unsafe application behavior. Review the Tor Project guidance on staying anonymous. Avoid logging sensitive information (credentials, onion private keys). Consider your threat model β€” Tor integration is only one part of privacy/security.

Contributing

Contributions welcome! Please read the 21-DOT-DEV contributing guidelines for general workflow. For swift-tor specific guidance and AI-assisted development, see AGENTS.md.

License

This project is licensed under the MIT License. See LICENSE.

Tor source code is vendored in Vendor/tor and is subject to its own license(s). See Vendor/tor/LICENSE.

About

Swift wrapper around the Tor daemon with a concurrency-first client API. Provides SOCKS5 proxy, onion-routing, and Tor control protocol. iOS, macOS, and Linux.

Topics

Resources

License

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Contributors