Start here

Bedrohungsmodell

47 Min. LesezeitView as markdown ↗

Dieses Dokument benennt URnetworks Angreifer und sagt, was das System gegen jeden einzelnen tut und was nicht. Es ist geschrieben, um angegriffen zu werden. Wo eine Eigenschaft an eine Bedingung geknüpft ist, steht die Bedingung im selben Satz; wo etwas eine Designabsicht ist, die der ausgelieferte Code noch nicht durchsetzt, ist es als Designabsicht gekennzeichnet und nicht als Eigenschaft. Eine Zusammenfassung für Leser, die die vollständige Akte nicht lesen werden, steht vor Abschnitt 1.

Der Maßstab ist Tors eigene Erklärung der Angriffe, die Onion-Routing nicht abwehrt: Ein Angreifer, der beide Enden eines Circuits beobachtet, kann sie über das Timing korrelieren, und Tor sagt das öffentlich. URnetwork liefert keinen Cover-Traffic und kein Padding aus, hier gilt also dasselbe, und Abschnitt 8 sagt es ohne Abmilderung.

Stand der unabhängigen Prüfung, einmal genannt und für alles Folgende gültig. Es gibt kein unabhängiges Audit des Protokolls, der Connect-Engine oder des Server-Codes des Operators — der Schicht, auf der die Privacy-Zusagen liegen. Zwei externe Prüfungen aus 2025 decken andere Flächen ab: ein unabhängiger externer Penetrationstest der Web-Anwendung und der API (25. April – 5. Mai 2025, mit Zugangsdaten, manuell, OWASP Top 10 plus eine Prüfung der ASVS-Kontrollen) und eine MASA-AL2-Prüfung der Android-App durch die Leviathan Security Group (abgeschlossen am 23. Mai 2025), die bestanden wurde und von der Leviathan selbst sagt, sie sei „keine ganzheitliche Sicherheitsbewertung oder umfassender Penetrationstest“. Keine der beiden untersuchte Logging, Aufbewahrung, den Datenpfad oder das Protokoll. Jede Behauptung in diesem Dokument ist deshalb eine Behauptung über lesbaren Quellcode, den jeder prüfen kann und den außerhalb des Projekts noch niemand als Ganzes geprüft hat.

Wie die Quellenangaben zu lesen sind

Behauptungen zitieren path:line gegen die Arbeitsbäume unter /Users/brien/urnetwork/{connect,sdk,server,proxy,extension}, Stand 2026-08-07. Zeilennummern verschieben sich; die Bezeichner nicht. Wo ein Befund in einem früheren Code-Verifikationsdurchgang festgestellt wurde, zitiert er review/verified/ARCHITECTURE.md oder review/verified/PRIVACY-ENFORCEMENT.md, die derselben Konvention folgen.

Drei Kennzeichnungen werden bewusst verwendet:

  • Eigenschaft — der Code setzt sie durch, und ein Angreifer, der die anderen Parteien kontrolliert, kann sie trotzdem nicht verletzen.
  • Disziplin — der Code entscheidet sich, etwas nicht zu tun, wozu er in der Lage wäre. Eine Deployment-Änderung oder ein einzeiliger Patch könnte das umkehren.
  • Designabsicht — das Design nennt dies als Ziel; der ausgelieferte Code setzt es noch nicht durch.

Zusammenfassung

Dieser Abschnitt ist für Leser, die die vollständige Akte nicht lesen werden. Jeder Satz darin ist durch einen nummerierten Abschnitt weiter unten oder durch die Aussage zur unabhängigen Prüfung oben gedeckt.

Was das System ist. URnetwork ist ein Privacy-Netzwerk, in dem Mitglieder Datenverkehr für andere Mitglieder weiterleiten. Der Pfad ist du → Extender → Operator → Provider → Internet. Die Vertrauensaufteilung liegt zwischen zwei Relay-Parteien: dem Operator, der weiß, wer du bist, und dem Provider, der sieht, wohin dein Datenverkehr geht.

Die zwei Eigenschaften, mit je eigenem Geltungsbereich. Auf einem Relay-Pfad erhält der Provider nie deine echte Quell-IP; keine Einstellung und kein Provider-Build ändert das (§2). Der Operator kann die versiegelte Sitzung zwischen Client und Provider nicht lesen, die auf den fünf nativen Apps (Android, iOS, macOS, Windows und Linux) unter dem Namen Post-Quanten-Verschlüsselung (Post Quantum Encryption) standardmäßig eingeschaltet ausgeliefert wird (§2.1). Die Browser-Erweiterung hat keine versiegelte Sitzung — ihr Client-Gerät läuft im Inneren des Operators, der als Protokoll-Übersetzungspunkt wirkt, eine Versiegelung kann dort also nicht existieren (§2.4).

Die Integritätsmängel — einer behoben, einer offen. Die Versiegelung fiel früher in jedem Modus still offen aus (fail open). Behoben am 2026-08-10: Mit eingeschalteter Post-Quanten-Verschlüsselung läuft der Client fail-closed und weigert sich, Anwendungsdaten im Klartext zu senden oder anzunehmen, statt darauf herabzustufen (§2.2). Ist der Schalter aus, gilt weiterhin das alte opportunistische Verhalten, und in keinem der beiden Modi meldet irgendeine App, welcher der beiden Fälle eingetreten ist. Weiterhin offen: Kann der Operator einen Provider-Schlüssel austauschen? Heute: ja (§7.4) — die Vertraulichkeit der Versiegelung hält also gegen einen passiven Operator und gegen einen Angreifer auf dem Pfad, der die Hülle abstreift, nicht aber gegen einen Operator, der bereit ist, einen falschen Schlüssel auszustellen. Die Punkte 8 und 9 von verify/BEFORELAUNCH.md verfolgen beide; §2.2 und §7.4 beschreiben den aktuellen Stand.

Wogegen das System nicht schützt. Ein Angreifer, der sowohl dein Zugangsnetz als auch die Provider beobachtet, die deinen Datenverkehr tragen, kann die beiden Enden über Größe und Timing korrelieren (§8.1). Ein globaler passiver Beobachter liegt vollständig außerhalb des Geltungsbereichs (§8.3). URnetwork liefert kein Padding, keinen Cover-Traffic und kein Mixing aus; Abschnitt 11 ist die vollständige Liste.

Unabhängige Prüfung. Es gibt kein unabhängiges Audit des Protokolls, der Connect-Engine oder des Server-Codes des Operators; zwei externe Prüfungen aus 2025 decken andere Flächen ab: ein Penetrationstest der Web-Anwendung und der API und eine MASA-AL2-Prüfung der Android-App durch die Leviathan Security Group, die bestanden wurde.

Wo die Details stehen. Die Abschnitte 1 und 2 definieren die Parteien, die drei Verbindungsmodi und was jede Partei in jedem Modus sieht. Die Abschnitte 3 bis 5 nehmen sich jeden Angreifer der Reihe nach vor: einen feindseligen Provider, einen Netzbeobachter und den Operator. Die Abschnitte 6 und 7 behandeln vom Operator betriebene Provider, Kollusion und den Austausch von Provider-Schlüsseln. Die Abschnitte 8 bis 12 behandeln Verkehrskorrelation, Gerätekennungen, rechtliche Anordnungen, die Liste dessen, wogegen das System nicht schützt, und was dieses Dokument nicht klären konnte. Abschnitt 13 sagt, wohin Schwachstellen und Korrekturen zu melden sind.

1. Die Parteien

ParteiBetrieben vonPosition
ClientDem NutzerDie App- oder SDK-Instanz; auf dem Browser- und dem Proxy-Pfad läuft sie stattdessen auf den Servern des Operators (§2.4)
Ingress-LBDem Operatornginx; stempelt die beobachtete Client-Adresse in X-UR-Forwarded-For (xops/gitops-unused/gitops-prod/urnetwork/connect/ingress.yaml:9 — das einzige Ingress-Manifest im Baum, und sein Verzeichnis heißt gitops-unused, es beschreibt also womöglich nicht die Produktion; §12)
Connect-DienstDem OperatorEin oder zwei Prozesse auf verschiedenen Hosts, verbunden über eine interne Exchange-Verbindung (server/connect/resident.go:2310-2327). Nicht „ein Relay-Server“
API / SteuerungsebeneDem OperatorAuth, Provider-Discovery und -Ranking, Verträge, das Nachschlagen öffentlicher Schlüssel (server/api/api.go)
ProviderJedem MitgliedEmpfängt weitergeleitete Pakete und wählt das Ziel über die eigene Verbindung an (connect/ip.go:3932 RemoteUserNatProviderLocalUserNat)
ExtenderJedem FreiwilligenDie erste Etappe des Pfades: ein TLS-Weiterleitungs-Relay auf einer unabhängigen Adresse. Trägt die Sitzung Client↔Plattform, ohne sie zu terminieren, sieht also die verbindende IP und kann nicht entschlüsseln (connect/net_extender.go:51-55, connect/extender/extender.go:57-61)
DoH-ResolverCloudflare, Google, Quad9, OpenDNSDer App-Pfad löst DNS über HTTPS durch den Tunnel zu einem dieser vier auf (connect/net_http_doh.go:150-155)
ZahlungsdienstleisterStripe, Apple, Google, Solana/CircleHalten die echte Identität bezahlter Konten; der Operator speichert die Verknüpfungsschlüssel (server/db_migrations.go:1712-1719, :4640, :2350-2356)

Der Operator ist BringYour, Inc., eine Delaware-Kapitalgesellschaft mit einer Postanschrift in San Francisco (docs/legal/ur.xyz/terms.md:16,25; docs/legal/terms.md:269-271). Abschnitt 10 behandelt, was das für Rechtsverfahren bedeutet.

2. Die drei Modi und was jede Partei sieht

Diese Tabelle ist maßgeblich und stimmt mit OVERVIEW.md überein. Alles Weitere in diesem Dokument ist der Mechanismus dahinter.

ModusOperator siehtProvider siehtStandard und Verfügbarkeit
Relay, versiegeltKonto/Quellverbindung, Provider-Zuordnung, Chiffretext sowie Timing/VolumenZiel-Datenverkehr, Geräte-/Vertrags-ID, nicht deine echte Quell-IPder native Standard, ab Werk; nur native Apps; siehe §2.2
Relay, StandardKonto/Quellverbindung, Provider-Zuordnung, innere Ziele und PaketbytesZiel-Datenverkehr, Geräte-/Vertrags-ID, nicht deine echte Quell-IPBrowser- und Proxy-Pfade (§2.4) oder Versiegelung aus; ist sie an, wird ein Provider, zu dem sie sich nicht aufbauen lässt, übersprungen statt hier bedient (§2.2)
Direktweniger Relay-Beteiligungdeine echte Quell-IP und Ziel-Datenverkehropt-in, durch Ausschalten von Starke Anonymisierung (Strong Anonymization, §2.3)

Es folgen zwei asymmetrische Aussagen, und sie sind nicht gleich stark:

Die Blindheit des Providers für deine Identität ist eine Eigenschaft, und sie ist auf beiden Relay-Pfaden bedingungslos. Der eingehende Einstiegspunkt des Providers nimmt IDs entgegen, nie eine Adresse: RemoteUserNatProvider.Receive ist über einen TransferPath aus drei 16-Byte-IDs parametrisiert (connect/ip.go:4298-4303; connect/connect.go:45-49). Kein Transport-Protobuf trägt eine Client-Adresse, eine Stadt oder ein Land — ein Grep über connect/protocol/*.proto nach location|city|country|region liefert nichts (review/verified/ARCHITECTURE.md §1.3). Es gibt keine Einstellung, die das auf einem Relay-Pfad abschaltet, und kein Provider-Build kann eine Adresse erlangen, die ihm nie gesendet wird.

Die Blindheit des Operators für Inhalte ist ein Standardwert, keine Einstellung — über einem Mechanismus mit nur noch einem Mangel. Der Mechanismus hat einen Namen: Post-Quanten-Verschlüsselung, ein Schalter im Connect-Drawer der Android-, iOS-, macOS-, Windows- und Linux-Apps, der eine Ende-zu-Ende-Sitzung zwischen deinem Client und dem Provider versiegelt (§2.1). Bedingt ist nicht mehr, ob der Nutzer den Schalter findet, und seit 2026-08-10 ist auch nicht mehr bedingt, ob die Versiegelung still nicht zustande kam: Ist der Schalter an, trägt eine Verbindung, die sich nicht versiegeln lässt, überhaupt keinen Anwendungsverkehr, statt ihn im Klartext zu tragen (§2.2). Bedingt bleibt, ob der Operator, der die Provider-Schlüssel ausliefert, gegen die die Versiegelung geprüft wird, sie ehrlich ausliefert (§7.4). Lies das, bevor du dich auf diese Eigenschaft verlässt; für den Nutzer ist es nicht sichtbar.

Standard beim Launch: AN. Der Product Owner hat am 2026-08-07 entschieden, dass die versiegelte Sitzung eingeschaltet ausgeliefert wird, die Blindheit des Operators ist also der Standard und nicht eine Entscheidung, die der Nutzer treffen muss. Dieses Dokument ist gegen diesen Launch-Zustand geschrieben; zum Zeitpunkt des Schreibens steht der Code weiterhin standardmäßig auf aus (verify/BEFORELAUNCH.md Punkt 7).

Diese Lücke wiegt in eine Richtung schwerer als früher. Weil das Fail-Closed-Gate an denselben Schalter gebunden ist, verbreitet das Einschalten des Standards jetzt eine echte Garantie an jeden Nutzer, statt bloß die Reichweite eines stillen Fehlschlags zu vergrößern — das Gegenteil dessen, wovor dieser Abschnitt warnte, als die Versiegelung noch in jedem Modus offen ausfallen konnte. Was der Standardwert weiterhin nicht beheben kann, ist §7.4, denn das ist eine Eigenschaft der Versiegelung selbst: Eine standardmäßig eingeschaltete Versiegelung, in die sich der Operator als MITM einklinken kann, schützt nur gegen einen passiven Operator.

Die Browser-Erweiterung hat überhaupt keine versiegelte Sitzung (extension/src/utils/auth-params.ts:33, performance_profile: null). Das ist Geltungsbereich, kein Standardwert, und die Launch-Entscheidung ändert daran nichts.

Überall dort, wo die Versiegelung fehlt — die Browser- und Proxy-Endpunkte (§2.4) und die nativen Apps mit ausgeschalteter Versiegelung (§2.2) —, terminiert der Operator das TLS/QUIC des Clients und leitet IP-Pakete im Klartext weiter (connect/protocol/ip.proto:9-16). Er entscheidet sich dafür, nur den Routing-Header zu parsen — FilteredTransferFrame enthält nichts als den Transferpfad (connect/protocol/transfer.proto:85-87; connect/transfer.go:1500-1509) —, und das ist Disziplin, keine kryptografische Barriere.

2.1 Was die Versiegelung genau ist

Eine TLS-1.3-Sitzung direkt zwischen Client und Provider, als gewöhnliche Control-Frames durch denselben Operator getragen (connect/transfer_encrypt.go:44-51). Der Schlüsselaustausch ist das hybride X25519MLKEM768, mit konventionellem X25519 als Fallback (connect/transfer_encrypt.go:373-381,406); das AEAD ist AES-256-GCM über einem 32-Byte-Exportschlüssel (:285-297); Identitäten sind Ed25519 (:892-897), „Post-Quanten“ bezieht sich also allein auf den Schlüsselaustausch. Wechselseitiges TLS ist Pflicht, und das Peer-Zertifikat wird auf der Sequence-Schicht gegen das Commitment des Vertrags geprüft statt vom TLS-Stack (:388-406).

2.2 Fail-closed, wenn du es verlangst, fail-open, wenn nicht

Aktualisiert am 2026-08-10. Dieser Abschnitt sagte früher, die Versiegelung falle still offen aus und es gebe keine Fail-Closed-Option. Das trifft nicht mehr zu, und die Änderung ist die bedeutendste Härtung in der Geschichte dieses Dokuments — sie hat den Downgrade-Vektor in beide Richtungen geschlossen. Was folgt, ist das aktuelle Verhalten, aus dem Quellcode gelesen.

Verschlüsselung ist weiterhin eine binäre Eigenschaft von Cipher() != nil, und ein Cipher von nil bedeutet weiterhin, dass der Handshake nicht abgeschlossen wurde: ein gescheiterter Handshake, ein gescheiterter Identitätsnachweis oder ein Vertrag, der den öffentlichen Schlüssel des Peers nie getragen hat, lassen ihn allesamt nil. Ein gescheiterter Identitätsnachweis ist auf der Sitzungsschicht weiterhin nicht fatal — die Sitzung bleibt „unauthentifiziert“ (connect/transfer_encrypt.go:1977), statt abgebaut zu werden.

Was sich geändert hat, ist, was danach passiert. Es gibt jetzt drei Modi (connect/transfer_encrypt.go, EncryptionMode):

ModusVerhalten
EncryptionModeOffDer Nullwert. Sitzungsschicht inaktiv, alles Klartext.
EncryptionModeOpportunisticVersiegelt, sobald eine Sitzung zustande kommt, bis dahin Klartext — oder dauerhaft, falls sie nie zustande kommt. Das historische Verhalten.
EncryptionModeRequiredGibt Anwendungsdaten niemals im Klartext an einen Peer weiter, für den eine Sitzung erwartet wird, und nimmt sie von ihm auch nie im Klartext an.

Unter EncryptionModeRequired wird die Garantie an vier Stellen durchgesetzt:

  • Eingangs-Gate beim Senden (connect/transfer.go:2796). Ein Anwendungspaket wartet innerhalb des Zeitlimits des Aufrufers auf den Cipher und wird dann abgelehnt, ungesendet — nie herabgestuft — mit einem typisierten ErrEncryptionRequiredNotEstablished.
  • Absicherung beim Senden (connect/transfer.go:4125). Ein Frame, das den Writer ohne Cipher erreicht, wird abgelehnt statt geschrieben; das deckt das enge Zeitfenster ab, in dem eine Sitzung zwischen Einreihen und Schreiben abgebaut wird.
  • Gate beim Empfangen (connect/transfer.go:5844). Ein Anwendungs-Frame im Klartext von einem Peer, für den eine Sitzung erwartet wird, wird verworfen und auditiert — das ist die Hälfte, auf die es am meisten ankommt, denn sie schließt das Downgrade, bei dem ein Angreifer die Hülle abstreift und der Empfänger den Klartext sonst annehmen würde. Das Frame wird bestätigt (ack) und verworfen, statt unbestätigt zu bleiben, denn ein zurückgehaltenes Ack würde eine Lücke in die geordnete Sequenz reißen und beide Seiten festfahren.
  • Vorfilter für Kandidaten (connect/ip_remote_multi_client.go, EncryptionCapabilityPrefilter). Ein Fenster-Kandidat, von dem die Out-of-Band-Schlüssel-API der Plattform sagt, er habe nie einen Identitätsschlüssel veröffentlicht, wird sofort als gescheitert gewertet, denn er kann den Handshake nie abschließen. Er beschleunigt nur ein ohnehin sicheres Scheitern; er lässt nie einen Kandidaten zu.

Handshake-Control-Frames, Acks und Control-Plane-Peers sind bewusst ausgenommen — das Gate deckt die Anwendungs-Nutzlast ab, nicht das Gerüst, das sie erst in Gang bringt.

Welchen Modus du bekommst, entscheidet der Schalter Post-Quanten-Verschlüsselung. Ist er an, läuft der Consumer-Client in EncryptionModeRequired (connect/ip_remote_multi_client.go:9186-9197): Ein Provider, der keine Sitzung aufbauen kann, trägt überhaupt keinen Anwendungsverkehr für dich, statt ihn im Klartext zu tragen. Der genannte Preis ist Verfügbarkeit, bewusst in Kauf genommen. Provider laufen in EncryptionModeOpportunistic (sdk/device_local_provider.go:98), damit ein Provider sowohl versiegelte als auch unversiegelte Consumer bedienen kann; das ist eine Kompatibilitätsentscheidung auf der Responder-Seite und schwächt die Garantie des Initiators nicht.

Was das für den Schalter bedeutet. Mit eingeschalteter PQE fällt die Versiegelung nicht mehr offen aus: Sie fällt geschlossen aus, laut, beim Senden wie beim Empfangen. Mit ausgeschalteter PQE gilt das opportunistische Verhalten, das frühere Fassungen dieses Abschnitts beschrieben haben, in vollem Umfang weiter — Datenverkehr kann im Klartext fließen, wenn nie eine Sitzung zustande kommt, und nichts sagt es dem Nutzer. Die ehrliche Aussage ist also an eine Bedingung geknüpft, und der Standardwert ist entscheidend: siehe den Hinweis Standard beim Launch: AN in §2.

Das Verhalten ist durch Tests abgedeckt, nicht nur durch Kommentare — TestRequiredEncryptionFailsClosedAgainstPlaintextPeer, TestRequiredGateNonBlockingSendRefusesPreCipher, TestRequiredGateBoundedBudgetRefusesUnsent, TestRequiredSendRefusalTypedErrorAndEvent und TestRequiredContractFreeWithoutKeySourceFailsClosed.

Ein veralteter Kommentar, den man beim Audit ignorieren sollte: Der Kopf von completeHandshake bei connect/transfer_encrypt.go:1650-1654 behauptet weiterhin uneingeschränkt, „der nachfolgende Datenverkehr fließt im Klartext“. Das stimmt nur im Modus Off und Opportunistic; unter Required setzen die Gates oben es außer Kraft. Der Kommentar ist älter als die Korrektur.

Weiterhin offen: Der Nutzer kann nicht sehen, welchen Modus eine Verbindung bekommen hat. Es gibt in keiner App eine Anzeige pro Verbindung, ob versiegelt oder nicht. Die einzigen Signale sind Geräte-Log-Zeilen — peer identity proof verified — cipher is now usable (connect/transfer_encrypt.go:1954) bei Erfolg, ein Errorf bei Fehlschlag und ein NotifyRequiredSendBlocked-Ereignis, wenn das Gate ablehnt. Das SDK stellt einen Änderungs-Hook bereit, das fehlende Stück ist also UI, nicht Verkabelung.

2.3 Der Direktmodus

Starke Anonymisierung auszuschalten setzt AllowDirect, dessen eigener Feldkommentar lautet: // setting this to true exposes the real source IP to the provider (sdk/sdk.go:710-711). Der Flow wird auf einen P2P-Stream gezwungen (connect/ip_remote_multi_client_probe.go:1176-1178), über einen WebRTC/ICE-Datenkanal (connect/transport_p2p_webrtc.go:986), und ICE heißt, dass beide Endpunkte die Adressen des jeweils anderen erfahren. Der Standard ist aus — ein Nil-Performance-Profil ergibt false (connect/ip_remote_multi_client.go:1861-1863; sdk/local_state.go:607-616).

Zwei erzwungene Fälle: Peers im selben Netzwerk (deine eigenen Geräte) erlauben direkt immer (connect/ip_remote_multi_client.go:1822-1825), und gehostete Geräte erzwingen aus (sdk/device_local.go:1516-1533). Beachte, dass der Direktmodus den Operator nur aus dem Datenpfad entfernt. Verträge, Provider-Auswahl und Steuernachrichten laufen weiterhin über die Plattform, der Operator erfährt also weiterhin, welchen Provider du genutzt hast und wie viele Bytes bewegt wurden.

2.4 Der Pfad, den die Tabelle nicht abdeckt: die Browser- und die Proxy-Endpunkte

In der Browser-Erweiterung, der Web-App und an den HTTPS-/SOCKS5-/WireGuard-Endpunkten betreibt der Operator den Client. Die Erweiterung stellt ein serverseitiges Proxy-Gerät bereit (extension/src/utils/auth-params.ts:22-36, enable_socks/enable_http), und der Browser wird auf den Proxy-Endpunkt des Operators gerichtet — standardmäßig HTTPS CONNECT (extension/src/bridge/background.ts:220; extension/src/utils/proxy-manager.ts:264). Die SDK-Instanz, die Verträge öffnet und das Provider-Fenster hält, läuft in server/proxy, nicht auf der Maschine des Nutzers.

Deshalb kann auf diesen Plattformen keine versiegelte Sitzung existieren, und zwar aus Gründen der Architektur, nicht wegen noch nicht aufgewendeter Entwicklungsarbeit. Die versiegelte Sitzung läuft Client↔Provider, und ihre Zusage hängt daran, dass der Client-Endpunkt auf dem eigenen Gerät des Nutzers lebt. Ein Browser oder ein einfacher Proxy-Client spricht sein eigenes Protokoll (HTTPS CONNECT, SOCKS5, WireGuard) und kann die Engine des Netzwerks nicht ausführen, also betreibt der Operator das Gerät des Clients aus der Ferne und wirkt als Protokoll-Übersetzungspunkt zwischen beiden. Eine von diesem entfernten Gerät ausgehandelte Versiegelung begänne im Inneren des Operators — genau der Partei, die die Versiegelung blind machen soll. Die Implementierung hat diesen Tausch bewusst in Kauf genommen: Diese Flächen existieren der Bequemlichkeit halber auf Plattformen und Protokollen, die den vollen Client nicht beherbergen können, und die Docs dürfen sie nicht als versiegelt beschreiben.

Die Folgen, klar benannt:

  • Die Blindheit des Providers für deine Identität gilt weiterhin — der Provider erhält weiterhin nur IDs.
  • Die Blindheit des Operators existiert auf diesem Pfad in keiner Form. Der Operator terminiert die Proxy-Verbindung, erhält den Zielhost aus CONNECT und hält die Schlüssel des Clients.
  • Das gehostete Gerät installiert keinen DNS-Upgrade-Mux (server/proxy/proxy_device.go:556, SetUpgradeMuxSettings(nil)), das DoH im Tunnel des App-Pfades (§3.2) gilt hier also nicht.

Was auf diesem Pfad an die Stelle der Verschlüsselung tritt, ist Speicherdisziplin: Der Proxy-Datenpfad loggt auf clientgesteuerten Pfaden nichts, und ein Regressionstest nagelt das fest — proxy/socks5_nolog_test.go, TestClientDrivenTrafficNeverLogs, sichert null Log-Zeilen über fehlerhafte, übergroße und nicht anwählbare Fälle zu. Zwei ehrliche Vorbehalte: Das erklärte Motiv des Tests ist Log-Amplification-DoS und nicht Privatsphäre, und der Panic-Recovery-Handler loggt sehr wohl eine Client-Adresse (proxy/socks5_server.go:128-131). Siehe review/verified/PRIVACY-ENFORCEMENT.md §2.2.

3. Angreifer: ein feindseliger Provider

Im Geltungsbereich und vorausgesetzt. Bereitstellen kann jeder. Es gibt für die Teilnahme als Provider keine Attestierung, keinen Stake, keine Identitätsprüfung und keine Sybil-Abwehr — eine Suche über die Bäume server, connect und sdk nach sybil|kyc|attestation liefert nichts Relevantes. Ein Provider führt Open-Source-Code auf Hardware aus, die er kontrolliert, geh also davon aus, dass er einen veränderten Build ausführt.

Was er sieht. Alles, was ein Internetanbieter für den Datenverkehr sieht, den er trägt: Ziel-IP und -Port, Paketgrößen und Timing sowie TLS-SNI, wo der Client es im Klartext sendet. Er empfängt rohe IP-Pakete und wählt sie selbst an (connect/protocol/ip.proto:9-16; connect/ip.go LocalUserNat). Er sieht außerdem die client_id, die als SourceId des Vertrags mitgeführt wird (connect/protocol/transfer.pb.go:1779; server/controller/connect_controller.go:461-463) — eine Kennung pro Fensterplatz, die nur der Operator zu einem Konto auflösen kann, und nie die device_id, die auf dem Draht kein Feld hat. Ihre Lebensdauer und die etlichen Dinge, die sehr wohl über Sitzungen hinweg bestehen bleiben, stehen in §9.

Was er tun kann.

  • HTTP im Klartext: lesen und verändern. Port 80 wird standardmäßig unverändert an den Ausgang durchgereicht — HttpUpgradeUnencrypted ist der Standardmodus (connect/ip_mux_upgrade.go:38-47). Für jeden Datenverkehr, der nicht selbst verschlüsselt ist, steht ein Provider in derselben Position wie ein feindseliger WLAN-Hotspot. HTTPS schützt Anwendungsinhalte vor dem Provider; nichts in URnetwork legt eine zweite Schicht über das eigene TLS des Ziels.
  • Datenverkehr selektiv verwerfen, verzögern oder anhalten. Ein Provider, der Datenverkehr quittiert, aber keinen zurückgibt, wird als Blackhole markiert und entfernt (connect/ip_remote_multi_client.go:37-54), Verweigerung wird also erkannt und umfahren — aber die Erkennung ist statistisch, und der Datenverkehr, den er vor der Entfernung gesehen hat, war trotzdem gesehen.
  • Über die Leistung lügen, um Datenverkehr anzuziehen. Das Ranking nutzt gemessene Latenz und gemessenen Durchsatz (server/model/network_client_location_model.go:2430-2447), und gemessen wird beim Operator, es ist keine Selbstauskunft des Providers, das ist also begrenzt — aber es ist ein Eingang ins Ranking, keine Integritätskontrolle.
  • Flows innerhalb der eigenen Sicht korrelieren. Die Bindung pro Website pinnt eine Website auf einen Provider (connect/ip_remote_multi_client.go:1234-1240), ein einzelner Provider sieht also für die Websites, die er hält, einen zusammenhängenden Ausschnitt des Surfens eines Clients.

Was er ohne Hilfe nicht tun kann. Deine echte IP auf einem Relay-Pfad erfahren, dein Konto oder deine E-Mail-Adresse erfahren oder eine client_id einer Person zuordnen. Dafür braucht es den Operator (§5).

3.1 Was ip_security einschränkt und was nicht

Die ip_security-Schicht der Connect-Engine läuft am eigenen Ausgang des Providers und auf dem eigenen Gerät des Nutzers, nie zentral beim Operator — die Felder IngressSecurityPolicyGenerator/EgressSecurityPolicyGenerator des Operators existieren, werden aber nie zugewiesen oder gelesen (server/connect/resident.go:263-264; review/verified/PRIVACY-ENFORCEMENT.md §4.1). Sie blockiert BitTorrent- und Filesharing-Signaturen, opake nicht standardisierte Protokolle, in Reputationslisten geführte Ziele und Datenverkehr mit Angriffsmustern, und sie tut das auf bewusst dünner Grundlage: 5-Tupel plus ein Payload-Präfix, begrenzt auf 8 Pakete / 512 Bytes.

Die Reputationstabellen werden erzeugt, nicht von Hand gepflegt. connect/security und connect/blocker fassen öffentliche Threat-Intelligence-Feeds — abuse.ch Feodo Tracker (Botnetz-C2), Spamhaus DROP und DROPv6, Emerging Threats (kompromittierte IPs), Blocklist.de, CINS Army, TweetFeed, ViriBack, BruteForceBlocker — zu gepackten Bereichstabellen zusammen (46.789 IPv4-Bereiche im Snapshot vom 2026-08-05; der Header von connect/ip_security_cfaa_block.go führt die Feed-Liste und die Namensnennung). Die Release-Pipeline erzeugt beide Tabellen neu aus den Live-Feeds (build/all/run.sh, der Schritt CONNECT_IP_UPDATE), jedes Release liefert also einen aktuellen Snapshot aus, und ein aktueller Client blockiert den zuletzt gelisteten Adressraum. Der Snapshot altert mit dem installierten Build; die Listen aktualisieren sich pro Release, nicht over the air.

Die Grenze des Payload-Präfixes steht in connect/ip_security_dmca.go:117-119, wobei der Servername aus dem Flow-Schlüssel gelöscht wird (:366,386,424) und Zähler auf (version, protocol, port) geführt werden, wobei das IP-Feld nie aktiviert ist (connect/ip_security.go:554,604-624).

Sie ist eine Schutzmaßnahme für ehrliche Provider, keine Sicherheitsmaßnahme gegen feindselige. Sie begrenzt, was die Verbindung eines Providers nach außen tragen wird. Gegen das, was ein Provider mit dem Datenverkehr tut, den er sehr wohl trägt, richtet sie nichts aus, sie läuft in einem Prozess, der dem Provider gehört, und ein veränderter Build kann sie abschalten. Lies sie nicht als Schranke für einen feindseligen Provider.

Ein Meldedetail gehört hierher, weil es ein Metadatenkanal ist. Ein Treffer auf eine BitTorrent-Signatur gibt Incident zurück, was ReportAbuse aufruft (connect/ip.go:4481-4487) und einen PeerAudit-Control-Frame an den Operator sendet, der die Geräte-ID des Peers und einen Boolean trägt — kein Ziel, keine Domain, keine Inhalte (connect/transfer.go:870-876,6653-6683). Der Operator hat heute keinen Handler für diesen Nachrichtentyp, beim Empfang wird also nichts gespeichert (server/controller/connect_controller.go:236-250; ein Grep über das gesamte Repo nach PeerAudit in server liefert null Treffer). Das ist eine Disziplin, die eine einzige case-Anweisung von der Änderung entfernt ist. Verwürfe opak verschlüsselten Datenverkehrs sind still, ohne Meldung (connect/ip_security_dmca.go:483-486).

3.2 DNS

Auf dem App- und SDK-Pfad wird einfaches DNS auf UDP/TCP 53 abgefangen und durch den Tunnel über DoH aufgelöst (connect/ip_mux_upgrade.go:120-148, standardmäßig installiert bei sdk/device_local.go:1120), zu Cloudflare, Google, Quad9 oder OpenDNS (connect/net_http_doh.go:150-155). Ein Provider sieht daher eine HTTPS-Verbindung zu einem öffentlichen Resolver, nicht die Anfrage. Zwei ehrliche Grenzen:

  • Ein DNS-Leck-Fenster beim Start ist im Quellcode dokumentiert. Solange das DoH im Tunnel noch aufgebaut wird, wird eine Anfrage im Wettlauf gegen einen Resolver über den Ausgang des lokalen Hosts geschickt, „um den Preis eines kurzen DNS-Lecks beim Start“ (connect/ip_mux_upgrade.go:120-126,78-92). Dein lokales Netzwerk und dein Internetanbieter können diese Anfragen sehen.
  • Der WireGuard-Endpunkt gibt dem Client einen festen öffentlichen Resolver mit, DNS = 1.1.1.1 (server/model/network_client_proxy_model.go:760), und Port 53 wird von der Sicherheitsrichtlinie ungeprüft durchgelassen (connect/ip_security_cfaa.go:113). Auf diesem Pfad durchqueren DNS-Anfragen den Provider als einfaches DNS, und der Provider kann sie lesen und beantworten.

Die vier DoH-Resolver sind Dritte, die Anfragen von der Ausgangsadresse eines Providers eintreffen sehen, ohne Verknüpfung zu deinem Konto. Das ist eine echte Abhängigkeit, und sie lässt sich nicht auf null konfigurieren: Irgendjemand muss DNS beantworten.

4. Angreifer: ein Netzbeobachter

Vier Positionen, von der schwächsten zur stärksten.

Dein lokales Netzwerk und dein Internetanbieter. Sie sehen, dass du dich über TLS/QUIC mit connect.bringyour.com verbindest oder mit einem Extender, dazu Größen und Zeitpunkte. Ziele im Inneren des Tunnels sehen sie nicht, außer im oben dokumentierten DNS-Fenster beim Start. Wo die Plattform blockiert ist, treten Extender als gewöhnliche Dienste auf glaubwürdigen Ports auf, und Clients rotieren Personas mit zufälliger Fragmentierung (connect/net_extender_profiles.go:14-30,50-57), und für Netze, aus denen nur DNS herauskommt, gibt es einen DNS-förmigen Transport (connect/transport_pt.go:18-45). Das sind Mechanismen zum Überwinden lokaler und regionaler Firewalls, im Rang unter den direkten Transporten (connect/transport.go:541-546). Sie sind keine Abwehr gegen Verkehrsanalyse und sollten nie als solche beschrieben werden.

Wer einen Extender betreibt. Betreiben kann ihn jeder, die App akzeptiert eine von Hand eingegebene IP, und standardmäßig ist keine Signatur nötig (connect/extender/extender.go:57-61). Ein Extender ist der TCP-Peer, sieht also die IP des sich verbindenden Nutzers, und er kann nicht entschlüsseln, was er weiterleitet, weil die TLS-Sitzung durch ihn hindurch zur Plattform läuft, statt auf ihm zu terminieren (connect/net_extender.go:51-55). Die Extender-Etappe setzt damit einen Beobachter deiner Adresse im ersten Hop in den Pfad, der nicht der Operator ist. Das ist der Tausch, den das Design eingeht, um dich durch eine Sperre zu bringen, und es lohnt sich, über seine Größe genau zu sein: Die Extender-Etappe fügt keine Verschlüsselung und keine Anonymität hinzu — sie erfährt, was dein Internetanbieter ohnehin weiß, und nicht mehr. Im ausgelieferten Client werden die direkten Transporte zuerst im Wettlauf probiert, und die Extender-Dialer weiten sich aus, wenn jene scheitern (connect/net_http.go:675); ein Client, der mit eigenen Extendern konfiguriert ist, nutzt sie als einzige Route (connect/net_http.go:357,469).

Ein Beobachter eines Endes. Nur die Client-Seite zu beobachten ergibt „dieser Nutzer ist mit URnetwork verbunden“ und ein Byte-/Timing-Profil. Nur den Ausgang eines Providers zu beobachten ergibt Ziele und ein Byte-/Timing-Profil ohne angehängte Identität.

Ein Beobachter beider Enden. Siehe §8. Das ist der Angreifer, den URnetwork nicht abwehrt.

5. Angreifer: der Operator

Der Operator authentifiziert Clients, wählt Provider aus und bewertet sie, verfasst Verträge, verteilt öffentliche Schlüssel und trägt die Pakete auf den Relay-Pfaden. Geh für diesen Abschnitt davon aus, dass er feindselig ist oder gezwungen wird.

Was er ohne jeden zusätzlichen Aufwand sieht: dein Konto; deine Adresse im Moment des Verbindens; eine Stadt/Region/ein Land, aus dieser Adresse per lokalem Datenbank-Lookup abgeleitet — server/ip.go:201 öffnet eine mitgelieferte mmdb/ip-ipinfo.mmdb von der Festplatte, und ip.go macht keinerlei Netzwerkaufrufe, eine Adresse wird also nie an einen Geolokalisierungsdienst übergeben — pro Verbindung gespeichert (server/model/network_client_location_model.go:1392-1414); welche Provider dich bedient haben; Bytezahlen; und auf dem Standardpfad die Zieladressen und Paketbytes im Inneren des Tunnels — obwohl die normalerweise bereits im HTTPS des Ziels selbst stecken.

Was er speichert. Verbindungs- und Auth-Datensätze halten einen Einweg-Hash mit geheimem Schlüssel über den umgebenden /29-Block (IPv4) bzw. /56-Block (IPv6) statt der Adresse (server/ip.go:46-67). Eine Präzisierung, auf die es einem Auditor ankommt: Es ist ein einzelner prozessweiter Pepper aus dem Vault, einmal memoisiert, ohne Salt pro Zeile und ohne jeden Rotationsmechanismus im Repo (server/ip.go:40-44); der IPv4-Schlüsselraum umfasst 2^29 Blöcke, wer den Pepper hält, kann ihn also per Brute Force umkehren. Die Geheimhaltung des Peppers ist die ganze Sicherheitseigenschaft. Das /verify-Subsystem nutzt für IPv6 /48, nicht /56 (server/ip.go:74-85; server/model/verify_model.go:111). Der Quellport wird im Klartext neben dem Hash gespeichert (server/db_migrations.go:1934). Die Audit-Zeile bei der Kontoerstellung verzeichnet den gepepperten Hash und den Port, nie das rohe ip:port (server/model/network_model.go:961-975), und Audit-Zeilen werden nach 180 Tagen entfernt (server/model/audit_model.go:984, eingeplant bei server/taskworker/taskworker.go:57,253).

Was gar nicht erst existiert, um gespeichert zu werden. Kein Ziel, kein Host, keine URL, kein SNI, kein Port und keine Domain taucht irgendwo im Transferregister auf — ein Grep der vollständigen Migrationsdatei nach diesen Begriffen liefert null Treffer (review/verified/PRIVACY-ENFORCEMENT.md §1.2). HTTP-Logging läuft durch eine Allowlist aus genau fünf Headern (server/http_log.go:15-29), festgenagelt per Test. Keine Prometheus-Metrik trägt eine IP, einen Host, ein Ziel, einen Port oder eine Client-ID; von 39 deklarierten Metriken sind nur drei mit Labels versehen, und jedes Label ist ein begrenztes Enum (review/verified/PRIVACY-ENFORCEMENT.md §2.5). Log-Uploads aus dem Support-Tab werden nach io.Discard abgeleitet (server/controller/log_file_controller.go:13-16,76); nur Metadaten bleiben bestehen.

5.1 Wie lange etwas davon aufbewahrt wird und wohin es geht

Die Aufbewahrung wird von eingeplanten Abräumläufen in server/taskworker durchgesetzt, nicht von einer Richtlinie. Die Fenster unten sind aus den Konstanten abgelesen, die diese Läufe aufrufen, und sie sind kürzer, als die Datenschutzerklärung — die überhaupt keine Aufbewahrungsfrist nennt — einen Leser erwarten ließe.

DatenAufbewahrungsdauerDurchgesetzt bei
Verbindungszeilen und die pro Verbindung daran hängenden Stadt/Region/Land, Latenz und Geschwindigkeit8 Stundentaskworker/work/network_client_work.go:84; das Löschen kaskadiert im selben Statement auf network_client_location (model/network_client_model.go:2295-2320)
Ein Top-Level-Client untätig (kein Auth, kein Connect) → als inaktiv markiert30 TageTopLevelClientIdleExpiration (model/network_client_model.go:2289)
Ein inaktiver Client → harte Löschung, mit Kaskaden+30 TageNetworkClientReapAfterDeactivate (:2269)
Ein ungenutztes Gerät also, Ende zu Ende~60 Tagedie beiden verkettet
Abgeschlossene Verträge7 Tagetaskworker/work/subscription_work.go:161
Audit-Zeilen180 Tagemodel/audit_model.go:984

Zwei Hinweise, die ein Auditor haben sollte. Das Untätigkeitsfenster wurde am 2026-07-18 von 90 Tagen auf 30 verschärft; die Konstante trägt ihre eigene Begründung, und ein veralteter Kommentar bei taskworker/work/network_client_work.go:83 sagt noch 90 — das ist die wahrscheinlichste Quelle dieser Zahl, falls sie dir anderswo begegnet. Und RemoveLocationLookupResults ist in jedem Zyklus eingeplant, aber ein No-op — sein Rumpf und sein Model-Aufruf sind beide auskommentiert, und eine solche Tabelle existiert nicht. Es ist ein funktionsloses Überbleibsel, keine Daten, die das Abräumen übersieht; der Standort pro Verbindung wird mit der Verbindungszeile oben gelöscht.

Die Geolokalisierung verlässt die Maschine nie. Der Stadt/Region/Land-Lookup liest eine mitgelieferte mmdb/ip-ipinfo.mmdb von der lokalen Festplatte (server/ip.go:201), und ip.go enthält keinen HTTP-Client und macht keine Netzwerkaufrufe. Keine Adresse wird an einen Geolokalisierungsdienst geschickt, und keine persönlichen Informationen werden an irgendeinen Dritten weitergegeben. Die einzigen Dritten in der Parteien-Tabelle dieses Dokuments halten Daten, die der Nutzer ihnen selbst aushändigt, indem er diesen Weg wählt: die DoH-Resolver, die von den eigenen DNS-Anfragen eines Nutzers durch den Tunnel erreicht werden, und die Zahlungsdienstleister, bei denen sich ein Nutzer registriert, um abgerechnet zu werden.

Bekannte Lücken in der Haltung „wir loggen deine Adresse nicht“, unabhängig von der Verbosität. server/http.go:415,455 konstruieren http.Server ohne ErrorLog, die Go-Standardbibliothek schreibt also bei jeder halboffenen Verbindung http: TLS handshake error from auf das stderr der Produktion — genau das Loch, das proxy/http.go:176,250 mit ErrorLog: discardLog schließt. Vom Client geliefertes SNI wird auf ERROR geloggt (server/tls.go:98,195), eine rohe Aufruferadresse bei server/proxy/proxy_device.go:274 und die WireGuard-Peer-Identität bei server/proxy/server.go:699. Bis das behoben ist, ist „wir loggen deine IP nie“ keine Aussage, die URnetwork treffen kann, und dieses Dokument trifft sie nicht. Vollständige Aufstellung: review/verified/PRIVACY-ENFORCEMENT.md §2.4.

Was der Operator kontrolliert und leicht zu übersehen ist. Die Provider-Discovery liegt vollständig auf der Seite des Operators: FindProviders2 ist der einzige aktive Endpunkt (server/api/api.go:67), die Plattform bewertet und sortiert die Kandidaten (server/model/network_client_location_model.go:2250-2254,2430-2447), und der Client nimmt die sortierte Auswahl, die er bekommt. Die einzige Steuerung auf Verbraucherseite ist eine Sperrliste für Standorte (server/api/api.go:73-75). Ein Client hat keine Möglichkeit zu prüfen, ob die ihm angebotenen Provider voneinander oder vom Operator unabhängig sind.

6. Vom Operator betriebene Provider und Kollusion zwischen Operator und Provider

6.1 Kann der Operator Provider betreiben?

Ja, und nichts im System kennzeichnet oder verhindert das. Bereitstellen steht jedem offen, ohne Attestierung und ohne Stake, und im Code gibt es nirgends ein Konzept von Erstanbieter-, offiziellen oder dem Operator gehörenden Providern — Suchen über server, connect und sdk nach einer solchen Vorstellung liefern nichts. Ein vom Operator betriebener Provider wäre von dem eines Mitglieds nicht zu unterscheiden.

Zusammen mit der Feststellung aus §5, dass der Operator die Provider-Auswahl sortiert und zurückgibt, heißt das: Der Operator kann im Prinzip eigene Provider in das Fenster eines Nutzers setzen. Es gibt keine clientseitige Vielfaltsprüfung, keine Attestierung der Unabhängigkeit und keine veröffentlichte Vermessung der Flotte durch eine außenstehende Partei. Die vorhandenen Gegengewichte sind echt, aber nur teilweise wirksam: Das Fenster hält mehrere Provider zugleich — ein Qualitätsfenster aus 2–6 (harte Obergrenze 12) und ein Geschwindigkeitsfenster aus 1–2 (harte Obergrenze 4), beide aktiv im Standardprofil (connect/ip_remote_multi_client.go:138-158) —, und Provider werden bei ungesunden Statistiken, erkanntem Blackhole, ausbleibendem Ping und der Lebensdauerrotation pro Kanal entfernt (:37-54, :610-616). Das begrenzt, wie viel ein einzelner Provider sieht. Es begrenzt keine Provider-Auswahl, die von einer einzigen Partei getroffen wird.

6.2 Was Operator und Provider gemeinsam rekonstruieren, je Modus

Geh davon aus, dass der Operator und einer oder mehrere Provider in deinem Fenster teilen, was sie jeweils halten.

Relay, StandardRelay, versiegeltDirekt
Deine Identität (Konto, E-Mail/Wallet/Zahlung)OperatorOperatorOperator
Deine echte IP zum VerbindungszeitpunktOperatorOperatorOperator und der Provider direkt
Ziele, die du besucht hastOperator (innere Pakete) und Providernur der ProviderProvider
Verknüpfung zwischen beidemtrivial — eine Partei hält bereits beidesder Vertrag: audit_contract_event paart Client- und Provider-Identität je Vertrag (server/db_migrations.go:234-251)trivial
Ergebnisvollständige Zuordnung des Surfens für alles, was die kolludierenden Provider getragen habenvollständige Zuordnung des Surfens für alles, was die kolludierenden Provider getragen habenvollständige Zuordnung des Surfens, und der Provider kennt deine Adresse ohne fremde Hilfe

Der ehrliche Schluss: Die versiegelte Sitzung schützt nicht gegen Kollusion zwischen Operator und Provider. Sie nimmt dem Operator die eigenständige Fähigkeit, deinen Datenverkehr zu lesen; sie hindert einen Provider, der deine Ziele ohnehin sieht, nicht daran, sie einem Operator zu geben, der ohnehin weiß, wer du bist. Der Vertragsdatensatz, der beides verbindet, ist kein Leck — er ist die Abrechnung, von der das Netzwerk lebt.

Was die Versiegelung gegen diesen Angreifer sehr wohl erkauft, ist eine Begrenzung des Geltungsbereichs: Ist sie an, enthält die eigene Sicht des Operators keine Ziele, Kollusion erfordert also die Mitwirkung genau der Provider, die genau diesen Datenverkehr getragen haben, und zwar zu der Zeit, als er getragen wurde, statt einer Abfrage gegen Datensätze, die der Operator allein hält. Das ist ein bedeutsamer Unterschied im Aufwand und darin, was eine erzwungene Herausgabe rückwirkend erreichen kann (§10). Es ist keine Immunität, und keine Anordnung dreier Parteien, in der eine die anderen auswählt, gewährt Immunität.

Die strukturelle Antwort des Designs, die nicht ausgerollt ist. Das Wire-Protokoll und der Client unterstützen das Verketten weiterer Provider-Zwischenstationen — MaxMultihopLength = 8 (connect/connect.go:13,214-233), IntermediaryIds an CreateContract (connect/protocol/transfer.pb.go:1516), Annahme im Client bei connect/ip_remote_multi_client_api.go:296-312, Server-Verkabelung bei server/controller/connect_controller.go:560. Der aktive Discovery-Endpunkt füllt das Feld nie — FindProvidersProvider wird an genau zwei Stellen konstruiert, und keine davon setzt es (server/model/network_client_location_model.go:3186-3190,3296-3303) —, das ausgelieferte Netzwerk vergibt also Ketten der Länge eins. Provider-Verkettung ist eine Fähigkeit des Protokolls, keine Eigenschaft des Deployments, und die Zahl 8 als Hop-Zahl zu zitieren wäre irreführend.

7. Provider-Identitätsschlüssel und ob der Operator einen austauschen kann

Das ist der Abschnitt, den anzugreifen sich am meisten lohnt, denn die Antwort ist unbequem, und der Quellcode sagt das selbst.

7.1 Ausgabe und Bindung

Jeder connect.Client — ein Provider-Client und jeder der Fenster-Clients des Nutzers — hält ein Ed25519-Schlüsselpaar, das im Prozess von ClientKeyManager erzeugt wird (connect/transfer_key.go:76-100, der einzige Aufruf von ed25519.GenerateKey in den drei Bäumen). Es ist langlebig, wo ein Seed persistiert und wieder geladen wird, was der Provider-Client tut, und pro Prozess frisch, wo keiner persistiert wird, was die Fenster-Clients tun; §9 behandelt den Unterschied und warum er zählt. Die private Hälfte verlässt den Prozess nie; ein Seed falscher Größe ist ein harter Konstruktionsfehler und keine stille neue Identität (:84-94). Die öffentliche Hälfte wird der Plattform in einer ClientKey-Control-Nachricht bekannt gegeben (connect/protocol/transfer.proto:501-513) und serverseitig in Redis unter ckey_ gespeichert, ohne Ablauf — Redis ist die Quelle der Wahrheit, eine SQL-Tabelle gibt es nicht (server/model/network_client_key_model.go:14-59).

Die Bindung ist damit client_id → public key, und der Operator gibt die client_id aus und hält die Zuordnung. Es gibt keinen externen Anker: IDs werden nicht aus Schlüsseln abgeleitet, und es gibt weder ein Transparency Log noch eine DHT. Beide Alternativen sind im Design als erwogen und zurückgestellt vermerkt (connect/DESIGNNOTES.md §3.7, „Deferred alternatives“).

7.2 Rotation und Widerruf

Erneutes Veröffentlichen überschreibt — der eigene Kommentar von SetClientKey lautet „nach client_id geschlüsselt (Rotation überschreibt)“ (server/controller/connect_controller.go:810-813). Der Eintrag wird gelöscht, wenn die Client-ID abgeräumt wird (network_client_key_model.go:61-71, aufgerufen aus RemoveDisconnectedNetworkClients). Es gibt keine planmäßige Rotation, keinen Ablauf und keine Widerrufsliste. Ein Provider-Client persistiert sein Schlüsselmaterial und lädt es wieder, seine Identität ist über Neustarts hinweg also stabil (sdk/device_local.go:2874-2889,2926-2935; sdk/local_state.go:418-461) — und unter Android bewusst auch über eine automatische Abmelde-Bereinigung hinweg stabil (§9.2). Ein Client ohne persistierten Seed erzeugt bei jedem Prozessstart eine neue Identität, und genau das tun die Fenster-Clients des Nutzers. Schlüsselwechsel mitten in der Sitzung werden abgelehnt: SetPeerClientPublicKey gilt nach dem Prinzip erster Schreibzugriff gewinnt, und ein späterer, abweichender Schlüssel wird geloggt und ignoriert (connect/transfer_encrypt.go:1787-1816).

7.3 Verifikation und die zwei Verteidigungen

  • Verteidigung 1, signierte Zertifikatsbindung. Der Provider signiert seine ephemere TLS-Zertifikatskette mit seinem Ed25519-Schlüssel und veröffentlicht beides (connect/protocol/transfer.proto:486-498). Die Plattform hängt die Kette, die Signatur und den öffentlichen Schlüssel des Providers an jeden Vertrag, der diesen Provider benennt (:340-368, :388-406). Der Client lässt die Kette nur zu, wenn die Signatur unter dem öffentlichen Schlüssel des Providers verifiziert, und prüft dann das im Handshake vorgelegte Zertifikat gegen die zugelassene Kette (connect/transfer.go:3930-3934,4006-4018).
  • Verteidigung 2, Identitätsnachweis im Handshake. Nach dem TLS-Handshake signiert jede Seite die RFC-5705-Exporter-Ausgabe unter dem Label urnetwork-sequence-identity-proof und sendet sie als EncryptedControl (connect/protocol/transfer.proto:460-476). Das AEAD wird dem Wrap-Pfad vorenthalten, bis der Nachweis des Peers verifiziert (connect/transfer_encrypt.go:1492-1498, :1719-1756), ein MITM, der TLS auf der einen Etappe terminiert und auf der anderen neu handshaked, erzeugt also nicht zusammenpassende Exporter und kann die Signatur nicht fälschen.

Beide Verteidigungen prüfen gegen peerClientPublicKey — und dieser Wert stammt aus dem von der Plattform verfassten Vertrag (connect/transfer.go:6119-6129; connect/transfer_encrypt.go:1780-1791).

7.4 Kann der Operator einen Provider-Schlüssel austauschen? Heute: ja.

Klar gesagt: Ein Operator, der das Zertifikat, die Zertifikatssignatur und destination_client_public_key im Gleichschritt austauscht, überwindet beide Verteidigungen und kann sich als Maschine in die Mitte einer versiegelten Sitzung setzen. Die Design Notes sagen das in den eigenen Worten des Repositorys (connect/DESIGNNOTES.md §3.7): „Verbleibendes Loch: Eine Plattform, die Zertifikat + Signatur + destination_client_public_key im Gleichschritt austauscht, gewinnt auf der Seite der Zertifikatsbindung weiterhin; der abschließende Zug besteht darin, SetPeerClientPublicKey aus dem OOB-Lookup statt aus dem Vertrag zu speisen. Diese Gegenprüfung ist derzeit nur ein Log-Eintrag.“

Der vorgesehene abschließende Zug existiert und ist verkabelt, handelt aber nicht. Eine nicht authentifizierte Route GET /key/ liefert den veröffentlichten Schlüssel aus (server/api/api.go:123-124; server/controller/connect_controller.go:826-849), und das ausgelieferte SDK installiert dagegen für jeden Fenster-Client und jeden Provider-Client einen Fetcher pro Sitzung (sdk/device_local_provider.go:398-420, erreicht von sdk/device_local.go:3434 und :90). Beim ersten Setzen des Schlüssels holt der Client den Schlüssel des Peers out of band und vergleicht. Bei Abweichung loggt er:

CONTRACT vs FETCHED peer client public key MISMATCH for <peerId>
— possible platform MITM (today: log only, contract value still trusted)

(connect/transfer_encrypt.go:1871-1876). Dem vom Vertrag gelieferten Schlüssel wird weiterhin vertraut (:520-525, :1839-1849). Vom Loggen zum Ablehnen überzugehen wird sowohl im Kommentar der Einstellungen als auch in den Design Notes als nächster Härtungsschritt beschrieben — das ist Designabsicht, keine ausgelieferte Eigenschaft.

Drei weitere Präzisierungen, die ein Auditor im Kopf behalten sollte:

  1. Der Out-of-Band-Kanal ist out of band gegenüber der Vertrags-Pipeline, nicht gegenüber dem Operator. Der Abruf geht an denselben API-Host des Operators (sdk/device_local_provider.go:404) und liest dasselbe Redis, das der Verfasser des Vertrags beschreibt. Selbst nachdem aus Loggen Ablehnen geworden ist, erwischt die Prüfung einen Operator, der zwischen zwei seiner eigenen Kanäle inkonsistent ist, nicht einen, der konsistent ist. Das zu schließen erfordert einen Anker, den der Operator nicht kontrolliert — aus Schlüsseln abgeleitete IDs oder ein Transparency Log, beide als zurückgestellt vermerkt.
  2. Weglassen ist so wirksam wie Austauschen, und leiser. Die Zertifikatsprüfung wird ohne Verriegelung übersprungen, wenn die vertraute Menge leer ist, auch dann, wenn Verträge ein leeres ProvideTlsCertificate tragen (connect/transfer.go:4010-4016), und der Identitätsnachweis lässt sich überhaupt nicht verifizieren, wenn der öffentliche Schlüssel des Peers fehlt (connect/transfer_encrypt.go:1706-1708). So oder so wird der Cipher nie nutzbar, und der Datenverkehr läuft im Klartext (§2.2), ohne für den Nutzer sichtbares Signal. Ein Operator, der den Datenverkehr eines bestimmten Nutzers lesen will, während der Schalter an ist, muss nichts fälschen; er muss ein Feld weglassen.
  3. Auch die Echtheit des Vertrags wurzelt im Operator. Ein Provider verifiziert das HMAC des Vertrags mit einem geheimen Bereitstellungsschlüssel, den der Provider erzeugt und der Plattform bekannt gibt (server/controller/connect_controller.go:768-779; sdk/device_local.go:2866-2872). Der Operator hält eine Kopie, und genau das erlaubt ihm überhaupt erst, Verträge zu verfassen. Das ist von einer Abrechnungsinstanz zu erwarten, aber es heißt, dass „der Vertrag ist echt“ keine vom Operator unabhängige Aussage ist.

Was das untergräbt und was nicht. Es rührt nicht an die Blindheit des Providers für deine Identität, die von keiner Schlüsselverteilung abhängt. Es heißt aber, dass die Blindheit des Operators für Inhalte auf dem versiegelten Pfad derzeit darauf ruht, dass der Operator Schlüssel ehrlich verteilt — was eine Eigenschaft der Richtlinie ist, mit einem kryptografischen Mechanismus dahinter, der den größten Teil des Weges gebaut ist, und noch keine Eigenschaft, die gegen einen feindseligen Operator hält. Jedes URnetwork-Dokument, das die versiegelte Sitzung so beschreibt, als mache sie die Blindheit des Operators bedingungslos, übertreibt, und dieses Dokument ersetzt eine solche Formulierung.

8. Timing- und Verkehrskorrelation und der globale passive Beobachter

8.1 Es gibt keine Abwehr gegen Verkehrsanalyse. Keine.

URnetwork liefert auf Nutzerdaten keinen Cover-Traffic, kein Padding, kein Mixing, keine Batching-Verzögerung und kein Traffic Shaping aus. Das wurde erschöpfend über connect, sdk und server geprüft:

  • Nirgends existiert ein Generator für Chaff, Köder, Dummy- oder Cover-Traffic.
  • Das AEAD ist konstruktionsbedingt längenerhaltend: Die Chiffretextlänge ist Nonce + Klartext + Tag (connect/transfer_encrypt.go:305,340). Kein Protobuf in connect/protocol/ trägt ein Padding-Feld, und das Framing ist ein nacktes 4-Byte-Längenpräfix (connect/message_framer.go:28-31).
  • Das Zusammenfassen auf der Sendeseite ist ausdrücklich verzögerungsfrei: „Es gibt keine Batching-Wartezeit: Die Sequenz nimmt ein zweites Pack nur, wenn es bereits in der Warteschlange steht“ (connect/transfer.go:387-392). Jedes jitter in den Bäumen ist Reconnect-Backoff oder Streuung der Kanallebensdauer, kein Traffic Shaping. Die Ack-Kompression (connect/transfer.go:272) verzögert allein Quittungen, um das Nachrichtenvolumen im Relay zu senken.
  • Die einzige Stelle, an der Nutzerdatenverkehr überhaupt getaktet wird, ist die Obergrenze WritePacketsPerSecond des DNS-förmigen Transports (connect/transport_pt.go:63,232-246), die es gibt, um resolverfreundlich zu sein, und die nur für den am wenigsten bevorzugten Transportmodus gilt. Seine „Pump“-Anfragen im Leerlauf haben eine von datentragenden unterscheidbare Länge, er verbirgt also weder Größe noch Rate.

Daher: Ein Angreifer, der den Datenverkehr beobachtet, der in dein Gerät eintritt, und den Datenverkehr, der die Provider verlässt, die ihn tragen, kann beides über Größe und Timing korrelieren. Gegen diesen Angreifer schützt URnetwork nicht. Das ist dieselbe Aussage, die Tor über Ende-zu-Ende-Korrelation trifft, und sie gilt hier mit weniger Spielraum, denn URnetworks Designziel ist niedrige Latenz — und genau diese Eigenschaft macht Korrelation leichter. Ein latenzbegrenzter Pfad aus vier Etappen ist ein bewusster Tausch gegen die zusätzliche Verzögerung eines Mixnets, und das ist die Seite dieses Tauschs, die der Nutzer bezahlt. Die Extender-Etappe mitzuzählen ändert daran nichts: Dieser Hop leitet die verschlüsselte Sitzung an den Operator weiter, ohne sie zu terminieren, er fügt also keine Schicht hinzu, die ein Beobachter abziehen könnte, und keine Verzögerung, in der er dich verlieren könnte.

Zwei Dinge werden bisweilen für Abwehrmaßnahmen gehalten und sind keine. Extender und die geformten Transporte (connect/net_resilient.go:112-215, connect/transport_pt.go:18-45) zielen auf DPI-gestützte Sperren, und die Resilienz-Schicht schaltet sich selbst ab, sobald der Stream steht (net_resilient.go:102,146-161) — sie rührt Records der Datenphase nie an. TLS-Session-Ticket-Caches werden bewusst nicht zwischen Ausgangspfaden geteilt, damit ein Server sie nicht über ein eingelöstes Ticket verknüpfen kann (connect/net_tls.go:38-42,99-103); das ist Unverknüpfbarkeit von Tickets, keine Verkehrsanalyse.

Nichts im Quellcode erkennt Verkehrskorrelation als Einschränkung an: Eine Suche nach traffic analysis, timing correlation, traffic correlation und global adversary über alle drei Bäume liefert null Treffer. Das Bedrohungsmodell im Repo (connect/DESIGNNOTES.md §3.7) dreht sich ausschließlich um den MITM durch den Operator. Dieses Dokument ist die erste Stelle, an der die Lücke aufgeschrieben ist.

8.2 Das Mehr-Provider-Fenster ist kein Mechanismus gegen Korrelation

Der Datenverkehr tritt normalerweise über mehrere Provider zugleich aus — üblicherweise drei bis acht über die beiden Fenster — und die Bindung pro Website hält eine bestimmte Website auf einem Provider (connect/ip_remote_multi_client.go:138-158,1234-1240). Das begrenzt tatsächlich, wie viel ein einzelner Exit sieht. Aber die Designbegründung des Fensters im Quellcode ist durchweg Zuverlässigkeit — Abfederung schlechter Ziele, gesundheitsgewichtete Größenanpassung, Blackhole-Erkennung (:27-52) —, und der eine Kommentar, der „geringere Bindung ist privater“ lautet, sitzt auf ClientAffinityTimeout, einem Regler, der auskommentiert ist (:587-591, und noch einmal in den Standardwerten bei :220), während DestinationAffinity aus Gründen der Zuverlässigkeit auf true ausgeliefert wird (:270). Plurale Exit-Identität ist ein echter Vorteil, und es ist fair, ihn zu behaupten; sie als geplante Abwehr gegen Korrelation zu behaupten, wird vom Code nicht gestützt.

8.3 Der globale passive Beobachter: außerhalb des Geltungsbereichs

Ein globaler passiver Angreifer — einer, der einen großen Teil der Internetverbindungen gleichzeitig beobachten kann — liegt außerhalb des Geltungsbereichs, und URnetwork bietet keine Abwehr gegen ihn. Ohne Padding und ohne Cover-Traffic korreliert ein solcher Angreifer Flows über das Relay und die Provider hinweg direkt. Das ist dieselbe Position, die Tor einnimmt, und anders als Tor fügt URnetwork nicht einmal eine Verzögerung pro Hop hinzu, die die Kosten erhöhen würde.

Was wirklich anders ist und sich zu sagen lohnt, ohne es aufzublasen: Die Ausgangsmenge ist eine ständig wechselnde Population unabhängiger Privatanschlüsse und nicht die veröffentlichten Adressbereiche eines Operators, ein Angreifer muss also eine breitere und sich verschiebende Menge von Endpunkten beobachten, um einen einzelnen Nutzer abzudecken, und die Endpunkte lassen sich nicht aus einer Serverliste aufzählen. Das erhöht die Kosten. Es ändert das Ergebnis für einen Angreifer nicht, der beide Enden ohnehin sehen kann.

9. Gerätekennungen und Verknüpfbarkeit

9.1 Was ein Provider über dich beobachten kann

Die SourceId des Vertrags ist eine Client-ID pro Fensterplatz, nicht dein Gerät. StoredContract.SourceId ist die client_id des authentifizierten Aufrufers (server/controller/connect_controller.go:461-463; vom Provider geparst bei connect/transfer.go:6088-6106). Der Generator prägt für jeden Fenstereintrag eine frische Client-ID, ein frisches JWT und eine frische Instanz-ID und entfernt sie beim Abbau — der Quellcode sagt das direkt: „Der API-Generator prägt für jeden Fenstereintrag eine ephemere Plattform-Client-ID (mit frischer Instanz-ID) und entfernt sie beim Abbau“ (connect/ip_remote_multi_client_identity.go:10-15; geprägt bei connect/ip_remote_multi_client_api.go:326-345, freigegeben bei :410,473).

Aber diese Identitäten werden bewusst bis zu vier Stunden lang wiederverwendet, auf jedem Pfad — nicht nur auf gehosteten. Das Gerät installiert seinen eigenen lokalen Identitätsspeicher, sofern es nicht gehostet ist (sdk/device_local.go:1204-1208), und eine gespeicherte Fenster-Identität wird gegenüber demselben Ziel erneut abgespielt, bis sie veraltet: windowIdentitiesStaleAfter = 4 * time.Hour (sdk/window_identity_store.go:44-52). Der Zweck ist, die NAT-Flows eines Providers über einen Neustart hinweg am Leben zu halten (connect/ip_remote_multi_client_identity.go:17-23). Der Preis ist, dass ein Provider dieselbe SourceId innerhalb dieses Fensters über App-Neustarts hinweg von dir wieder auftauchen sehen kann. Sag vier Stunden, nicht „ephemer“.

Der Ed25519-Identitätsschlüssel des Clients ist auf dem Ausgangspfad pro Fenster-Client frisch. Die Einstellungen der Fenster-Clients werden aus einem frischen DefaultClientSettingsWithBufferSize gebaut (sdk/device_local.go:3434-3437), das keinen ClientKeySeed trägt (connect/transfer.go:166-183), also erzeugt ClientKeyManager ein neues Schlüsselpaar (connect/transfer_key.go:96). Der persistierte Fenster-Snapshot speichert nur Client-ID, JWT und Instanz-ID — keinen Schlüssel-Seed (sdk/window_identity_store.go:46-52), eine wiederhergestellte Identität veröffentlicht also einen neuen Schlüssel. Ist die Post-Quanten-Verschlüsselung aus, wird überhaupt kein Identitätsnachweis vorgelegt (connect/ip_remote_multi_client.go:9149-9157).

Die Quelladresse im Tunnel gilt pro Sitzung und hat wenig Entropie. Das SDK weist seinem TUN eine zufällige RFC1918-Adresse zu — „eine zufällige 10.x.y.h (RFC1918, DHCP-förmig)“ (sdk/device_local.go:566-571,1070-1090) —, erzeugt mit einem Host-Oktett in 2..254 über dem kleinsten kollisionsfreien 10.a.b.0/24 (connect/tun.go:323-348). Sie wird im Konstruktor berechnet und nie persistiert, sie ändert sich also mit jeder Sitzung. Der Provider sieht sie sehr wohl: Sie ist das Quellfeld jedes getunnelten Pakets, und der Provider führt seinen NAT-Zustand danach (connect/ip.go:742,1009-1020,1151). Rund 253 Werte sind ein schwacher Fingerabdruck pro Sitzung, keine sitzungsübergreifende Kennung.

9.2 Der Identitätsschlüssel der Provider-Rolle ist dauerhaft

Wenn du auch deine Verbindung teilst, hält dein Provider-Client ein langlebiges Ed25519-Schlüsselpaar, dessen Seed im lokalen Speicher persistiert wird (sdk/local_state.go:418-461, .device_local_key_material, JSON client_key_seed; angewandt bei sdk/device_local_key_material.go:54-70). Anders als die Fenster-Clients bleibt dieser Schlüssel bewusst über eine automatische Abmelde-Bereinigung hinweg erhalten — der Android-Kommentar ist eindeutig: „Einen veralteten oder unvollständigen Auth-Zustand löschen, OHNE die Geräteidentität zu rotieren. Das Identitätsschlüsselmaterial gilt geräteweit, nicht sitzungsweit“ (android/.../MainApplication.kt:636-647). Token-Erneuerung, Wiederherstellung nach unvollständiger Anmeldung und die erneute Anmeldung nach 30 Tagen Untätigkeit erzeugen daher eine neue client_id und eine neue device_id, während der öffentliche Ed25519-Schlüssel derselbe bleibt. Nur eine ausdrückliche Abmeldung durch den Nutzer rotiert ihn (sdk/local_state.go:639-644).

Dieser Schlüssel ist veröffentlicht, in jeden Vertrag eingeschlossen, der diesen Client als Ziel benennt (server/controller/connect_controller.go:413-415,468), und für jeden lesbar: GET /key/ ist von Haus aus nicht authentifiziert (server/api/api.go:123-124; server/controller/connect_controller.go:826-829). Jede Partei — nicht nur ein Provider, den du bedient hast — kann also jede Client-ID zu ihrem öffentlichen Schlüssel auflösen und die Client-IDs zusammenfassen, die sich einen teilen. Für ein Provider-Konto ist das eine dauerhafte Handhabe über Sitzungen, über client_id und über device_id hinweg. Das ist ein echter Preis des Bereitstellens, und er ist nirgendwo sonst im Korpus dokumentiert.

9.3 Beim Operator verknüpft sich alles

  • Eine client_id wird nie widerrufen. Der Kommentar im Code selbst: „client_ids sind global eindeutige Adressen, gleichbedeutend mit IPv6 / sie werden nach der Zuteilung nie widerrufen, um Sicherheits- und Audit-Datensätze zu bewahren“ (server/model/network_client_model.go:64-67). Ein Top-Level-Client wird nach 30 Tagen Untätigkeit deaktiviert (TopLevelClientIdleExpiration, :2289) und 30 Tage danach hart gelöscht (NetworkClientReapAfterDeactivate, :2269), kaskadierend auf die Gerätezeile und den Redis-ckey (:2537).
  • Eine device_id gilt pro Anmeldung, nicht pro Hardware oder pro Installation: Mit jedem Top-Level-Client wird eine frische geprägt (:332-352), und der Code vermerkt den daraus folgenden „Identitätswechsel (eine frische device_id pro Anmeldung)“ (:2280). Fenster-Clients erben sie (:356-380), und sie wird nie an einen Provider gesendet.
  • Das Netzwerk-Token ist ein JWT mit 24 Stunden Laufzeit (server/jwt/by_jwt.go:35-37,188), vom SDK bei Halbwertszeit erneuert — nach etwa 12 Stunden — mit Jitter (sdk/device_token_manager.go:107-124,165). Das Token zu erneuern rotiert die Client-ID nicht; derselbe Claim wird neu geprägt (server/model/network_client_model.go:102-125).
  • audit_contract_event verzeichnet Client- und Provider-Identität je Vertrag (server/db_migrations.go:234-251). client_reliability verbindet den Block-Hash mit geheimem Schlüssel direkt mit einer Client-ID, pro Block: Der ursprüngliche vierspaltige Primärschlüssel (server/db_migrations.go:2083-2106) wurde später auf (block_number, client_address_hash, client_id) reduziert (:2191-2195), und die aktive partitionierte Tabelle nutzt dieselben drei (server/model/network_client_reliability_partition_model.go:194-195), wobei network_id als Index-Payload überlebt (:63,631-634). Die Aufbewahrung beträgt 30 Tage (server/model/network_client_reliability_model.go:42,687-721; Tagespartitionen werden als Ganzes verworfen).
  • network_client.auth_time ist ein dauerhaftes Zuletzt-gesehen, 30 Tage aufbewahrt (server/model/network_client_model.go:2269,2289); Zeilen getrennter Verbindungen werden nach 8 Stunden abgeräumt (server/taskworker/work/network_client_work.go:85).
  • Kennungen auf Kontoebene, allein beim Operator: E-Mail-Adresse oder Telefonnummer im Klartext in network_user.user_auth, das vollständige JWT des Drittanbieter-IdP bei SSO-Anmeldungen, Kennungen im Klartext für jeden Anmeldeversuch in user_auth_attempt, Wallet-Adressen, Zahlungszeilen, eine dauerhafte Wer-hat-wen-eingeladen-Kante network_referral und audit_provider_event-Zeilen, die wörtliche Land-, Regions- und Stadt-Strings gegen network_id und device_id tragen, nicht an das 8-Stunden-Abräumen der Verbindungen gebunden (review/verified/PRIVACY-ENFORCEMENT.md §1.4).
  • User-Agent wird von der Allowlist aus fünf Headern geloggt (server/http_log.go:15-29). Er ist der eine Eintrag der fünf, der eine Fingerprinting-Fläche ist.

9.4 Die WireGuard-Brücke verknüpft dich über Provider hinweg

Jedem WireGuard-Client wird eine stabile private Tunnel-Adresse aus einem durchmischten Pool von 10.000.000 RFC1918-Adressen zugeteilt (server/model/network_client_proxy_model.go:1034,1038-1118), in die Konfiguration als Adresse der Schnittstelle geschrieben (:756-771), und sie wird vor dem Ausgang nicht normalisiert — das FIXME im Quellcode selbst lautet „derzeit wird die Client-IPv4 zu den Ausgangs-Providern durchgereicht / das kann es erlauben, eine einzelne Client-IPv4 über mehrere Provider hinweg zu verfolgen“ (proxy/wg.go:23-25). Es ist eine private Adresse, nicht deine echte IP, und die Ziel-Website sieht sie nie. Aber jeder Provider in deinem Fenster sieht dieselbe Quelladresse, kolludierende Provider können also erkennen, dass diese Flows zu einem Client gehören — genau die Eigenschaft, die das Mehr-Provider-Fenster ansonsten liefert. Zusammen mit dem festen Resolver 1.1.1.1 (§3.2) und dem Fehlen einer versiegelten Sitzung auf diesem Pfad ist die WireGuard-Brücke der am wenigsten private Weg, das Netzwerk zu nutzen. Die Apps und das SDK sind der privateste.

10. Rechtliche Anordnungen

Gerichtsstand. BringYour, Inc. ist eine Delaware-Kapitalgesellschaft (docs/legal/ur.xyz/terms.md:16,25) mit einer Postanschrift in San Francisco (docs/legal/terms.md:269-271). Die ur.io-Bedingungen legen anwendbares Recht und Gerichtsstand auf Harris County, Texas, fest (docs/legal/terms.md:315-321); die ur.xyz-Bedingungen legen Delaware fest (ur.xyz/terms.md:223-229). Alles davon liegt in den USA, und alles davon ist durch US-Zwangsverfahren erreichbar, einschließlich Verfahren, die mit einer Schweigeverfügung kommen. Die Bedingungen erklären, dass der Operator „Informationen überwachen und offenlegen darf, wo Gesetz oder behördliche Anordnung dies verlangen“ (docs/legal/terms.md:204-206).

Was eine Anordnung an den Operator erreicht. In absteigender Reihenfolge der deanonymisierenden Kraft: wer das Konto ist — E-Mail-Adresse oder Telefonnummer im Klartext, das JWT des Drittanbieter-IdP bei SSO-Anmeldungen, Wallet-Adressen und Zahlungsdatensätze, die über Stripe, Apple, Google Play oder eine tx_signature on-chain an eine echte Identität binden; wann es sich verbunden hat und aus welcher Stadt, pro Verbindung; welche Provider es genutzt hat und für wie viele Bytes; und einen Hash mit geheimem Schlüssel über den /29- oder /56-Block, aus dem es sich verbunden hat, dazu den Quellport im Klartext. Dritte halten mehr: Die in §1 genannten Zahlungsdienstleister halten Identität, die der Operator nie speichert, und eine Solana-tx_signature löst sich zu einer Wallet auf einer öffentlichen Chain auf.

Was eine Anordnung nicht erreicht, weil es nicht existiert. Das Transferregister hat nirgends ein Feld für Ziel, Host, URL, SNI, Port oder Domain (review/verified/PRIVACY-ENFORCEMENT.md §1.2). Hochgeladene Support-Logs werden beim Empfang verworfen. Es gibt keinen Browserverlauf herauszugeben, auf keinem Pfad, versiegelt oder nicht — das ist die stärkste strukturelle Eigenschaft in diesem Dokument, und sie gilt unabhängig davon, ob sich jemand anständig verhält.

Was eine Anordnung mit Blick nach vorn erreicht. Alles bisher Gesagte betrifft gespeicherte Datensätze. Eine Anordnung, die künftiges Verhalten erzwingt, ist etwas anderes, und §7.4 ist die Stelle, an der sie greift: Ein Operator, der gezwungen wird, einen bestimmten Nutzer abzuhören, könnte heute Provider-Schlüsselmaterial austauschen oder weglassen und den Datenverkehr dieses Nutzers lesen, selbst bei eingeschalteter Post-Quanten-Verschlüsselung, wobei das einzige Signal eine Zeile im Gerätelog wäre. Die versiegelte Sitzung erhöht die Kosten rückwirkender Offenlegung. Sie widersteht in ihrer heutigen Form keinem gezwungenen Operator mit Blick nach vorn.

Die Kontolöschung ist unvollständig. RemoveNetwork ist eine von Hand geschriebene Löschliste, keine Datenbank-Kaskade (server/model/account_model.go:108-260). Sie entfernt die network_user-Zeilen und ihre Auth-Datensätze (Passwort, SSO, Wallet, Seedphrase), die network-Zeile und den Eintrag im Namensindex, und sie plant eine Stripe-Kündigung ein. Sie löscht nicht account_payment, stripe_customer, apple_subscription_transaction, abgeschlossene solana_payment_intent-Zeilen, network_referral, Gerätezeilen oder Audit-Zeilen; die altern nach ihren eigenen Zeitplänen aus — Audit-Ereignisse nach 180 Tagen (server/model/audit_model.go:984,1018) — oder, bei abgeschlossenen On-Chain-Zahlungen, gar nicht (server/model/solana_payment_intent_model.go:386-400 löscht nur Intents mit einer tx_signature von null). Die Löschung schreibt außerdem eine Zeile: ein AuditEventTypeNetworkDeleted-Ereignis, geschlüsselt auf die gelöschte network_id (account_model.go:255-257). Das „Konto und die zugehörigen persönlichen Daten löschen“ der Datenschutzerklärung (docs/legal/privacy.md:69) ist weiter gefasst als das, was der Code tut.

Es gibt keinen Warrant Canary, keinen Transparenzbericht und kein veröffentlichtes Verfahren für Strafverfolgungsbehörden. Als fehlend verifiziert über docs/ und den Quellcode der ur.io-Website. Der einzige Hinweis auf ein Rechtsverfahren irgendwo verweist denjenigen, der Schriftstücke zustellt, darauf, den Delaware Registered Agent bei der Delaware Division of Corporations zu erfragen (docs/legal/ur.xyz/terms.md:262); [email protected] ist auf Schwachstellenmeldungen begrenzt (docs/legal/vdp.md:42), und [email protected] ist die allgemeine Adresse für vertragliche Mitteilungen. URnetworks eigene Vergleichsdokumente räumen das gegenüber Mitbewerbern, die eines veröffentlichen, bereits ein, und dieses Dokument wiederholt es, statt es abzumildern.

Die Datenschutzerklärung schweigt, wo sie konkret sein müsste. Sie erklärt, „alle persönlichen Informationen, die wir von und über Nutzer erheben, beschränken sich auf E-Mail-Adresse oder Telefonnummer“, und die Erhebung geschehe „direkt von dir, wenn du sie angibst“ (docs/legal/privacy.md:29-37).

Ein früherer Entwurf dieses Abschnitts nannte das eine zu knappe Beschreibung des Systems, mit der Begründung, der Code speichere mehr Kategorien, als die Erklärung benennt. Diese Einordnung wurde am 2026-08-09 als falsch zurückgezogen. Alles andere, was dieses Dokument inventarisiert, sind entweder Kontodaten, die der Nutzer durch Registrieren oder Bezahlen selbst erzeugt (den Stripe-Verknüpfungsschlüssel gibt es, weil jemand zugestimmt hat, abgerechnet zu werden), oder gar keine persönlichen Informationen: ein Quellport und ein Block-Hash mit geheimem Schlüssel, den der Operator nicht umkehrt. Ob ein Hash mit geheimem Schlüssel im Prinzip von seinem eigenen Inhaber invertiert werden könnte, ist ein echter technischer Vorbehalt — §5 benennt ihn, und die fehlende Rotation ist eine echte Schwäche —, aber ein Wert, den niemand nachschlägt, ist keine Kategorie persönlicher Informationen, die zu deklarieren die Erklärung versäumt hätte.

Was der Erklärung tatsächlich fehlt, ist etwas anderes und Engeres: keine Aufbewahrungsfrist, keine Aussage zum Logging und kein Abschnitt zu Strafverfolgung oder behördlicher Offenlegung. Die Zeichenketten retention, log, IP address, subpoena und legal process tauchen in ihr nicht auf. Das fällt ins Gewicht, weil die Aufbewahrungsdisziplin im Code existiert und stärker ist, als die Erklärung behauptet — §5.1 legt die verifizierten Fenster dar. Ein Nutzer, der die Erklärung heute liest, kann den Operator an keinem von ihnen festhalten.

11. Wogegen URnetwork nicht schützt

Drei Dinge gelten, und es lohnt sich, sie vor der folgenden Liste zu benennen, damit die Liste als Grenze gelesen wird und nicht als Urteil. Auf einem Relay-Pfad erfährt ein Provider nie, wer du bist, und keine Einstellung und kein Provider-Build ändert das (§2). Es gibt nirgends im Transferregister ein Feld für Ziel, Host, URL, SNI, Port oder Domain, es gibt also keinen Browserverlauf, den man erzwingen, leaken oder verkaufen könnte (§5, §10). Und jede einzelne dieser Aussagen ist in öffentlichem Quellcode nachlesbar, und deshalb kann dieses Dokument bei den eigenen Fehlern konkret werden.

Alles Folgende ist ein Versagen. Ohne Absicherungen benannt; jeder Punkt ist oben ausgeführt.

  1. Ende-zu-Ende-Timing- und -Verkehrskorrelation. Kein Padding, kein Cover-Traffic, kein Mixing. Ein Angreifer, der sowohl dein Zugangsnetz als auch die Provider beobachtet, die deinen Datenverkehr tragen, kann beides korrelieren. §8.1.
  2. Ein globaler passiver Angreifer. Vollständig außerhalb des Geltungsbereichs. §8.3.
  3. Kollusion zwischen Operator und Provider. Der Operator weiß, wer du bist, der Provider weiß, wohin du gegangen bist, und der Vertragsdatensatz verbindet beides. Die Versiegelung verengt, was der Operator allein hält; sie zerbricht die Verbindung nicht. §6.2.
  4. Ein gezwungener oder feindseliger Operator, der Provider-Schlüsselmaterial austauscht oder weglässt. Heute loggt die Gegenprüfung, die das erwischen würde, und macht weiter. §7.4.
  5. Stilles Downgrade der versiegelten Sitzung — BEI AUSGESCHALTETER Post-Quanten-Verschlüsselung. In diesem Modus fällt jeder Fehlschlag (Handshake, Identitätsnachweis, fehlender Schlüssel) auf Klartext zurück, ohne für den Nutzer sichtbaren Hinweis. Behoben für den Modus, der danach verlangt, am 2026-08-10: Ist der Schalter an, läuft der Client fail-closed und weigert sich, Anwendungsdaten im Klartext zu senden oder anzunehmen. Was in beiden Modi bestehen bleibt, ist die fehlende Anzeige — keine App zeigt, ob eine gegebene Verbindung versiegelt ist. §2.2.
  6. Ein feindseliger Provider, der unverschlüsselten Datenverkehr manipuliert. HTTP im Klartext wird unverändert durchgereicht; der Provider steht in der Position eines feindseligen Hotspots. §3.
  7. Kompromittierung des Endpunkts. Schadsoftware, ein kompromittiertes Betriebssystem, eine feindselige Browser-Erweiterung oder jeder, der dein entsperrtes Gerät in der Hand hat. Nichts an einem Netzwerkprodukt geht das an.
  8. Identifizierung auf der Zielseite. Cookies, Anmeldungen, Browser-Fingerprinting und Kontoverhalten identifizieren dich gegenüber den Websites, die du besuchst, unabhängig davon, wie die Pakete angekommen sind. Deine Exit-Adresse zu wechseln macht dich gegenüber einem Dienst, bei dem du dich anmeldest, nicht anonym.
  9. Identität über den Zahlungsweg. Karten- und App-Store-Wege legen deine Identität beim Zahlungsdienstleister ab, auch wenn der Operator sie nicht speichert. On-Chain-USDC hinterlässt eine öffentliche tx_signature.
  10. Sybil-Provider. Bereitstellen kann jeder, ohne Attestierung und ohne Stake. Eine einzelne Partei kann viele Provider betreiben, auch der Operator.
  11. Das Fehlen einer Versiegelung auf dem Browser- und dem Proxy-Pfad. Die Browser-Erweiterung hat keine Einstellung für Post-Quanten-Verschlüsselung, und auf diesen Pfaden betreibt der Operator den Client. §2.4.
  12. Verknüpfbarkeit über Provider hinweg auf der WireGuard-Brücke. Eine stabile Tunnel-Adresse über jeden Provider in deinem Fenster hinweg. §9.4.
  13. Eine dauerhafte Handhabe zur Identität für jeden, der auch bereitstellt. Der Ed25519-Schlüssel des Provider-Clients überlebt gewollt die Rotation von Client-ID und Geräte-ID, und GET /key/ ist nicht authentifiziert, jede Partei kann also Client-IDs zusammenfassen, die sich einen Schlüssel teilen. §9.2.
  14. Datenverkehr, den das lokale Netzwerk im DNS-Fenster beim Start sieht. Im Quellcode als hingenommener Preis dokumentiert. §3.2.
  15. Alles, was eine externe Prüfung gefunden hätte. Für das Protokoll und den Server-Code wurde keine durchgeführt.

12. Was dieses Dokument nicht klären konnte

Eine ungeklärte Behauptung in einem Bedrohungsmodell ist eine Behauptung, die auch nicht in der Dokumentation stehen sollte. Diese sind offen.

  • Was der Ingress-LB loggt. Die nginx-Header-Konfiguration liegt im Baum (xops/.../connect/ingress.yaml:9), und der Connect-Dienst begrenzt die Rate anhand einer gehashten Adresse (server/connect/transport_rate_limit.go:65-80), aber die Zugriffslog-Konfiguration von nginx selbst wurde nicht auditiert. Jede durchgängige Aussage „wir bewahren deine Adresse nicht auf“ hängt daran.
  • Ob die nicht von der Verbosität abhängigen Log-Zeilen mit Adressen und SNI aus §5 dauerhaften Speicher erreichen. Sie werden nach stderr geschrieben; wohin stderr in Produktion geht und wie lange es aufbewahrt wird, ist eine Frage des Deployments, die diese Quellcode-Durchsicht nicht beantworten kann. Der Monitoring-Tailer liest sehr wohl ip:port-Muster aus Log-Zeilen heraus und gibt sie erneut in Befunde aus (server/monitor/tailer.go:136,328,333), was ein Beleg dafür ist, dass zumindest einige dieser Zeilen von Systemen gelesen werden, die sie aufbewahren.
  • Ob das serverseitige WireGuard-Proxy-Gerät die Paketquelle umschreibt, bevor es Pakete an Provider übergibt. Die stabile Tunnel-Adresse und die Sicht des Operators auf den echten öffentlichen Endpunkt (server/proxy/wg_handoff.go:56-67) sind beide verifiziert; der vollständige Pfad durch server/proxy/proxy_device.go und OpenProxyDevice wurde nicht von Anfang bis Ende gelesen (review/verified/ARCHITECTURE.md, nicht verifizierbarer Punkt 3).
  • Ob die Namensauflösung auf dem Browser-Pfad jemals lokal geschehen kann. Die Erweiterung konfiguriert standardmäßig einen HTTPS-CONNECT-Proxy, der aus der Ferne auflöst, und die SOCKS-Namensauflösung läuft über den DoH-Dialer des Tunnels (connect/tun.go:1155-1175). Das Proxy-DNS-Verhalten je Browser unter jeder Konfiguration wurde nicht getestet.
  • Die tatsächliche Verbosität in Produktion. BY_LOG_V steht standardmäßig auf 0 (server/env.go:41-49), was das an V(1) gebundene Ziel-Logging auf Client und Server unterdrückt — aber das einzige Deployment-Manifest im Baum setzt es auf 2 (xops/gitops-unused/.../api/deployment.yaml:47-48), in einem Verzeichnis namens gitops-unused. Behandle Verbosität als Laufzeit-Flag, nie als strukturelle Garantie.
  • Unabhängigkeit der Flotte. Der Operator veröffentlicht Live-Zahlen zu Städten und Ländern aus der eigenen Aggregation über aktuell verbundene, gültige Provider (server/model/network_client_location_model.go:1607-1641), und der Standort wird vom Operator aus der Verbindung abgeleitet, die er beobachtet, statt vom Provider deklariert zu werden (review/verified/ARCHITECTURE.md §6.4). Aber keine unabhängige Partei hat die Flotte von außen vermessen, und nichts weist nach, dass die einem bestimmten Client angebotenen Provider voneinander oder vom Operator unabhängig sind. Der Mechanismus ist im Quellcode überprüfbar; die Population ist heute für eine dritte Partei nicht überprüfbar.
  • Die Bindung des Identitätsschlüssels bei der Kontoerstellung. Das Design formuliert die Erwartung, dass „die Bindung (ClientId, öffentlicher Schlüssel) bei der Kontoerstellung registriert wird“ (connect/transfer_key.go:20-30). Ausgeliefert wird ein Client, der seinen Schlüssel in ein vom Operator kontrolliertes Redis veröffentlicht, wieder ausgeliefert von einer vom Operator kontrollierten API. Ob betrieblich eine stärkere Registrierung existiert, ließ sich aus dem Quellcode nicht klären; geh davon aus, dass es sie nicht gibt.
  • Ob jeder Sendepfad des Geräts einen Fenster-Client nutzt. Die ephemere Schlüsselvergabe des Fensterpfades ist verifiziert (§9.1). Der Top-Level-Client des Geräts trägt den dauerhaften Seed und aktiviert die Verschlüsselung bedingungslos (sdk/device_local_provider.go:90-98), jedes Senden, das auf dem Top-Level-Client reitet — Netzwerk-Peer-Beziehungen, Companion-Rückwege —, wird also mit dem stabilen Schlüssel signiert und trägt die stabile Top-Level-client_id. Diese Pfade wurden nicht erschöpfend aufgezählt. Lies „Ausgangskennungen sind ephemer“ als auf den Fenster-Client-Pfad begrenzt.
  • Ob Roles und Principal bei gewöhnlichen Verbraucher-Clients jemals gefüllt werden. Es sind vom Operator zugewiesene Strings, die in die signierten Vertragsbytes eingeschlossen sind und nur für ProvideMode_Network gesetzt werden (connect/protocol/transfer.proto:408-415; server/controller/connect_controller.go:474-481). Es fand sich kein Beleg dafür, dass die Apps sie füllen, und nicht jeder Aufrufer wurde auditiert. Würden sie jemals für Verbraucher-Datenverkehr gefüllt, wären sie vollwertige sitzungsübergreifende Kennungen, die für einen Provider sichtbar sind.
  • Persistenz des Schlüsselmaterials auf nicht-mobilen Hosts. ClientKeySeed wird nur aus sdk/local_state.go, sdk/device_local_key_material.go, sdk/device_local.go und sdk/cgo/ referenziert. Hosts, die diese nicht aufrufen, bekommen pro Prozess einen frischen Schlüssel; das Verhalten von Linux, Windows, Erweiterung und Server-Proxy wurde nicht einzeln verifiziert.
  • Ob der Gerichtsstand Texas in den ur.io-Bedingungen beabsichtigt ist, angesichts einer Delaware-Gesellschaft, einer kalifornischen Adresse und eines Gerichtsstands Delaware in den ur.xyz-Bedingungen. Kein Dokument im Baum erklärt das. Das ist eine Frage an die Rechtsberatung, kein Befund.

13. Meldungen

Schwachstellen: [email protected] und die Offenlegungsrichtlinie unter ur.io/vdp. Korrekturen an diesem Dokument, einschließlich Widerspruch zu jedem Urteil darin, sind auf demselben Weg willkommen — ein unabhängiger Befund, der einer Behauptung hier widerspricht, ist nützlicher als die Behauptung.