21-dot-dev/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
- Platforms
- Installation
- Quick Start (Basic)
- Creating a Hidden Service
- Demo
- Testing
- Roadmap
- Security
- Contributing
- License
## 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:
``swift .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:
``swift .target( name: "MyApp", dependencies: [ .product(name: "Tor", package: "swift-tor") ] ) ``
## Quick Start (Basic)
Start Tor, wait for bootstrap, and get a SOCKS endpoint:
```swift 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
cacheDirectoryacross runs can significantly reduce bootstrap time.
```swift 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
CFNetworkand 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:
```bash
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:
```bash
swift test
```
Integration tests are **env-gated** and skipped by default:
```bash
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](.specify/memory/roadmap.md) for full details.
## Security
For information on reporting security vulnerabilities in swift-tor, see [SECURITY.md](SECURITY.md). For other 21-DOT-DEV projects, see the [organization Security Policy](https://github.com/21-DOT-DEV/.github/blob/main/SECURITY.md).
> [!CAUTION]
> Tor can't "fix" unsafe application behavior. Review the [Tor Project guidance](https://support.torproject.org/) 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](https://github.com/21-DOT-DEV/.github/blob/main/CONTRIBUTING.md) for general workflow. For swift-tor specific guidance and AI-assisted development, see [AGENTS.md](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`.Package Metadata
Repository: 21-dot-dev/swift-tor
Default branch: main
README: README.md