v0.1.4
Changelog
All notable changes to CupriTor are recorded here. This project adheres to Semantic Versioning.
0.1.4
Vanguards-lite (guard-spec / proposal 333) — guard-discovery defense for onion-service circuits.
Anonymity
- Layer-2 vanguards. Onion-service circuits (both client and service — rendezvous, introduction, HSDir) now
route through a small, stable, slowly-rotating layer-2 guard set inserted as the second hop
(guard → L2 → middle → destination), so an adversary who can induce many circuits can't enumerate random
middles to work back toward the entry guard. Per spec: 4 vanguards,max(X,X)lifetime with X uniform in
[1, 12] days, bandwidth-weighted + /16-distinct selection, replaced when a vanguard leaves the consensus or
loses Fast/Stable; persisted in theIStateStorelike the entry guards. - Behavior change: onion circuits are now 4 hops by default (was 3). Configure via
TorClientOptions.Vanguards—All(client + service, the default, matching Tor),OnionServiceOnly, or
Off. Exit and directory circuits are unaffected.
0.1.3
Tunnelled directory fetches — closes the largest remaining anonymity gap (per-circuit relay selection leaking
over the clearnet directory channel). No breaking changes.
Anonymity
- Download-all microdescriptor warm at bootstrap. After verifying the consensus, every listed relay's
microdescriptor is fetched once (over the clearnet bootstrap source) into the cache, so subsequent circuit
builds hit the cache and never fetch per-hop descriptors over the directory channel. Fetching all of them
also means an observer of the bootstrap can't infer which relays a circuit uses (including the guard). - Over-circuit directory refreshes. After bootstrap, consensus refreshes go over a Tor circuit (BEGIN_DIR
to a V2Dir cache) via the new internalCircuitDirectorySource, with a clearnet fallback — so directory
traffic stops signalling "Tor user" to an on-path observer on every refresh. Each refresh re-warms the cache
over a circuit for the new consensus. - Net effect: the only clearnet directory traffic is the one-time bootstrap (consensus + keys + all
microdescriptors), which reveals that a Tor client is bootstrapping but not its path selection. Build-time
microdescriptor resolution stays cache-first (clearnet only as a rare fallback for a brand-new relay).
Correctness
- PADDING keepalives no longer break circuit builds. Build-time CREATE2/EXTEND2 reads now drop PADDING/VPADDING
cells (matching the receive loop and tor-spec) instead of failing — a relay with connection padding can send a
keepalive mid-handshake, which previously aborted the build. Surfaced live by the long over-circuit consensus
transfer; hardens every circuit build (onion + exit included). - Onion descriptor lookup fixed in the morning UTC window. The client computed the HSDir ring with the current
shared-random value unconditionally; in the[00:00–12:00)UTC window the current time period pairs with the
previous SRV (now selected viaIsBetweenTpAndSrv, mirroring the service's two-period publish). Previously,
onion lookups in that window fetched from the wrong HSDirs and got a 404 from every one — a pre-existing latent
bug that made onion connect fail all morning and succeed in the afternoon.
0.1.2
Hardening and DX for onion-only, high-fan-out transports. No breaking changes (all new members are additive).
Anonymity / correctness
- Microdescriptor cache — per-hop microdescriptors are cached by content digest and pruned on consensus
refresh, so building a circuit no longer re-fetches (over the clearnet directory channel) the descriptors of
relays already seen. Reduces the repeated on-path leak of relay selection and cuts per-dial latency. - Cancellation cleanup — fixed two paths that could leak an
OrConnectionuntil GC when a dial was cancelled
mid-handshake (the guard first hop inBuildOverPathAsync, and INTRODUCE1 in the onion connector); partially
built circuits are now always disposed, including on cancellation.
Safety / API
TorClientOptions.OnionOnly— when set, any clearnet/exit dial (including a malformed address) throws
ClearnetBlockedExceptioninstead of routing through a Tor exit, so an onion-only transport can't silently
leave Tor. CoversConnectAsync, the SOCKS5 server (replies "connection not allowed by ruleset"), and the
HttpClient integration, since all dial through the same seam.FileStateStore— a durable, atomically-written (temp+ rename)IStateStoreso entry guards persist
across restarts. The in-memory default now emits a one-timeStatusChangedwarning; set
TorClientOptions.RequirePersistentStateto refuse to start without a persistent store.InvalidOnionAddressException(derives fromArgumentException) is now thrown for malformed onion
addresses byConnectToOnionAsync/LookupOnionAsync.- Per-call timeouts —
ConnectToOnionAsync/ConnectAsync/ConnectViaExitAsyncgained overloads taking an
explicitTimeSpantimeout (overridingTorClientOptions.Timeout), convenient for a racing dialer.
0.1.1
Licensing and packaging only — no code or API changes; the library is identical to 0.1.0.
- Add a repository
LICENSE(MIT) andTHIRD-PARTY-NOTICES.mdattributing the redistributed
dependencies (BouncyCastle, CupriCurve, and the .NET runtime bundled into the sample apps). - The self-contained sample bundles now include
LICENSEandTHIRD-PARTY-NOTICES.md.
0.1.0
First feature-complete release: a 100%-managed Tor client, v3 onion service, exit-capable dialer, and SOCKS5 proxy.
Live-validated end to end against the real Tor network (onion client, onion service, exit — over the default managed
TLS transport). 175 tests.
Client (use Tor)
TorClient— bootstraps and verifies the microdescriptor consensus against the 9 hard-coded directory
authorities (strict 5-of-9 majority) before trusting it; maintains entry guards; auto-refreshes the consensus.
new TorClient()works with no configuration (built-in directory authorities).- Onion client —
ConnectToOnionAsync(descriptor lookup → rendezvous → introduce →Stream), including
connecting to private (client-authorized) onions. - Exit / clearnet —
ConnectViaExitAsync/ConnectAsyncroute non-onion hosts through exit relays
(exit-policy aware, remote DNS at the exit). HttpClientintegration —ITorDialer.CreateTorHttpClient()/CreateTorHttpHandler().- SOCKS5 proxy —
Socks5ProxyServer(NO-AUTH, CONNECT; onion + clearnet), with a handshake timeout and a
connection cap. - Progress —
TorClient.StatusChanged/CurrentStatusfor bootstrap + connect phases.
Onion services (host on Tor)
PublishOnionAsync— durable v3 onion service: establishes intro points, publishes descriptors (two-period),
self-heals, and serves inbound streams (raw handler or reverse proxy to a local TCP app).- Private onions —
OnionClientAuthorization(tor-formatdescriptor:x25519:…keys). - Persistent / vanity identities —
OnionServiceKey(torhs_ed25519_secret_keyinterop).
Hosting integrations
CupriTor.AspNetCore—UseCupriTorOnion(...)hosts an ASP.NET Core app on an onion in-process (no
loopback), binding clearnet + onion side by side.CupriTor.Host— cross-platform sidecar (dotnet tool / Windows Service / systemd / Docker): config-driven
ClearnetOnly/TorOnly/Bothfront door for any local app, plus an optional SOCKS5 port.
Path selection & anonymity
- Bandwidth-weighted path selection with /16 subnet and relay-family distinctness (mutual), and persistent
entry guards with correct up/down attribution. - See docs/THREAT-MODEL.md for what CupriTor does and does not protect.
Managed crypto
- No
tor.exeand no OS crypto on the critical path: SHA3/SHAKE/AES/X25519/Ed25519-verify via BouncyCastle,
Ed25519 signing / key blinding via CupriCurve.
Known limitations
- Not independently audited; pre-1.0. No relay/node support, IPv6 exit,
RELAY_RESOLVE, or traffic-analysis
padding yet — see the roadmap.
Full Changelog: v0.1.3...v0.1.4