Modelo de amenazas
Este documento nombra a los adversarios de URnetwork y dice qué hace y qué no hace el sistema respecto de cada uno. Está escrito para ser atacado. Allí donde una propiedad es condicional, la condición está en la misma frase; allí donde algo es una intención de diseño que el código publicado todavía no impone, se etiqueta como intención de diseño, no como propiedad. Un resumen para lectores que no vayan a leer el registro completo precede a la sección 1.
La referencia es la propia explicación de Tor sobre los ataques que el encaminamiento onion no derrota: un adversario que observa ambos extremos de un circuito puede correlacionarlos por temporización, y Tor lo dice en público. URnetwork no incorpora tráfico de cobertura ni relleno, así que aquí ocurre lo mismo, y la sección 8 lo dice sin suavizarlo.
Estado de garantía, declarado una vez y aplicable a todo lo de abajo. No hay ninguna auditoría independiente del protocolo, del motor connect ni del código de servidor del operador — la capa sobre la que descansan las afirmaciones de privacidad. Dos evaluaciones de terceros de 2025 cubren otras superficies: una prueba de penetración independiente de terceros de la aplicación web y la API (25 de abril – 5 de mayo de 2025, con credenciales, manual, OWASP Top 10 más una revisión de controles ASVS), y una evaluación MASA AL2 de Leviathan Security Group sobre la app de Android (completada el 23 de mayo de 2025), que la superó y que la propia Leviathan acota como "no una evaluación de seguridad integral ni una prueba de penetración exhaustiva". Ninguna examinó los registros, la retención, la ruta de datos ni el protocolo. Toda afirmación de este documento es por tanto una afirmación sobre código fuente legible, que cualquiera puede comprobar y que nadie fuera del proyecto ha comprobado todavía en su conjunto.
Cómo leer las citas
Las afirmaciones citan path:line contra los árboles de trabajo en /Users/brien/urnetwork/{connect,sdk,server,proxy,extension} a fecha de 2026-09-17, cuando cada cita anclada a un número de línea de este documento se volvió a resolver contra los árboles por identificador. Los números de línea se desplazan; los identificadores no. Allí donde un hallazgo se estableció en una pasada anterior de verificación de código, se cita review/verified/ARCHITECTURE.md o review/verified/PRIVACY-ENFORCEMENT.md, que llevan el mismo convenio.
Esa nueva resolución no fue cosmética, y merece la pena dejarla anotada como advertencia para quien lea una copia más antigua. Entre la pasada del 2026-08-07 y esta, connect/transfer.go multiplicó su longitud aproximadamente por tres, y casi todas las citas de connect se movieron — la compuerta de envío de fallo en cerrado citada en transfer.go:2796 está hoy en :6751, y la compuerta de recepción citada en :5844 está en :14886. Trata como poco fiable cualquier número de línea de una copia de este documento que no lleve la fecha de arriba, y navega por identificador.
Se usan tres etiquetas deliberadamente:
- Propiedad — el código la impone, y un atacante que controle a las demás partes sigue sin poder violarla.
- Disciplina — el código elige no hacer algo que es capaz de hacer. Un cambio de despliegue o un parche de una línea podría revertirlo.
- Intención de diseño — el diseño dice que este es el objetivo; el código publicado todavía no lo impone.
Resumen
Esta sección es para lectores que no vayan a leer el registro completo. Cada frase de ella está respaldada por una sección numerada de más abajo o por la declaración de garantía de más arriba.
Qué es el sistema. URnetwork es una red de privacidad en la que unos miembros retransmiten tráfico para otros. La ruta es tú → extensor → operador → proveedor → internet. El reparto de confianza es entre dos partes retransmisoras: el operador, que sabe quién eres, y el proveedor, que ve adónde va tu tráfico.
La pregunta que este documento existe para responder. ¿Pueden un operador no confiable y un conjunto de proveedores no confiables transportar aun así una sesión privada y anónima, siempre que no estén en connivencia? La respuesta honesta hoy es en su mayor parte, y el hueco se puede localizar con precisión: la ceguera del proveedor a la identidad se sostiene frente a un proveedor arbitrariamente malicioso, de forma incondicional; la ceguera del operador al contenido se sostiene frente a un operador pasivo y frente a cualquier atacante en la ruta, pero descansa en que el operador distribuya honestamente las claves de identidad de los proveedores y en que no elija tus proveedores de forma adversarial. §1.1 es el modelo de confianza que lo enuncia parte por parte; §7 es el mecanismo que hay detrás de la salvedad.
Las dos propiedades, acotadas por separado. En una ruta retransmitida el proveedor nunca recibe tu IP de origen real; ningún ajuste ni compilación de proveedor cambia eso (§2). El operador no puede leer la sesión sellada cliente-proveedor, que está disponible en las cinco apps nativas (Android, iOS, macOS, Windows y Linux) bajo el nombre Cifrado poscuántico (§2.1). La extensión de navegador no tiene sesión sellada — su dispositivo cliente corre dentro del operador, que actúa como punto de traducción de protocolo, así que allí no puede existir un sellado (§2.4).
Tres dependencias de confianza, y en qué punto está cada una. El sellado solía fallar en abierto en silencio en todos los modos. Corregido el 2026-08-08/10: con el Cifrado poscuántico activado, el cliente falla en cerrado y se niega a enviar o aceptar datos de aplicación en texto plano en lugar de degradarse a ellos (§2.2). Quedan dos cosas, y ambas son confianza en el operador y no en la criptografía:
- Distribución de claves. ¿Puede el operador sustituir la clave de un proveedor? Sustancialmente más difícil desde el 2026-09-17, y ya no es negable. Con el Cifrado poscuántico activado, el cliente retiene el cifrador de la sesión hasta que la clave de proveedor suministrada por el contrato queda corroborada contra un historial de registro firmado, encadenado por hash y ligado al dominio que el operador ha publicado, y un desacuerdo verificado mata la sesión con ese proveedor (§7.4). Un operador que sustituya tiene ahora que firmar la sustitución, lo que deja un registro permanente y atribuible que diverge de lo que ve cualquier otro lector de ese id de cliente. Lo que no detiene es a un operador que sea malicioso desde tu primer contacto y consecuente en ello: firma una única cadena sin contradicciones que nombra su propia clave, y un cliente sin una vista independiente de qué firmante tiene autoridad no puede distinguirlo. Firmado no equivale a infalsificable por el firmante.
- Selección de proveedores. El operador clasifica y devuelve el conjunto de proveedores, y el cliente no tiene forma de comprobar que los proveedores que se le ofrecieron son independientes entre sí o del operador (§5, §6.1). Un operador no confiable no necesita por tanto entrar en connivencia con proveedores; puede seleccionar los suyos.
- Y el valor por defecto. La garantía de fallo en cerrado está atada a un conmutador que sigue publicándose desactivado en las cinco apps nativas (§2.2), así que un cliente con la configuración por defecto no abre sesión alguna con el proveedor: todo paquete de aplicación cruza el operador sin sellar, por la ruta estándar.
Los puntos 7, 8 y 9 de verify/BEFORELAUNCH.md siguen la pista de las tres; §2.2, §6 y §7.4 describen el estado actual.
Contra qué no defiende el sistema. Un adversario que observe tanto tu red de acceso como los proveedores que transportan tu tráfico puede correlacionar los dos extremos por tamaño y temporización (§8.1). Un observador pasivo global queda enteramente fuera del alcance (§8.3). URnetwork no incorpora relleno, ni tráfico de cobertura, ni mezclado; la sección 11 es la lista completa.
Garantía. No hay ninguna auditoría independiente del protocolo, del motor connect ni del código de servidor del operador; dos evaluaciones de terceros de 2025 cubren otras superficies: una prueba de penetración de la aplicación web y la API, y una evaluación MASA AL2 de Leviathan Security Group sobre la app de Android, que la superó.
Dónde vive el detalle. La sección 1 define las partes y enuncia el modelo de confianza parte por parte (§1.1). La sección 2 cubre los tres modos de conexión y qué ve cada parte en cada uno. Las secciones 3 a 5 toman cada adversario por turno: un proveedor malicioso, un observador de red y el operador. Las secciones 6 y 7 cubren los proveedores gestionados por el operador, la connivencia y la sustitución de claves de proveedor. Las secciones 8 a 12 cubren la correlación de tráfico, los identificadores de dispositivo, los requerimientos legales, la lista de aquello contra lo que el sistema no defiende, y lo que este documento no pudo establecer. La sección 13 dice dónde reportar vulnerabilidades y correcciones.
1. Las partes
| Parte | Gestionada por | Posición |
|---|---|---|
| Cliente | El usuario | La instancia de la app o del SDK; en las rutas del navegador y del proxy corre en los servidores del operador (§2.4) |
| LB de entrada | El operador | nginx; estampa la dirección de cliente observada en X-UR-Forwarded-For (xops/gitops-unused/gitops-prod/urnetwork/connect/ingress.yaml:9 — el único manifiesto de entrada del árbol, y su directorio se llama gitops-unused, así que puede que no describa producción; §12) |
| Servicio connect | El operador | Uno o dos procesos en hosts distintos, unidos por una conexión de intercambio interna (server/connect/resident.go:395). No "un servidor de retransmisión" |
| API / plano de control | El operador | Autenticación, descubrimiento y clasificación de proveedores, contratos, la consulta de clave pública (server/api/api.go) |
| Proveedor | Cualquier miembro | Recibe paquetes retransmitidos y marca el destino desde su propia conexión (connect/ip.go:7167 NewRemoteUserNatProvider; Receive en :8726, ReceiveBatch en :8486 → LocalUserNat, :731,784) |
| Extensor | Cualquier voluntario | El primer tramo de la ruta: un relé de reenvío TLS en una dirección independiente. Transporta la sesión cliente↔plataforma sin terminarla, así que ve la IP de conexión y no puede descifrar — el flujo interior es "el propio tls del cliente hacia el destino, cuyo interior el extensor nunca ve" (connect/extender/extender.go:35-44; connect/net_extender.go:34-48) |
| Resolvedores DoH | Cloudflare, Google, Quad9, OpenDNS | La ruta de la app resuelve el DNS sobre HTTPS a través del túnel hacia uno de estos cuatro (connect/net_http_doh.go:148-166) |
| Procesadores de pago | Stripe, Apple, Google, Solana/Circle | Retienen identidad real para las cuentas de pago; el operador almacena las claves de unión (server/db_migrations.go:1804, :4732, :2441) |
El operador es BringYour, Inc., una sociedad de Delaware del tipo C corporation con dirección para notificaciones en San Francisco (docs/legal/terms.md:8-9,265-271 nombra la entidad y la dirección; el estado de constitución no figura en los términos publicados). La sección 10 cubre qué significa eso para el proceso legal.
Una nota de vocabulario, porque la capa económica de la red usa una palabra distinta para la misma parte. A los proveedores se les llama mineros allí donde se les paga por el ancho de banda que transportan, y la red está diseñada para tener muchos operadores gestionados de forma independiente en lugar de uno solo. Ninguna de las dos cosas cambia la criptografía: el patrón es siempre un cliente ↔ un operador ↔ un proveedor, y cada propiedad de más abajo es una propiedad de ese triángulo. Lo que una pluralidad de operadores y proveedores cambia es la verosimilitud del supuesto de no connivencia de §1.1, no el mecanismo que ese supuesto protege.
1.1 El modelo de confianza
Lee esta tabla así: si esta parte es arbitrariamente maliciosa y las demás son honestas, ¿sobrevive la propiedad?
| Propiedad | frente a un proveedor malicioso | frente a un extensor malicioso | frente a un operador malicioso |
|---|---|---|---|
| Tu identidad queda oculta para la salida (el proveedor nunca averigua tu dirección ni tu cuenta) | Sí — incondicional. La dirección nunca está en el cable; el punto de entrada del proveedor solo toma ids (§2, §3) | Sí. Un extensor ve tu dirección, que tu ISP ya conoce, y no averigua nada sobre la salida. La excepción es el rol de proveedor, que activas al compartir tu conexión: se identifica con su id de cliente ante los extensores que mide (§4) | No. El operador sabe quién eres por construcción; ese es su papel (§5) |
| Tus destinos quedan ocultos para el coordinador (el operador no puede leer la sesión) | no aplica — el proveedor es la parte que ve los destinos | Sí. El extensor reenvía una sesión que no puede terminar (§4) | Condicional. Se sostiene frente a un operador pasivo, frente a la degradación, frente a un operador que se vuelve malicioso más tarde, y frente a uno inconsistente entre sus propios canales. No se sostiene frente a un operador malicioso y coherente consigo mismo desde el primer contacto (§7.4) |
| Ninguna degradación a texto plano (el tráfico va sellado o no fluye) | Sí, con el conmutador activado. La compuerta de recepción rechaza el texto plano al que se ha retirado el envoltorio (§2.2) | Sí, con el conmutador activado | Sí, con el conmutador activado — la omisión se convierte en denegación, no en divulgación. No, con él desactivado, que sigue siendo el valor por defecto publicado (§2.2) |
| No existe ningún registro de navegación que se pueda requerir o filtrar | Sí | Sí | Sí — estructural. No existe campo alguno de destino, host, URL, SNI, puerto ni dominio en ninguna parte del registro de transferencias (§5, §10) |
| Tu tráfico no es correlacionable de extremo a extremo | No | No | No. Sin relleno, sin tráfico de cobertura, sin mezclado — fuera del alcance por diseño (§8) |
De ahí se siguen tres cosas, y son la forma honesta de la respuesta a la pregunta del resumen.
Al proveedor no se le confía nada, y eso se impone estructuralmente. No por política, no porque el proveedor ejecute la compilación oficial — sino porque la dirección nunca se envía. Esta es la propiedad más fuerte del documento.
Al operador se le confían dos cosas, y ambas están abiertas. Distribuye las claves de identidad de los proveedores contra las que se comprueba el sellado (§7), y elige qué proveedores se te ofrecen (§5, §6.1). Ninguna de las dos es verificable hoy por el cliente.
Esas dos no son independientes, y la segunda es la más afilada. Un documento que diga "seguro salvo que el operador y los proveedores entren en connivencia" está subestimando el problema mientras sea el operador quien elige a los proveedores: no necesita sobornar a nadie si puede seleccionar a quien ya controla. La distinción de diseño es sustitución frente a selección (connect/DESIGNNOTES2.md §3.4). Cerrar la sustitución tiene una respuesta criptográfica, y se está construyendo una (§7.4). Cerrar la selección necesita algo que el cliente pueda comprobar sobre la independencia de un conjunto de proveedores, y nada en el código publicado lo intenta.
2. Los tres modos, y qué ve cada parte
Esta tabla es canónica y coincide con OVERVIEW.md. Todo lo demás de este documento es el mecanismo que hay detrás de ella.
| Modo | El operador ve | El proveedor ve | Valor por defecto y disponibilidad |
|---|---|---|---|
| Retransmitido sellado | conexión de cuenta/origen, asociación con proveedores, texto cifrado y tiempo/volumen | tráfico de destino, id de dispositivo/contrato, no tu IP de origen real | solo apps nativas, y opcional hoy: dictaminado como valor por defecto, pero se sigue publicando desactivado; ver §2.2 |
| Retransmitido estándar | conexión de cuenta/origen, asociación con proveedores, destinos interiores y bytes de los paquetes | tráfico de destino, id de dispositivo/contrato, no tu IP de origen real | el valor por defecto nativo hoy, ya que el sellado se publica desactivado (§2.2), y las rutas del navegador y del proxy (§2.4); con el sellado activado, un proveedor con el que no se puede sellar se omite en vez de atenderte en este modo (§2.2) |
| Directo | menos intervención en la retransmisión | tu IP de origen real y el tráfico de destino | opcional, desactivando Anonimización fuerte (§2.3) |
De ahí se siguen dos afirmaciones asimétricas, y no son igual de fuertes:
La ceguera del proveedor a la identidad es una propiedad, y es incondicional en ambas rutas retransmitidas. El punto de entrada del proveedor toma ids, nunca una dirección: RemoteUserNatProvider.Receive está parametrizado por un TransferPath de tres ids de 16 bytes (connect/ip.go:8726, forma por lotes en :8486; connect/connect.go:50). Ningún protobuf de transporte lleva una dirección, ciudad o país de cliente — un grep sobre connect/protocol/*.proto buscando location|city|country|region no devuelve nada (review/verified/ARCHITECTURE.md §1.3). No hay ningún ajuste que desactive esto en una ruta retransmitida, y ninguna compilación de proveedor puede obtener la dirección que nunca se le envía.
La ceguera del operador al contenido es un ajuste, todavía no un valor por defecto — sobre un mecanismo al que le queda un defecto. El mecanismo tiene nombre: Cifrado poscuántico, un control del panel de conexión de las apps de Android, iOS, macOS, Windows y Linux, que sella una sesión de extremo a extremo entre tu cliente y el proveedor (§2.1). La primera condición es que el usuario encuentre el conmutador, porque sigue publicándose desactivado (más abajo). Desde el 2026-08-10, lo condicional ya no es si el sellado dejó de establecerse en silencio: con el conmutador activado, una conexión que no puede sellarse no transporta tráfico de aplicación en absoluto, en lugar de transportarlo en claro (§2.2). Lo que sigue siendo condicional es si el operador, que sirve las claves de proveedor contra las que se comprueba el sellado, las está sirviendo honestamente (§7.4). Lee eso antes de confiar en esta propiedad; no es visible para el usuario.
Valor por defecto en el lanzamiento: dictaminado ACTIVADO; a fecha de 2026-09-24 se sigue publicando DESACTIVADO. El propietario del producto dictaminó el 2026-08-07 que la sesión sellada se publica habilitada, para que la ceguera del operador fuera el valor por defecto y no una elección que el usuario tenga que hacer. Eso no se ha materializado. La bandera del perfil de rendimiento sigue en false en las cinco apps — android/.../PerformanceProfileSettings.kt:34,88, apple/app/network/Shared/ViewModels/DeviceManager.swift:395,479, windows/app/src/App/SdkHost.h:192 y linux/app/src/ConnectDrawer.cpp:746 —, y el SDK arranca un dispositivo sin perfil alguno. Sin la bandera, los clientes de ventana conservan DefaultEncryptionSettings, cuyo modo es EncryptionModeOff (connect/transfer_encrypt.go:768-770; sdk/device_local.go:4483), así que un cliente con la configuración por defecto no corre hoy ni Required ni Opportunistic: no abre ninguna sesión con el proveedor, y todo paquete de aplicación cruza el operador sin sellar (punto 7 de verify/BEFORELAUNCH.md, veredicto: falla). Toda afirmación de este documento sobre el comportamiento de fallo en cerrado está acotada a que el conmutador esté activado, y un lector debería asumir que está desactivado salvo que lo haya activado él mismo.
Esa brecha importa ahora más en una dirección que antes. Como la compuerta de fallo en cerrado está atada a ese mismo conmutador, activar el valor por defecto propagaría una garantía real a todos los usuarios en lugar de limitarse a ampliar el alcance de un fallo silencioso — lo contrario de lo que advertía esta sección cuando el sellado podía fallar en abierto en todos los modos. Lo que el valor por defecto sigue sin poder arreglar es §7.4, que es una propiedad del sellado en sí: un sellado activado por defecto en el que el operador puede interponerse como máquina en medio solo protege frente a un operador pasivo.
La extensión de navegador no tiene sesión sellada en absoluto (extension/src/utils/auth-params.ts:33, performance_profile: null). Eso es alcance, no un valor por defecto, y el dictamen de lanzamiento no lo cambia.
Allí donde el sellado está ausente — los endpoints del navegador y del proxy (§2.4), y las apps nativas con el sellado desactivado (§2.2) — el operador termina el TLS/QUIC del cliente y retransmite paquetes IP en texto plano (connect/protocol/ip.proto:9-16). Elige analizar solo la cabecera de encaminamiento — FilteredTransferFrame no contiene más que la ruta de transferencia (connect/protocol/transfer.proto:85-87), y el relé deserializa exactamente ese mensaje y nada más (server/connect/resident.go:3824) —, lo cual es disciplina, no una barrera criptográfica.
2.1 Qué es el sellado, con precisión
Una sesión TLS 1.3 directamente entre cliente y proveedor, transportada como tramas de control ordinarias a través del mismo operador: los mensajes EncryptedControl llevan los bytes del handshake y viajan por el flujo normal de Pack/Ack, fiable y en orden, con una sesión por pareja de pares, y el ClientId lexicográficamente menor asume el rol de cliente TLS (connect/transfer_encrypt.go:30-60). El intercambio de claves es el híbrido X25519MLKEM768, con X25519 convencional como respaldo (connect/transfer_encrypt.go:376-384); el AEAD es AES-256-GCM sobre una clave de 32 bytes exportada bajo la etiqueta RFC 5705 urnetwork-connect-aead, con un nonce aleatorio de 12 bytes por mensaje (:88-99 para las etiquetas y las longitudes, :283-301 para la construcción); las identidades son Ed25519, generadas en el proceso por ClientKeyManager (connect/transfer_key.go:87-143, la única llamada a ed25519.GenerateKey de los tres árboles, en :113) — así que "poscuántico" se acota solo al intercambio de claves. El TLS mutuo es obligatorio (ClientAuth: tls.RequireAnyClientCert, connect/transfer_encrypt.go:406) y el certificado del par se comprueba en la capa de secuencia contra el compromiso del contrato en lugar de por la pila TLS; el comentario de connect/transfer_encrypt.go:386-398 explica que sin mTLS el lado con rol de servidor TLS no tendría PeerCertificates y la verificación del contrato fallaría para la mitad de todas las parejas de pares.
2.2 Falla en cerrado cuando lo pides, en abierto cuando no
Actualizado el 2026-08-10. Esta sección decía antes que el sellado falla en abierto en silencio y que no había opción de fallo en cerrado. Eso ya no es cierto, y el cambio es el endurecimiento más significativo de la historia de este documento — cerró el vector de degradación en ambas direcciones. Lo que sigue es el comportamiento actual, leído del código fuente.
El cifrado sigue siendo una propiedad binaria de Cipher() != nil, y un cifrador nulo sigue significando que la sesión no es utilizable: un handshake fallido, una prueba de identidad fallida o un contrato que nunca llevó la clave pública del par lo dejan nulo. La razón estructural merece enunciarse una vez, porque todo lo demás depende de ella — el AEAD existe antes de que el par esté autenticado y se retiene deliberadamente. completeHandshake lo aparca en derivedTlsCipher en lugar de exponerlo (connect/transfer_encrypt.go:1719-1726), y solo maybeVerifyPendingPeerIdentityProof promueve una época a establecida, y solo ante una prueba que verifica (:1928-2050, la promoción en :1979-1981). Cipher() lee establishedEpoch (:2625), así que para cualquier llamante un par no probado es indistinguible de un handshake incompleto.
Una corrección a versiones anteriores de esta sección: el fallo de la prueba de identidad ya no consiste solo en "quedar sin autenticar". Ahora además establece identityFailedTerminal, cancela la época y emite un EncryptionEventIdentityFailed (connect/transfer_encrypt.go:2005-2030). La línea de log sigue diciendo "sesión dejada sin autenticar" (:2027), y esa redacción se queda corta respecto de lo que hace el código.
Lo que cambió es lo que ocurre después. Ahora hay tres modos (connect/transfer_encrypt.go, EncryptionMode):
| Modo | Comportamiento |
|---|---|
EncryptionModeOff | El valor cero. Capa de sesión inerte, todo en texto plano. |
EncryptionModeOpportunistic | Sella en cuanto se establece una sesión, texto plano hasta entonces — o permanentemente si nunca llega a establecerse. El comportamiento histórico. |
EncryptionModeRequired | Nunca expone datos de aplicación en texto plano hacia o desde un par para el que se espera una sesión. |
Bajo EncryptionModeRequired la garantía se hace cumplir en cuatro puntos:
- Compuerta de entrada al envío (
connect/transfer.go:6751-6800, enSendSequence.Pack). Un pack de aplicación espera al cifrador dentro del tiempo límite de quien llama y luego se rechaza, sin enviar — nunca se degrada — con unErrEncryptionRequiredNotEstablishedtipado (connect/transfer_encrypt.go:505). La compuerta está situada deliberadamente antes de que se asigne un número de secuencia: el handshake del rol de cliente viaja por esta misma secuencia, así que retener una trama ya secuenciada abriría un hueco en el lado de recepción ordenado y dejaría varado el ClientHello detrás del hueco, lo que interbloquearía el handshake que despejaría la compuerta. - Red de seguridad del envío (
connect/transfer.go:11726-11738, enwriteMaybeWrappedBytes). Una trama que llega al escritor sin cifrador se rechaza en lugar de escribirse, lo que cubre la estrecha carrera en la que una sesión se desmonta entre el encolado y la escritura. - Compuerta de recepción (
connect/transfer.go:14886-14913). Una trama de aplicación en texto plano procedente de un par para el que se espera una sesión se descarta y se audita — esta es la mitad que más importa, porque cierra la degradación en la que un atacante retira el envoltorio y el receptor aceptaría el texto plano en otro caso. La trama se acusa y se descarta en lugar de quedar sin acuse, ya que retener el acuse abriría un hueco en la secuencia ordenada y atascaría ambos lados. - Prefiltro de candidatos (
connect/ip_remote_multi_client.go:11695-11710,EncryptionCapabilityPrefilter, a true por defecto en:196). Un candidato de ventana del que la API de claves fuera de banda de la plataforma dice que nunca ha publicado una clave de identidad se hace fallar de inmediato, ya que nunca podrá completar el handshake. La regla de rechazo es deliberadamente estrecha (:13229-13236): un error de recuperación no rechaza, así que la inalcanzabilidad del operador no puede convertirse en un veto a un proveedor. Solo acelera un fallo seguro; nunca admite a un candidato.
Las tramas de control del handshake, los acuses y los pares del plano de control están exentos por diseño — la compuerta cubre la carga útil de aplicación, no el andamiaje que la arranca.
Qué modo te toca lo decide el conmutador de Cifrado poscuántico. Cuando está activado, el cliente consumidor corre EncryptionModeRequired (connect/ip_remote_multi_client.go:13080-13091, a partir de la bandera del perfil en :1311): un proveedor que no pueda establecer una sesión no transporta para ti tráfico de aplicación en absoluto, en lugar de transportarlo en claro. El coste declarado es la disponibilidad, aceptado deliberadamente. Los proveedores corren EncryptionModeOpportunistic (sdk/device_local_provider.go:186) para que un mismo proveedor pueda servir a consumidores sellados y no sellados; esa es una decisión de compatibilidad del lado respondedor y no debilita la garantía del iniciador.
Fíjate en que el valor cero del enum de modos es Off (connect/transfer_encrypt.go:469-504), así que una estructura de ajustes a cero no cifra nada en lugar de cifrar a medias en silencio. Es una decisión deliberada, tomada cuando Encrypt bool se sustituyó por el triestado.
Qué significa esto para el conmutador. Con el Cifrado poscuántico activado, el sellado ya no falla en abierto: falla en cerrado, a voces, tanto al enviar como al recibir. Con el Cifrado poscuántico desactivado, que es como se publican las apps, el cliente ni siquiera es oportunista: corre EncryptionModeOff y nunca inicia una sesión, así que todo su tráfico de aplicación fluye sin sellar, y nada se lo dice al usuario. Así que la afirmación honesta es condicional, y el valor por defecto importa: ver la nota Valor por defecto en el lanzamiento de §2.
El comportamiento está cubierto por pruebas, no solo por comentarios — TestRequiredEncryptionFailsClosedAgainstPlaintextPeer, TestRequiredGateNonBlockingSendRefusesPreCipher, TestRequiredGateBoundedBudgetRefusesUnsent, TestRequiredSendRefusalTypedErrorAndEvent y TestRequiredContractFreeWithoutKeySourceFailsClosed.
Un comentario obsoleto que ignorar al auditar: el comentario dentro de completeHandshake en connect/transfer_encrypt.go:1723-1725 sigue afirmando sin matices que, hasta que la prueba de identidad verifica, "la ruta de envoltura observa cipher == nil y el tráfico fluye en texto plano". Eso solo es cierto en los modos Off y Opportunistic; las compuertas de arriba lo anulan bajo Required. El comentario es anterior a la corrección.
Parcialmente cerrado desde entonces: ahora hay una superficie de identidad, aunque no un aviso por conexión. El SDK exporta el conjunto de pares con una sesión establecida y con la identidad verificada, junto con una huella canónica para mostrar — sdk/post_quantum_identity.go define PublicIdentityKeyHash como "EL hash canónico de visualización para las claves de identidad en todas las plataformas: el SHA256 de la clave, codificado en base32 en mayúsculas y sin relleno (RFC 4648)", con ProviderIdentity en :24-30 y un view controller en sdk/post_quantum_identity_view_controller.go:21, consumidos por ambas apps (PostQuantumIdentityViewModel.kt, PostQuantumIdentityStore.swift). Eso es más que un hook: es el principio de un canal de comparación que el usuario puede comprobar y que no depende del operador, ya que una huella obtenida por otra vía puede compararse con la que se muestra. Lo que sigue faltando es un indicador sencillo por conexión de "esta sesión está sellada". Las señales que quedan en el log de dispositivo son peer identity proof verified — cipher is now usable (connect/transfer_encrypt.go:2004) en caso de éxito, un Errorf en caso de fallo (:2026-2029), y un evento NotifyRequiredSendBlocked cuando la compuerta rechaza.
2.3 Modo directo
Desactivar Anonimización fuerte establece AllowDirect, cuyo propio comentario de campo dice: // setting this to true exposes the real source IP to the provider (sdk/sdk.go:1130-1139). El flujo se fuerza a un stream P2P (connect/ip_remote_multi_client_probe.go:1207-1209) sobre un canal de datos WebRTC/ICE (connect/transport_p2p_webrtc.go:818-825), e ICE significa que ambos extremos aprenden la dirección del otro. El valor por defecto es desactivado — un perfil de rendimiento nulo da falso (connect/ip_remote_multi_client.go:841-850; sdk/sdk.go:1130-1139).
Dos casos forzados: los pares de la misma red (tus propios dispositivos) siempre permiten el directo (connect/ip_remote_multi_client.go:843-850,2656-2660), y los dispositivos alojados lo fuerzan a desactivado (sdk/device_local.go:608-615). Fíjate en que el modo directo retira al operador solo de la ruta de datos. Los contratos, la selección de proveedores y los mensajes de control siguen pasando por la plataforma, así que el operador sigue sabiendo qué proveedor usaste y cuántos bytes se movieron.
2.4 La ruta que la tabla no cubre: el navegador y los endpoints proxy
En la extensión de navegador, la app web y los endpoints HTTPS/SOCKS5/WireGuard, el operador ejecuta el cliente. La extensión aprovisiona un dispositivo proxy del lado del servidor (extension/src/utils/auth-params.ts:22-36, enable_socks/enable_http) y se apunta el navegador al endpoint proxy del operador — HTTPS CONNECT por defecto (extension/src/bridge/background.ts:220; extension/src/utils/proxy-manager.ts:264). La instancia del SDK que abre contratos y sostiene la ventana de proveedores corre en server/proxy, no en la máquina del usuario.
Por eso no puede existir ninguna sesión sellada en estas plataformas, como cuestión de arquitectura y no de esfuerzo de ingeniería aún no invertido. La sesión sellada es cliente↔proveedor, y su garantía depende de que el extremo cliente viva en el propio dispositivo del usuario. Un navegador o un cliente proxy corriente habla su propio protocolo (HTTPS CONNECT, SOCKS5, WireGuard) y no puede ejecutar el motor de la red, así que el operador ejecuta el dispositivo del cliente de forma remota y actúa como punto de traducción de protocolo entre los dos. Un sellado negociado desde ese dispositivo remoto empezaría dentro del operador — la parte a la que el sellado existe para cegar. La implementación aceptó este intercambio deliberadamente: estas superficies existen por comodidad en plataformas y protocolos que no pueden alojar el cliente completo, y la documentación no debe describirlas como selladas.
Consecuencias, dichas llanamente:
- La ceguera del proveedor a la identidad sigue sosteniéndose — el proveedor sigue recibiendo solo ids.
- La ceguera del operador no existe en esta ruta de ninguna forma. El operador termina la conexión proxy, recibe el host de destino desde CONNECT, y retiene las claves del cliente.
- El dispositivo alojado no instala ningún mux de mejora de DNS (
server/proxy/proxy_device.go:858,SetUpgradeMuxSettings(nil)), así que el DoH dentro del túnel de la ruta de la app (§3.2) no aplica aquí.
Lo que hace las veces del cifrado en esta ruta es la disciplina de almacenamiento: la ruta de datos del proxy no registra nada en las rutas que el cliente puede dirigir, y un test de regresión lo fija — proxy/socks5_nolog_test.go, TestClientDrivenTrafficNeverLogs, comprueba cero líneas de log en los casos malformado, sobredimensionado y no marcable. Dos salvedades honestas: el motivo declarado del test es la denegación de servicio por amplificación de logs y no la privacidad, y el manejador de recuperación de pánico sí registra una dirección de cliente (proxy/socks5_server.go:129-132). Ver review/verified/PRIVACY-ENFORCEMENT.md §2.2.
3. Adversario: un proveedor malicioso
Dentro del alcance, y asumido. Cualquiera puede proveer. No hay atestación, garantía depositada, comprobación de identidad ni resistencia Sybil en la participación como proveedor — una búsqueda en los árboles de server, connect y sdk de sybil|kyc|attestation no devuelve nada relevante. Un proveedor ejecuta código abierto en hardware que controla, así que asume que ejecuta una compilación modificada.
Qué ve. Todo lo que ve un ISP respecto del tráfico que transporta: IP y puerto de destino, tamaños y temporización de los paquetes, y el SNI de TLS allí donde el cliente lo envía en claro. Recibe paquetes IP en crudo y los marca él mismo (connect/protocol/ip.proto:9-16; connect/ip.go LocalUserNat). También ve el client_id transportado como el SourceId del contrato (connect/protocol/transfer.pb.go:1779; server/controller/connect_controller.go:816-830) — un identificador por plaza de ventana que solo el operador puede resolver a una cuenta, y nunca el device_id, que no tiene campo en el cable. Su vida útil, y las varias cosas que sí persisten entre sesiones, están en §9.
Qué puede hacer.
- HTTP en texto plano: leerlo y modificarlo. El puerto 80 se pasa a la salida sin cambios por defecto —
HttpUpgradeUnencryptedes el modo por defecto (connect/ip_mux_upgrade.go:38-47). Un proveedor está en la misma posición que un punto de acceso Wi-Fi hostil para cualquier tráfico que no vaya cifrado por sí mismo. HTTPS protege el contenido de la aplicación frente al proveedor; nada en URnetwork añade una segunda capa sobre el propio TLS del destino. - Descartar, retrasar o atascar tráfico de forma selectiva. Un proveedor que acusa recibo de tráfico pero no devuelve ninguno se etiqueta como agujero negro y se retira (
connect/ip_remote_multi_client.go:37-54), así que la denegación se detecta y se rodea — pero la detección es estadística, y el tráfico que vio antes de ser retirado ya lo había visto. - Mentir sobre el rendimiento para atraer tráfico. La clasificación usa la latencia y el rendimiento medidos (
server/model/network_client_location_model.go:4098,4220), y la medición es del operador, no un autoinforme del proveedor, así que esto está acotado — pero es una entrada de clasificación, no un control de integridad. - Correlacionar flujos dentro de su propia vista. La afinidad por sitio fija un sitio a un proveedor (
connect/ip_remote_multi_client.go:837), así que un solo proveedor ve una porción coherente de la navegación de un cliente para los sitios que sostiene.
Qué no puede hacer sin ayuda. Averiguar tu IP real en una ruta retransmitida, averiguar tu cuenta o tu correo, o atribuir un client_id a una persona. Eso requiere al operador (§5).
3.1 Qué restringe y qué no restringe ip_security
La capa ip_security del motor connect corre en la propia salida del proveedor y en el propio dispositivo del usuario, nunca de forma central en el operador — los campos IngressSecurityPolicyGenerator/EgressSecurityPolicyGenerator del operador existen pero nunca se asignan ni se leen (server/connect/resident.go:263-264; review/verified/PRIVACY-ENFORCEMENT.md §4.1). Bloquea firmas de BitTorrent y de intercambio de archivos, protocolos opacos no estándar, destinos listados por reputación y tráfico con patrón de ataque, y lo hace sobre una base deliberadamente delgada: la 5-tupla más un prefijo de carga útil acotado a 8 paquetes / 512 bytes.
Las tablas de reputación son generadas, no mantenidas a mano. connect/security y connect/blocker agregan feeds públicos de inteligencia sobre amenazas — el Feodo Tracker de botnets C2 de abuse.ch, Spamhaus DROP y DROPv6, las IPs comprometidas de Emerging Threats, Blocklist.de, CINS Army, TweetFeed, ViriBack, BruteForceBlocker — en tablas de rangos empaquetadas (46.789 rangos IPv4 a fecha de la instantánea de 2026-08-05; la cabecera de connect/ip_security_cfaa_block.go lleva la lista de feeds y la atribución). El pipeline de publicación regenera ambas tablas desde los feeds en vivo (build/all/run.sh, el paso CONNECT_IP_UPDATE), así que cada versión incorpora una instantánea actual y un cliente al día bloquea el espacio de direcciones listado más reciente. La instantánea envejece con la compilación instalada; las listas se actualizan por versión, no por el aire.
La cota del prefijo de carga útil está en connect/ip_security_dmca.go:136, con el nombre del servidor borrado de la clave de flujo (:366,386,424) y los contadores llevados por (version, protocol, port) con el campo de IP nunca habilitado (connect/ip_security.go:371).
Es un control de seguridad para proveedores honestos, no un control de seguridad contra los hostiles. Restringe qué sacará hacia fuera la conexión de un proveedor. No hace nada respecto de lo que un proveedor haga con el tráfico que sí transporta, corre en un proceso que el proveedor posee, y una compilación modificada puede desactivarlo. No lo leas como un límite a un proveedor malicioso.
Un detalle de reporte pertenece aquí porque es un canal de metadatos. Una coincidencia de firma de BitTorrent devuelve Incident, que llama a ReportAbuse (connect/ip.go:9014,9110), transmitiendo una trama de control PeerAudit al operador que lleva el id de dispositivo del par y un booleano — sin destino, sin dominio, sin contenidos (connect/transfer.go:2971). El operador no tiene hoy manejador para ese tipo de mensaje, así que no se almacena nada al recibirlo (server/controller/connect_controller.go:132,436-437; un grep de todo el repositorio buscando PeerAudit en server devuelve cero resultados). Eso es una disciplina a un case de distancia de cambiar. Los descartes de tráfico opaco cifrado son silenciosos y sin informe (connect/ip_security_dmca.go:483-486).
3.2 DNS
En la ruta de la app y del SDK, el DNS plano sobre UDP/TCP 53 se intercepta y se resuelve por DoH a través del túnel (connect/ip_mux_upgrade.go:120-148, instalado por defecto en sdk/device_local.go:7105), hacia Cloudflare, Google, Quad9 u OpenDNS (connect/net_http_doh.go:150-155). Un proveedor ve por tanto una conexión HTTPS hacia un resolvedor público, no la consulta. Dos límites honestos:
- Una ventana de fuga de DNS en el arranque está documentada en el código fuente. Mientras el DoH del túnel todavía se está estableciendo, una consulta compite contra un resolvedor sobre la salida del host local, "a costa de una breve fuga de DNS durante el arranque" (
connect/ip_mux_upgrade.go:120-126,78-92). Tu red local y tu ISP pueden ver esas consultas. - El endpoint WireGuard entrega al cliente un resolvedor público fijo,
DNS = 1.1.1.1(server/model/network_client_proxy_model.go:774), y el puerto 53 se pasa sin inspeccionar por la política de seguridad (connect/ip_security_cfaa.go:113). En esa ruta las consultas DNS atraviesan al proveedor como DNS plano, y el proveedor puede leerlas y responderlas.
Los cuatro resolvedores DoH son terceros que ven consultas llegando desde la dirección de salida de un proveedor, sin vincular a tu cuenta. Esa es una dependencia real y no es configurable a cero: alguien tiene que responder el DNS.
4. Adversario: un observador de red
Cuatro posiciones, de la más débil a la más fuerte.
Tu red local y tu ISP. Ven que te conectas a connect.bringyour.com sobre TLS/QUIC, o a un extensor, más tamaños y temporizaciones. No ven los destinos dentro del túnel, salvo en la ventana de DNS de arranque documentada más arriba. Allí donde la plataforma está bloqueada, los extensores se presentan como servicios ordinarios en puertos verosímiles y los clientes rotan identidades con fragmentación aleatoria (connect/net_extender.go:123-133; construidas en connect/net_extender_network.go:915), y existe un transporte con forma de DNS para redes donde solo escapa el DNS (connect/transport_pt.go:37,73). Estos son mecanismos para atravesar cortafuegos locales y regionales, clasificados por debajo de los transportes directos (connect/transport.go:29-33). No son defensas frente al análisis de tráfico y nunca deberían describirse como tales.
Un operador de extensor. Cualquiera puede ejecutar uno, la app acepta una IP introducida a mano, y por defecto no se exige firma (connect/extender/extender.go:35-44). Un extensor es el par TCP, así que ve la IP del usuario que se conecta, y no puede descifrar lo que reenvía, porque la sesión TLS lo atraviesa hasta la plataforma en lugar de terminar en él (connect/net_extender.go:34-48). El tramo del extensor pone por tanto en la ruta a un observador de primer salto de tu dirección que no es el operador. Ese es el intercambio que hace el diseño para sacarte de un bloqueo, y merece la pena ser exacto sobre su tamaño: el tramo del extensor no añade cifrado ni anonimato — aprende lo que tu ISP ya sabe y nada más, con la única excepción que sigue. En el cliente publicado los transportes directos compiten primero y los marcadores de extensor se expanden cuando aquellos fallan (connect/net_http.go:1686-1709); un cliente configurado con extensores personalizados los usa como su única ruta (connect/net_http.go:1686-1709).
La excepción es el rol de proveedor. Todo cliente sondea los extensores para clasificarlos por tiempo de ida y vuelta. Un dispositivo atestigua lo que midió solo mientras comparte su conexión: empieza a atestiguar cuando activas el conmutador de compartir conexión y deja de hacerlo cuando lo desactivas, incluso a mitad de una pasada de sondeo, y un dispositivo que nunca comparte su conexión sondea de forma anónima (sdk/device_local_provider.go:1155-1167; connect/net_extender_network.go:1240). Mientras comparte, cada declaración lleva su id de cliente y va firmada con su clave de cliente (connect/DESIGNNOTES4.md §1, §3). El extensor averigua que el proveedor con ese id, y por tanto con esa clave pública (§9.2), lo midió desde esa dirección; el operador recibe el id del proveedor, el extensor, el tiempo de ida y vuelta y la hora. Los extensores se miden entre sí de la misma forma, un objetivo cofirma cada tiempo de ida y vuelta que acepta, y el operador conserva los pings durante un día para derivar ubicaciones (connect/GEOMAP.md §2). Son datos operativos sobre infraestructura pública, bajo una clave que en el rol de proveedor ya es duradera y pública.
Un observador de un solo extremo. Vigilar solo el lado del cliente da "este usuario está conectado a URnetwork" y un perfil de bytes/temporización. Vigilar solo la salida de un proveedor da destinos y un perfil de bytes/temporización sin identidad adjunta.
Un observador de ambos extremos. Ver §8. Este es el adversario al que URnetwork no derrota.
5. Adversario: el operador
El operador autentica a los clientes, selecciona y clasifica a los proveedores, redacta los contratos, distribuye las claves públicas y transporta los paquetes en las rutas retransmitidas. Asume en esta sección que es hostil o que está obligado.
Qué ve, sin ningún esfuerzo adicional: tu cuenta; tu dirección en el momento en que te conectas; una ciudad/región/país derivados de esa dirección por una consulta a una base de datos local — ipDb en server/ip.go abre desde disco la base de datos MaxMind GeoLite2-City empaquetada (mmdb/geolite2.mmdb) e ip.go no hace ninguna llamada de red de ningún tipo, así que una dirección nunca se entrega a un servicio de geolocalización — persistidos por conexión (SetConnectionLocation en server/model/network_client_location_model.go); qué proveedores te sirvieron; recuentos de bytes; y en la ruta estándar, las direcciones de destino y los bytes de los paquetes dentro del túnel — aunque esos normalmente ya van dentro del propio HTTPS del destino.
Qué almacena. Los registros de conexión y de autenticación guardan un hash unidireccional con clave del bloque /29 (IPv4) o /56 (IPv6) circundante en lugar de la dirección (server/ip.go:46-72). Una precisión que importa a un auditor: es un único pepper de ámbito de proceso obtenido del vault, memoizado una vez, sin sal por fila y sin ningún mecanismo de rotación en todo el repositorio (server/ip.go:42-43); el espacio de claves IPv4 es de 2^29 bloques, así que cualquiera que tenga el pepper puede invertirlo por fuerza bruta. El secreto del pepper es toda la propiedad de seguridad. El subsistema /verify usa /48 para IPv6, no /56 (server/ip.go:74-89; server/model/verify_model.go:113-116). El puerto de origen se almacena en claro junto al hash (server/db_migrations.go:2044). La fila de auditoría de creación de cuenta registra el hash con pepper y el puerto, nunca el ip:port en crudo (server/model/network_model.go:1157), y las filas de auditoría se retiran a los 180 días (server/model/audit_model.go:1018,1052, programado en server/taskworker/taskworker.go:85-88).
Qué no existe para poder ser almacenado. No aparece ningún destino, host, URL, SNI, puerto ni dominio en ninguna parte del registro de transferencias — un grep del archivo de migraciones completo buscando esos términos devuelve cero resultados (review/verified/PRIVACY-ENFORCEMENT.md §1.2). El registro HTTP pasa por una lista de permitidos de exactamente cinco cabeceras (server/http_log.go:13-29), fijada por test. Ninguna métrica de Prometheus lleva una IP, host, destino, puerto ni id de cliente; de 39 métricas declaradas solo tres están etiquetadas y cada etiqueta es un enum acotado (review/verified/PRIVACY-ENFORCEMENT.md §2.5). Las subidas de logs de la pestaña de soporte se vuelcan a io.Discard (server/controller/log_file_controller.go:76); solo persisten los metadatos.
5.1 Cuánto tiempo se conserva cada cosa, y adónde va
La retención la imponen barridos programados en server/taskworker, no una política. Las ventanas de abajo se leen de las constantes a las que llaman esos barridos, y son más cortas de lo que la política de privacidad — que no declara periodo de retención alguno — haría esperar a un lector.
| Datos | Se conservan | Se impone en |
|---|---|---|
| Las filas de conexión, y la ciudad/región/país, latencia y velocidad por conexión que cuelgan de ellas | 8 horas | taskworker/work/network_client_work.go:91; el borrado elimina en cascada network_client_location en la misma sentencia (model/network_client_model.go:2580) |
| Un cliente de nivel superior inactivo (sin autenticación, sin conexión) → se desactiva | 30 días | TopLevelClientIdleExpiration (model/network_client_model.go:2576) |
| Un cliente desactivado → borrado en duro, con cascadas | +30 días | NetworkClientReapAfterDeactivate (:2556) |
| Así que un dispositivo sin usar, de extremo a extremo | ~60 días | los dos plazos encadenados |
| Contratos completados | 7 días | taskworker/work/subscription_work.go:21-43 |
| Filas de auditoría | 180 días | model/audit_model.go:1018,1052 |
Dos notas que un auditor debería tener. La ventana de inactividad se estrechó de 90 días a 30 el 2026-07-18; la constante lleva su propia justificación, y un comentario obsoleto en taskworker/work/network_client_work.go:83-91 todavía dice 90, que es la fuente más probable de esa cifra si te la encuentras en otra parte. Y RemoveLocationLookupResults se programa en cada ciclo pero es un no-op — su cuerpo y su llamada al modelo están ambos comentados y no existe tal tabla. Es un stub vestigial, no datos sin barrer; la ubicación por conexión se borra con la fila de conexión de arriba.
La geolocalización nunca sale de la máquina. La consulta de ciudad/región/país lee desde el disco local la base de datos MaxMind GeoLite2-City empaquetada, mmdb/geolite2.mmdb (ipDb en server/ip.go), e ip.go no contiene ningún cliente HTTP ni hace ninguna llamada de red. El operador actualiza ese archivo con el cliente geoipupdate de MaxMind, una descarga que no envía a MaxMind nada sobre ningún usuario. This product includes GeoLite2 data created by MaxMind, available from https://www.maxmind.com. GeoLite2 incorpora datos de GeoNames, disponibles bajo CC BY 4.0. Ninguna dirección se envía a un servicio de geolocalización, y ninguna información personal se comparte con ningún tercero. Los únicos terceros de la tabla de partes de este documento retienen datos que el usuario les entrega directamente al elegir esa vía: los resolvedores DoH a los que llegan las consultas DNS del propio usuario a través del túnel, y los procesadores de pago con los que un usuario se da de alta para que se le facture.
El LB de entrada no registra las direcciones de los clientes, y eso es comprobable. La configuración de nginx del balanceador de carga la genera warp, que es código abierto bajo Apache 2.0 (warp/LICENSE), así que esto es código fuente que cualquiera puede leer y no una afirmación sobre el despliegue. Dos capas:
- El log de acceso usa un formato hecho a propósito que omite la dirección —
log_format noclientaddrlleva la hora, la conexión, el host, la petición, el estado, los bytes, los tiempos, el upstream y el user-agent, y los comentarios de la configuración nombran lo que falta deliberadamente:$remote_addr, y también$http_x_forwarded_fory$http_referer, "[ambas] pueden llevar una dirección de cliente reenviada desde otra parte" (warp/warpctl/config.go:1243-1254). - Al log de errores no se le puede dar formato, así que el campo
, client:propio de nginx se elimina en cambio aguas abajo: el LB ejecuta nginx con su stderr envuelto en unClientAddrScrubber(warp/nginx.go:98-103) que retira ese campo mediante una expresión regular y después sustituye todo literal IPv4 o IPv6 restante, en cualquier parte de la entrada, por[scrubbed], conservando el puerto (warp/nginx.go:111,116,176-232). Nada queda exento por rango, así que ninguna regla sobre qué direcciones pertenecen a usuarios puede estar equivocada.
Un test de regresión lo fija: TestNginxLogsOmitClientAddr (warp/warpctl/config_test.go:1208) falla si algún access_log no nombra un formato, porque el formato de reserva "combined" integrado en nginx empieza por $remote_addr.
Los contenedores de servicio se depuran en el descriptor, no en el punto de llamada. warp ejecuta los contenedores de servicio con --log-driver=journald (warp/warpctl/run.go:200) y no filtra su salida, así que durante un tiempo cualquier línea con una dirección que escribiera un servicio llegaba a journald tal cual. Los puntos de llamada eran reales y varios: server/http.go:413,453 construyen http.Server sin ErrorLog, así que la biblioteca estándar de Go escribe http: TLS handshake error from en cada conexión a medio abrir — el agujero que proxy/http.go:199,273 cierra con ErrorLog: discardLog; el SNI suministrado por el cliente se registra a nivel ERROR (server/tls.go:111); una dirección de llamante en crudo en server/proxy/proxy_device.go:858; la identidad del par WireGuard en server/proxy/server.go:699.
Esos ya no son la frontera, porque la frontera ya no es el punto de llamada. Cada servicio sustituye sus propios descriptores de archivo de stdout y stderr por tuberías en la primera sentencia de main y depura todo lo que se escribe en ellos (server.ScrubProcessLogs, server/scrub.go:62, llamado desde los ocho servicios — server/cli/{api,connect,alt,proxy,taskworker,gossip,monitor,mcp}/main.go). Operar sobre el descriptor en lugar de sobre el escritor significa que cubre log, glog, el ErrorLog por defecto de net/http, cualquier dependencia y cualquier punto de llamada añadido más tarde, sin que nadie tenga que encontrarlos. Usa el mismo warp.ScrubAddrs (warp/nginx.go:207) que el LB usa para nginx, así que hay una sola implementación del depurador y no dos que puedan divergir.
Dos límites deliberados, y un auditor debería sopesar ambos en lugar de aceptarlos por confianza:
- Los volcados de fallo pasan sin depurar. Una línea
panic:,fatal error:,signal SIGogoroutinedeja enclavado el depurador en modo de paso directo durante el resto de la vida de ese proceso (scrubPassthroughMarkers,server/scrub.go:48). Un volcado es raro y, cuando se produce, es lo más valioso del log, así que se conserva entero — aceptando que un marco de pila o un registro del procesador puedan llevar una dirección. El enclavamiento escribe un aviso visible cuando salta, así que un proceso que dejó de depurar no es algo que un operador tenga que deducir. - Depura lo que se analiza como una dirección. Las cadenas de versión y los identificadores separados por dos puntos que pueden analizarse como una dirección también se enmascaran. La consecuencia más visible es que
server/monitor/tailer.go, que extraeip:portde las líneas de log, ahora ve[scrubbed]:443.
Así que "un servicio no escribe tu dirección en sus logs" es ahora una propiedad del proceso y no de sus puntos de llamada. Lo que esto no establece es qué hace después journald con lo que recibe; §12 lo recoge como abierto. Detalle completo sobre los puntos de llamada originales: review/verified/PRIVACY-ENFORCEMENT.md §2.4.
Qué controla el operador que es fácil pasar por alto. El descubrimiento de proveedores es enteramente del lado del operador: FindProviders2 es el único endpoint vivo (server/api/api.go:140), la plataforma puntúa y clasifica a los candidatos (server/model/network_client_location_model.go:5056), y el cliente toma el conjunto clasificado que se le da. El único control del lado del consumidor es una lista de bloqueo de ubicaciones (server/api/api.go:149). Un cliente no tiene forma de verificar que los proveedores que se le ofrecieron son independientes entre sí o del operador.
6. Proveedores gestionados por el operador, y connivencia operador–proveedor
6.1 ¿Puede el operador ejecutar proveedores?
Sí, y nada en el sistema lo marca ni lo impide. Proveer está abierto a cualquiera sin atestación ni garantía depositada, y no hay ningún concepto de proveedor propio, oficial o de titularidad del operador en ninguna parte del código — las búsquedas en server, connect y sdk de tal noción no devuelven nada. Un proveedor gestionado por el operador sería indistinguible del de un miembro.
Combinado con el punto de §5 de que el operador clasifica y devuelve el conjunto de proveedores, esto significa que el operador puede, en principio, colocar sus propios proveedores en la ventana de un usuario. No hay comprobación de diversidad del lado del cliente, ni atestación de independencia, ni medición publicada de la flota por una parte externa. Los contrapesos que existen son reales pero parciales: la ventana sostiene varios proveedores a la vez — un conjunto de calidad de 2–6 (tope duro 12) y un conjunto de velocidad de 1–2 (tope duro 4), ambos vivos en el perfil por defecto (connect/ip_remote_multi_client.go:160-189) — y los proveedores se retiran ante estadísticas insanas, detección de agujero negro, fallo de ping y rotación de vida útil por canal (:37-54, :610-616). Esos acotan cuánto ve cualquier proveedor individual. No acotan un conjunto de proveedores elegido por una sola parte.
6.2 Qué reconstruyen juntos operador y proveedor, por modo
Asume que el operador y uno o más proveedores de tu ventana comparten lo que retiene cada uno.
| Retransmitido estándar | Retransmitido sellado | Directo | |
|---|---|---|---|
| Tu identidad (cuenta, correo/cartera/pago) | operador | operador | operador |
| Tu IP real en el momento de conectar | operador | operador | operador, y el proveedor directamente |
| Destinos que visitaste | operador (paquetes interiores) y proveedor | solo el proveedor | proveedor |
| Vínculo entre los dos | trivial — una parte ya retiene ambos | el contrato: audit_contract_event empareja la identidad del cliente y la del proveedor por contrato (server/db_migrations.go:325-347) | trivial |
| Resultado | atribución completa de la navegación para lo que transportaran los proveedores en connivencia | atribución completa de la navegación para lo que transportaran los proveedores en connivencia | atribución completa de la navegación, más el proveedor conoce tu dirección sin ayuda |
La conclusión honesta: la sesión sellada no defiende contra la connivencia operador–proveedor. Elimina la capacidad independiente del operador de leer tu tráfico; no impide que un proveedor que ya ve tus destinos se los entregue a un operador que ya sabe quién eres. El registro del contrato que une a los dos no es una fuga — es la contabilidad sobre la que funciona la red.
Lo que el sellado sí compra frente a este adversario es un límite de alcance: con él activado, la vista propia del operador no contiene destinos, así que la connivencia exige la cooperación de los proveedores concretos que transportaron el tráfico concreto, en el momento en que se transportó, en lugar de una consulta contra registros que el operador retiene por sí solo. Esa es una diferencia significativa en esfuerzo y en lo que la divulgación obligada puede alcanzar de forma retroactiva (§10). No es inmunidad, y ninguna disposición de tres partes en la que una seleccione a las otras proporciona inmunidad.
La respuesta estructural del diseño, que no está desplegada. El protocolo del cable y el cliente admiten encadenar proveedores intermediarios adicionales — MaxMultihopLength = 8 (connect/connect.go:15-18), IntermediaryIds en CreateContract (connect/protocol/transfer.proto:547-561), aceptación del cliente en connect/ip_remote_multi_client_api.go:661-662, fontanería de servidor en server/controller/connect_controller.go:915. El endpoint de descubrimiento vivo nunca puebla el campo — FindProvidersProvider se construye en exactamente dos sitios y ninguno lo establece (server/model/network_client_location_model.go:5044,5093; el campo se declara en :3693 y no se asigna en ninguno de los dos) — así que la red publicada asigna cadenas de longitud uno. El encadenamiento de proveedores es una capacidad del protocolo, no una propiedad del despliegue, y citar el número 8 como recuento de saltos sería engañoso.
7. Claves de identidad de proveedor, y si el operador puede sustituir una
Esta es la sección que más merece la pena atacar, porque la respuesta es incómoda y el código fuente lo dice él mismo.
7.1 Emisión y vinculación
Cada connect.Client — un cliente proveedor, y cada uno de los clientes de ventana del usuario — sostiene un par de claves Ed25519 generado en el proceso por ClientKeyManager (connect/transfer_key.go:87-143, la única llamada a ed25519.GenerateKey de los tres árboles). Es de vida larga allí donde se persiste y se recarga una semilla, que es el cliente proveedor, y fresca por proceso allí donde no la hay, que son los clientes de ventana; §9 cubre la diferencia y por qué importa. La mitad privada nunca sale del proceso; una semilla de tamaño incorrecto es un error duro de construcción en lugar de una identidad fresca y silenciosa (:84-94). La mitad pública se publica a la plataforma en un mensaje de control ClientKey (connect/protocol/transfer.proto:684-700) y se almacena del lado del servidor en Redis en ckey_ sin caducidad — Redis es la fuente de verdad, no hay tabla SQL (server/model/network_client_key_model.go:19-65).
La vinculación es por tanto client_id → public key, y el operador emite el client_id y retiene el mapeo. No hay ancla externa: los ids no se derivan de las claves, y no hay registro de transparencia ni DHT. Ambas alternativas están recogidas en el diseño como consideradas y aplazadas (connect/DESIGNNOTES.md §3.7, "Deferred alternatives").
7.2 Rotación y revocación
Volver a publicar sobrescribe — el propio comentario de SetClientKey dice "llaveado por client_id (la rotación sobrescribe)" (server/controller/connect_controller.go:1193-1199). La entrada se elimina cuando el id de cliente se purga (network_client_key_model.go:67-78, llamado desde RemoveDisconnectedNetworkClients). No hay rotación programada, ni caducidad, ni lista de revocación. Un cliente proveedor persiste su material de claves y lo recarga, así que su identidad es estable entre reinicios (sdk/device_local.go:3592-3597; sdk/local_state.go:788) — y, en Android, deliberadamente estable también a través de un borrado por cierre de sesión automático (§9.2). Un cliente sin semilla persistida genera una identidad fresca en cada arranque de proceso, que es lo que hacen los clientes de ventana del usuario. Los cambios de clave a mitad de sesión se rechazan: SetPeerClientPublicKey es de gana-la-primera-escritura, y una clave posterior distinta se registra y se ignora (connect/transfer_encrypt.go:2051-2096).
7.3 Verificación, y las dos defensas
- Defensa 1, vinculación por certificado firmado. El proveedor firma su cadena de certificados TLS efímera con su clave Ed25519 y publica ambas (
connect/protocol/transfer.proto:494-522). La plataforma adjunta la cadena, la firma y la clave pública del proveedor a cada contrato que nombre a ese proveedor (:340-368,:388-406). El cliente admite la cadena solo si la firma verifica bajo la clave pública del proveedor, y después comprueba el certificado presentado en el handshake contra la cadena admitida (connect/transfer.go:9560-9583, comprobado en:11928). - Defensa 2, prueba de identidad dentro del handshake. Tras el handshake TLS cada lado firma la salida del exportador de la RFC 5705 bajo la etiqueta
urnetwork-sequence-identity-proofy la envía como unEncryptedControl(connect/protocol/transfer.proto:615-660,701-712). El AEAD se retiene fuera de la ruta de envoltura hasta que la prueba del par verifica (connect/transfer_encrypt.go:1719-1726,:1719-1756), así que un MITM que termine el TLS en un tramo y rehaga el handshake en el otro produce exportadores discordantes y no puede falsificar la firma.
Ambas defensas verifican contra peerClientPublicKey — y ese valor se toma del contrato redactado por la plataforma (connect/transfer.go:9560-9583; connect/transfer_encrypt.go:2051-2096).
7.4 ¿Puede el operador sustituir la clave de un proveedor? Firmada, atribuible y mucho más difícil — pero no imposible.
Actualizado el 2026-09-17. Esta sección decía antes "Hoy: sí". Eso ya no es toda la respuesta, y el cambio es el segundo endurecimiento más significativo de la historia de este documento, después de la corrección del fallo en cerrado.
El ataque, sin cambios en su forma. Un operador que sustituya el certificado, la firma del certificado y destination_client_public_key al unísono derrota ambas defensas de §7.3, porque ambas verifican contra una clave que suministró el mismo contrato. Las notas de diseño lo dicen con las palabras del propio repositorio (connect/DESIGNNOTES.md §3.7).
Qué se interpone ahora. Con el Cifrado poscuántico activado (EncryptionModeRequired), el cliente retiene el cifrador de la sesión hasta que la clave de identidad suministrada por el contrato queda corroborada contra un historial de registro firmado que el operador ha publicado, y un desacuerdo verificado es terminal para ese proveedor — no fluye tráfico hacia él y se retira de la ventana.
Cada registro (connect/transfer_key_history.go:200-213) lleva el dominio del despliegue, una generación monótona, la clave de identidad, un enlace al hash de contenido de su predecesor y una firma secp256k1 del firmante raíz del operador. El cliente comprueba la codificación canónica, la recuperación de la firma, la contigüidad de la cadena desde la generación 1, la autoridad del dominio y del firmante, y el avance solo hacia delante respecto de una cabeza fijada (VerifyClientKeyHistory, :422). La compuerta vive en Cipher() (connect/transfer_encrypt.go:2699-2716) y la tabla de decisión en connect/transfer_key_history_session.go:157. Del lado del servidor: server/model/st_client_key_history.go almacena la cadena firmada, server/controller/st_client_key_history_public.go:77 la sirve, enrutada en server/api/api.go:226.
Así que el operador ya no puede ser inconsistente y, a la vez, negarlo. Para sustituir, tiene que comprometerse en forma firmada con una cadena, y ese artefacto es permanente, atribuible y divergente de lo que ve cualquier otro lector de ese id de cliente — incluidos los validadores independientes que ya auditan este registro. La comprobación cruzada más antigua y sin firmar contra GET /key/ sigue corriendo en paralelo y sigue siendo meramente consultiva, y registra CONTRACT vs FETCHED ... MISMATCH ... (today: log only) en connect/transfer_encrypt.go:2131-2134.
Cuatro precisiones que un auditor debería retener.
- Firmado no equivale a infalsificable por el firmante. Un operador malicioso desde tu primer contacto y coherente consigo mismo en ello firma una única cadena sin contradicciones que nombra su propia clave. Verificar la cadena comprueba la cadena, no la autoridad que hay detrás; el propio protocolo lo dice — "Quien llama debe autenticar por separado al firmante esperado en la versión de la cadena fijada del operador" (
sn/protocol/client_key_history.go:166-167). El cliente responde con firmantes fijados en la compilación más confianza en el primer uso (connect/transfer_key_history.go:491), lo que atrapa a un operador que se vuelve malicioso, que apunta a un subconjunto o que bifurca la cadena — y no a uno que nunca fue honesto. Cerrar eso exige la lectura plural a través de operadores gestionados de forma independiente, que está aplazada (connect/DESIGNNOTES3.md§10). - La omisión sigue siendo el movimiento más barato, y lo que la acota es el trinquete. La verificación del certificado se omite sin quedar fijada cuando el conjunto de confianza está vacío (
connect/transfer.go:11915-11926), y la prueba de identidad no puede verificarse en absoluto cuando la clave del par está ausente (connect/transfer_encrypt.go:1947-1950). Retener el historial firmado es el mismo movimiento un nivel más arriba. Lo acota un trinquete de niveles: un par verificado una vez contra evidencia firmada nunca vuelve a aceptarse sin ella, y una vez que un operador ha servido evidencia firmada a una instalación, un par sin ninguna se rechaza (connect/transfer_key_history_session.go:168-185, persistido ensdk/peer_client_key_pin_store.go). Un error de recuperación deliberadamente no se trata como un rechazo — de lo contrario una sola caída de la API excluiría a la vez a todos los proveedores para todos los usuarios —, así que un operador que rompe su propio endpoint degrada a los clientes a la clave del contrato. Ese es un intercambio de disponibilidad aceptado y un riesgo residual real. - Todo ello está acotado al conmutador. La imposición solo corre bajo
EncryptionModeRequired(connect/transfer_key_history_session.go:91), porque rechazar bajo Opportunistic degrada a texto plano, que es lo que quería un operador que sustituye. Con el conmutador todavía publicándose desactivado (§2.2), un cliente con la configuración por defecto no obtiene nada de esto. - La autenticidad del contrato también está enraizada en el operador. Un proveedor verifica el HMAC del contrato usando una clave secreta de provisión que el proveedor genera y publica a la plataforma (
server/controller/connect_controller.go:727,840;sdk/device_local.go:3592-3597). El operador retiene una copia, que es lo que le permite redactar contratos siquiera. Eso es lo esperable de una autoridad contable, pero significa que "el contrato es genuino" no es una afirmación independiente del operador.
Qué socava y qué no socava esto. No toca la ceguera del proveedor a la identidad, que no depende de ninguna distribución de claves. La ceguera del operador al contenido en la ruta sellada ya no es una mera propiedad de política: la sustitución le cuesta ahora al operador un artefacto firmado, permanente y detectable por terceros. Sigue sin ser una propiedad que se sostenga frente a un operador decidido que nunca fue honesto, y cualquier documento de URnetwork que describa la sesión sellada como algo que hace incondicional la ceguera del operador la está exagerando. Este documento reemplaza tal redacción.
8. Correlación de temporización y de tráfico, y el observador pasivo global
8.1 No hay defensas frente al análisis de tráfico. Ninguna.
URnetwork no incorpora tráfico de cobertura, ni relleno, ni mezclado, ni retardo por lotes, ni conformado de tráfico sobre los datos del usuario. Esto se comprobó de forma exhaustiva en connect, sdk y server:
- No existe ningún generador de paja, señuelo, relleno ni tráfico de cobertura en ninguna parte.
- El AEAD preserva la longitud por construcción: la longitud del texto cifrado es nonce + texto plano + tag (
connect/transfer_encrypt.go:303-336). Ningún protobuf deconnect/protocol/lleva un campo de relleno, y el encuadre es un prefijo de longitud pelado de 4 bytes (connect/message_framer.go:29-31). - La agrupación del lado del envío es explícitamente de retardo cero: "No hay espera de agrupación: la secuencia solo toma un segundo Pack cuando ya está encolado" (
connect/transfer.go:2971). Todojitterde los árboles es retroceso de reconexión o dispersión de vida útil de canal, no conformado de tráfico. La compresión de acuses (connect/transfer.go:272) retrasa solo los acuses de recibo, para recortar el volumen de mensajes de retransmisión. - El único lugar donde el tráfico del usuario se acompasa siquiera es el tope
WritePacketsPerSeconddel transporte con forma de DNS (connect/transport_pt.go:68,359-361), que existe para ser amable con los resolvedores y aplica solo al modo de transporte menos preferido. Sus consultas de "bombeo" en reposo tienen una longitud distinguible de las que llevan datos, así que no oculta ni el tamaño ni la tasa.
Por lo tanto: un adversario que observe el tráfico que entra en tu dispositivo y el tráfico que sale de los proveedores que lo transportan puede correlacionar los dos por tamaño y temporización. URnetwork no defiende contra ese adversario. Esta es la misma afirmación que hace Tor sobre la correlación de extremo a extremo, y aquí aplica con menos margen, porque el objetivo de diseño de URnetwork es la baja latencia — que es exactamente la propiedad que facilita la correlación. Una ruta de cuatro tramos acotada en latencia es un intercambio deliberado frente al retardo añadido de una mixnet, y este es el lado de ese intercambio que paga el usuario. Contar el tramo del extensor no lo cambia: ese salto reenvía la sesión cifrada hacia el operador sin terminarla, así que no añade ninguna capa que un observador pueda pelar ni ningún retardo en el que pueda perderte.
Dos cosas se confunden a veces con defensas y no lo son. Los extensores y los transportes conformados (connect/net_resilient.go:114-215, connect/transport_pt.go:37,73) apuntan al bloqueo basado en DPI, y la capa resiliente se desactiva a sí misma una vez el stream está establecido (net_resilient.go:102,146-161) — nunca toca los registros de la fase de datos. Las cachés de tickets de sesión TLS deliberadamente no se comparten entre rutas de salida para que un servidor no pueda enlazarlas a través de un ticket canjeado (connect/net_tls.go:38-42,99-103); eso es no vinculabilidad de tickets, no análisis de tráfico.
Nada en el código fuente reconoce la correlación de tráfico como una limitación: una búsqueda de traffic analysis, timing correlation, traffic correlation y global adversary en los tres árboles devuelve cero resultados. El modelo de amenazas interno del repositorio (connect/DESIGNNOTES.md §3.7) va enteramente sobre el MITM del operador. Este documento es el primer lugar donde se escribe el hueco.
8.2 La ventana multiproveedor no es un mecanismo anticorrelación
El tráfico sale normalmente por varios proveedores a la vez — habitualmente de tres a ocho entre las dos ventanas — y la afinidad por sitio mantiene un sitio dado en un proveedor (connect/ip_remote_multi_client.go:160-189,837). Eso limita genuinamente cuánto ve cualquier salida individual. Pero la razón de diseño de la ventana en el código fuente es la fiabilidad de principio a fin — mitigación de destinos malos, redimensionado ponderado por salud, detección de agujeros negros (:27-52) — y el único comentario que dice "menos afinidad es más privado" está sobre ClientAffinityTimeout, un mando que está comentado (:587-591, y de nuevo en los valores por defecto en :220), con DestinationAffinity publicándose en true por razones de fiabilidad (:270). La identidad de salida plural es un beneficio real y es justo reclamarlo; reclamarla como una defensa diseñada contra la correlación no está respaldado por el código.
8.3 El observador pasivo global: fuera del alcance
Un adversario pasivo global — capaz de observar una fracción grande de los enlaces de internet simultáneamente — queda fuera del alcance, y URnetwork no proporciona ninguna defensa contra él. Sin relleno y sin tráfico de cobertura, tal adversario correlaciona flujos a través de la retransmisión y de los proveedores directamente. Esta es la misma posición que toma Tor y, a diferencia de Tor, URnetwork ni siquiera añade un retardo por salto que elevaría el coste.
Lo que sí es genuinamente distinto, y merece la pena decir sin inflarlo: el conjunto de salidas es una población cambiante de conexiones residenciales independientes en lugar de los rangos de direcciones publicados de un solo operador, así que un adversario tiene que observar un conjunto de extremos más amplio y cambiante para cubrir a un solo usuario, y los extremos no pueden enumerarse desde una lista de servidores. Eso eleva el coste. No cambia el resultado para un adversario que ya puede ver ambos extremos.
9. Identificadores de dispositivo y vinculabilidad
9.1 Qué puede observar un proveedor sobre ti
El SourceId del contrato es un id de cliente por plaza de ventana, no tu dispositivo. StoredContract.SourceId es el client_id del llamante autenticado (server/controller/connect_controller.go:816-830; analizado por el proveedor en connect/transfer.go:6088-6106). El generador acuña un id de cliente, un jwt y un id de instancia frescos para cada entrada de ventana y lo retira al desmontarla — el código fuente lo dice directamente: "El generador de la api acuña un id de cliente de plataforma efímero (con un id de instancia fresco) para cada entrada de ventana y lo retira al desmontarla" (connect/ip_remote_multi_client_identity.go:10-15; acuñado mediante AuthNetworkClient en connect/ip_remote_multi_client_api.go:691-727, liberado mediante RemoveNetworkClient en :802-832). Encima de eso, la pertenencia a la ventana cambia constantemente: los proveedores salen ante estadísticas insanas, detección de agujero negro, fallo de ping y rotación de vida útil por canal (connect/ip_remote_multi_client.go:37-54), así que un id de plaza es de vida corta por construcción y no por política.
Tu tráfico se reparte entre varios proveedores a la vez, así que ninguna salida individual ve al cliente. El perfil por defecto corre un conjunto de calidad de seis y un conjunto de velocidad de uno a dos (connect/ip_remote_multi_client.go:160-189). Cada proveedor sostiene una porción de tu actividad bajo su propio id de plaza, y la afinidad por sitio mantiene un sitio dado en un proveedor en lugar de esparcirlo entre todos ellos (:837). Esto acota genuinamente lo que puede reconstruir una sola salida honesta pero curiosa. No es una defensa anticorrelación, y §8.2 explica por qué.
Los ids de ventana y tu id de rol de proveedor son espacios de identidad separados, así que un proveedor no puede remontar un id de plaza hasta ti. Si además compartes tu conexión, tu cliente proveedor es el cliente de nivel superior que sostiene la semilla de clave duradera y persistida (§9.2). Los clientes de ventana son clientes distintos: se construyen a partir de un DefaultClientSettingsWithBufferSize fresco (sdk/device_local.go:4349) que no lleva ClientKeySeed, así que cada uno genera su propio par de claves por proceso, y tu propio id de cliente queda excluido explícitamente de tu propio conjunto de candidatos (sdk/device_local.go:4338). Un proveedor que tenga un id de plaza y lo consulte mediante el GET /key/ no autenticado obtiene por tanto una clave que es fresca para ese proceso y no enlaza con nada duradero — ni con tu identidad de proveedor, ni entre reinicios, ni entre plazas. La clave duradera y resoluble públicamente de §9.2 pertenece al rol de proveedor, y nada en la ruta de salida la expone.
El propio id de cliente del dispositivo no llega a ningún proveedor en la ruta de salida, y el rastro es lo bastante corto para comprobarlo. DeviceLocal.sendPacket sí establece source := connect.SourceId(self.clientId) — el id de nivel superior del dispositivo — y lo entrega al multicliente (sdk/device_local.go:4870, forma por lotes en :4968). Ese valor se usa solo para contabilidad local: para llavear el reensamblado de fragmentos de salida y la comprobación de relación de provideMode (connect/ip_remote_multi_client.go:5876-5906). El envío al cable lo hace el cliente de ventana de cada plaza — self.client.SendMultiHopWithTimeoutDetailed(frame, self.args.Destination, …) (connect/ip_remote_multi_client.go:14941-14948), sobre un Client que el generador acuñó para esa plaza (:13094) — y el id de origen del dispositivo no está entre sus argumentos. Un grep de source en esa región de envío no devuelve nada.
La única identidad duradera es el rol de proveedor, y apunta en la otra dirección. Un dispositivo que comparte su conexión corre un segundo cliente construido en sdk/device_local_provider.go:198 con ProviderStreamPolicy = true (:191) y la semilla de clave persistida. Las relaciones entre pares de red y el tráfico de retorno del lado del proveedor viajan sobre ese cliente, así que llevan el client_id estable de nivel superior y la clave duradera. Esa es la identidad bajo la que sirve un dispositivo, no aquella bajo la que navega: queda expuesta a los consumidores a los que sirve un proveedor, lo que §9.2 documenta por completo, y nunca se entrega a las salidas que transportan el tráfico propio de un consumidor. Los dos espacios de identidad son los descritos más arriba, y no se cruzan.
Pero esas identidades se reutilizan deliberadamente hasta cuatro horas, en todas las rutas — no solo en las alojadas. El dispositivo instala su propio almacén local de identidades salvo que esté alojado (sdk/device_local.go:824-830), y una identidad de ventana almacenada se reproduce contra el mismo destino hasta que caduca: windowIdentitiesStaleAfter = 4 * time.Hour (sdk/window_identity_store.go:42-45). El propósito es mantener vivos los flujos NAT de un proveedor a través de un reinicio (connect/ip_remote_multi_client_identity.go:17-23). El coste es que un proveedor puede ver reaparecer el mismo SourceId viniendo de ti a través de reinicios de la app dentro de esa ventana. Di cuatro horas, no "efímero".
La clave de identidad Ed25519 del cliente es fresca por cliente de ventana, en la ruta de salida. Los ajustes del cliente de ventana se construyen a partir de un DefaultClientSettingsWithBufferSize fresco (sdk/device_local.go:7105), que no lleva ClientKeySeed (connect/transfer.go:1547-1558), así que ClientKeyManager genera un par de claves nuevo (connect/transfer_key.go:113). La instantánea persistida de la ventana almacena solo el id de cliente, el jwt y el id de instancia — ninguna semilla de clave (sdk/window_identity_store.go:42-45), así que una identidad restaurada vuelve a publicar una clave nueva. Con el Cifrado poscuántico desactivado, no se presenta ninguna prueba de identidad en absoluto (connect/ip_remote_multi_client.go:13080-13091).
La dirección de origen del túnel es por sesión y de baja entropía. El SDK asigna a su TUN una dirección RFC1918 aleatoria — "un 10.x.y.h aleatorio (RFC1918, con forma de DHCP)" (sdk/device_local.go:7105) — generada con un octeto de host entre 2 y 254 sobre el 10.a.b.0/24 sin conflicto más pequeño (connect/tun.go:517-523). Se computa en el constructor y nunca se persiste, así que cambia en cada sesión. El proveedor sí la ve: es el campo de origen de cada paquete tunelizado y el proveedor llavea el estado NAT sobre ella (connect/ip.go:731,784). Unos 253 valores son una huella débil por sesión, no un identificador entre sesiones.
9.2 La clave de identidad del rol de proveedor es duradera
Si además compartes tu conexión, tu cliente proveedor sostiene un par de claves Ed25519 de vida larga cuya semilla se persiste al almacenamiento local (sdk/local_state.go:788, .device_local_key_material, el JSON client_key_seed; aplicado en sdk/device_local_key_material.go:32,90). A diferencia de los clientes de ventana, esa clave se preserva deliberadamente a través de un borrado por cierre de sesión automático — el comentario de Android es explícito: "Limpiar un estado de autenticación caduco o parcial SIN rotar la identidad del dispositivo. El material de claves de identidad es de ámbito de dispositivo, no de ámbito de sesión" (android/.../MainApplication.kt (clearStaleAuthState)). El refresco de token, la recuperación de autenticación parcial y el reinicio de sesión a los 30 días de inactividad producen por tanto un client_id nuevo y un device_id nuevo mientras la clave pública Ed25519 sigue siendo la misma. Solo un cierre de sesión explícito del usuario la rota (sdk/local_state.go:788).
Esa clave se publica, se sella en cada contrato que nombre a este cliente como destino (server/controller/connect_controller.go:744-770,816-830), y es legible por cualquiera: GET /key/ no está autenticado por diseño (server/api/api.go:222-223; server/controller/connect_controller.go:1227). Así que cualquier parte — no solo un proveedor al que hayas servido — puede resolver cualquier id de cliente a su clave pública y agrupar los ids de cliente que comparten una. Para una cuenta de proveedor, eso es un asidero duradero entre sesiones, entre client_id y entre device_id. Este es un coste real de proveer, y no está documentado en ningún otro lugar del corpus.
9.3 En el operador, todo enlaza
- El
client_idnunca se revoca. El propio comentario del código: "los client_id son direcciones globalmente únicas equiparables a IPv6 / nunca se revocan una vez asignados, para preservar los registros de seguridad y de auditoría" (server/model/network_client_model.go:74). Un cliente de nivel superior se desactiva tras 30 días de inactividad (TopLevelClientIdleExpiration,:2576) y se borra en duro 30 días después (NetworkClientReapAfterDeactivate,:2556), en cascada hacia la fila del dispositivo y elckeyde Redis (:2537). - El
device_ides por inicio de sesión, no por hardware ni por instalación: se acuña uno fresco con cada cliente de nivel superior (:332-352), y el código señala la "rotación de identidad resultante (un device_id fresco por inicio de sesión)" (:2280). Los clientes de ventana lo heredan (:356-380) y nunca se envía a un proveedor. - El token de red es un JWT de 24 horas (
server/jwt/by_jwt.go:55-67,238), refrescado por el SDK a la mitad de su vida — unas 12 horas — con jitter (sdk/device_token_manager.go:172-217). Refrescar el token no rota el id de cliente; se vuelve a acuñar la misma reclamación (server/model/network_client_model.go:74). audit_contract_eventregistra la identidad del cliente y la del proveedor por contrato (server/db_migrations.go:325-347).client_reliabilityune el hash con clave del bloque de IP directamente a un id de cliente, por bloque: la clave primaria original de cuatro columnas (server/db_migrations.go:325-347) se redujo después a(block_number, client_address_hash, client_id)(:2191-2195), y la tabla particionada en vivo usa esas mismas tres (server/model/network_client_reliability_partition_model.go:194), connetwork_idsobreviviendo como carga útil de índice (:63,631-634). La retención es de 30 días (server/model/network_client_reliability_partition_model.go:52-69; las particiones diarias se descartan enteras).network_client.auth_timees un último-visto duradero retenido 30 días (server/model/network_client_model.go:2556,2576); las filas de conexión desconectadas se purgan a las 8 horas (server/taskworker/work/network_client_work.go:85).- Identificadores a nivel de cuenta, solo en el operador: correo o teléfono en claro en
network_user.user_auth, el JWT completo del IdP de terceros para los inicios de sesión SSO, identificadores en claro en cada intento de inicio de sesión enuser_auth_attempt, direcciones de cartera, filas de pago, una arista persistente de quién-invitó-a-quién ennetwork_referral, y filas deaudit_provider_eventque llevan cadenas literales de país, región y ciudad contranetwork_idydevice_id, no atadas a la purga de conexiones a las 8 horas (review/verified/PRIVACY-ENFORCEMENT.md§1.4). - El
User-Agentse registra por la lista de permitidos de cinco cabeceras (server/http_log.go:13-29). Es la única entrada de las cinco que es una superficie de huella digital.
9.4 El puente WireGuard: la dirección de túnel pasa por NAT antes de la salida
A cada cliente WireGuard se le asigna una dirección de túnel privada estable de un pool barajado de 10.000.000 de direcciones RFC1918 (server/model/network_client_proxy_model.go:774), escrita en la configuración como la dirección de la interfaz (:756-771). Esa dirección es duradera: forma parte de la configuración del par y sobrevive a todas las sesiones.
No llega a los proveedores. El dispositivo alojado sustituye en la salida una dirección por dispositivo y restaura la del propio par en el retorno. La sustituta se toma una sola vez al construir el dispositivo del mismo pool 169.254.0.0/16 que usa el tun de la ruta de la app (server/proxy/proxy_device.go:954, mediante connect.TakeLocalIpv4Address, connect/tun.go:471) y se devuelve al cerrarlo, así que es por dispositivo y no por cuenta, y se extrae del mismo espacio que cualquier otra dirección de túnel local en lugar de ser distintiva. La reescritura es natRewriteEgress (server/proxy/proxy_device.go:1297) y natRewriteReturn (:1314), sobre connect.RewriteIpv4Source/RewriteIpv4Destination (connect/ip_nat_rewrite.go:71,77), que reparan de forma incremental la suma de comprobación de la cabecera IPv4 y cualquier suma de comprobación de transporte con pseudocabecera. Los paquetes de retorno se emparejan por la dirección sustituida, ya que es a ella a la que el proveedor los dirigió (receiveWithNotifyNat, server/proxy/proxy_device.go:1339).
Tres límites que merece la pena enunciar, porque esto elimina un vínculo y no otros. Solo se reescribe el tráfico propio del par conectado, así que un dispositivo que además sirva HTTP o SOCKS deja esos flujos intactos. Si el pool local se agota, la reescritura se desactiva a sí misma en lugar de descartar tráfico, lo que significa que el comportamiento antiguo sigue siendo el modo de fallo — una elección de disponibilidad, y la razón por la que la propiedad es "normalmente se sostiene" y no "no puede fallar". Y cada proveedor de una misma ventana sigue viendo la misma dirección sustituida mientras dure ese dispositivo, exactamente igual que la dirección del tun de la ruta de la app se comparte entre los proveedores de una sesión (§9.1); lo que se elimina es el asidero duradero de cuenta y entre sesiones, no la correlación dentro de una sesión.
El comportamiento lo fijan TestWgNatReplacesThePeerAddressOnEgress, TestWgNatRoundTripIsExact, TestWgNatLeavesOtherSourcesAlone y TestWgNatReturnIsMatchedOnTheNatAddress (server/proxy/proxy_device_nat_test.go), y la aritmética de las sumas de comprobación la fijan doce tests de connect/ip_nat_rewrite_test.go que verifican contra un recálculo completo en lugar de reproducir las cuentas incrementales.
Combinado con el resolvedor fijo 1.1.1.1 (§3.2) y la ausencia de sesión sellada en esa ruta (§2.4), el puente WireGuard sigue siendo la forma menos privada de usar la red. Las apps y el SDK son la más privada.
10. Requerimientos legales
Jurisdicción. La contraparte es BringYour, Inc., una sociedad de Delaware del tipo C corporation (docs/legal/terms.md:8-9 nombra la entidad; el estado de constitución no figura allí), con dirección para notificaciones en 2261 Market Street #5245, San Francisco, CA 94114 (docs/legal/terms.md:265-271). La ley aplicable es la de Texas y la sede judicial es el condado de Harris, Texas (docs/legal/terms.md:314-321) — una elección deliberada, y no una incoherencia con la dirección de California: los abogados de la empresa están en Texas. El único hueco con el que se topa un lector es que los términos publicados no declaran ni el estado de constitución ni un agente registrado, así que los datos societarios necesarios para notificar un proceso no pueden establecerse solo a partir de ellos. Todo ello es estadounidense, y todo ello es alcanzable por proceso legal obligatorio de EE. UU., incluido el proceso que llega con una orden de silencio. Los términos declaran que el operador "puede monitorizar y divulgar información cuando la ley o una orden gubernamental le obliguen a ello" (docs/legal/terms.md:204-206).
Qué alcanza un requerimiento al operador. En orden descendente de poder desanonimizador: quién es la cuenta — correo o teléfono en claro, el JWT del IdP de terceros para los inicios de sesión SSO, direcciones de cartera, y registros de pago que atan a una identidad real vía Stripe, Apple, Google Play o un tx_signature en cadena; cuándo se conectó y desde qué ciudad, por conexión; qué proveedores usó y por cuántos bytes; y un hash con clave del bloque /29 o /56 desde el que se conectó, más el puerto de origen en claro. Los terceros retienen más: los procesadores de pago nombrados en §1 retienen identidad que el operador nunca almacena, y un tx_signature de Solana resuelve a una cartera en una cadena pública.
Qué no alcanza un requerimiento, porque no existe. El registro de transferencias no tiene campo alguno de destino, host, URL, SNI, puerto ni dominio en ninguna parte (review/verified/PRIVACY-ENFORCEMENT.md §1.2). Los logs de soporte subidos se descartan al recibirlos. No hay ningún registro de navegación que entregar, en ninguna ruta, sellada o no — esta es la propiedad estructural más fuerte de este documento, y se sostiene se comporte quien se comporte.
Qué alcanza un requerimiento de forma prospectiva. Todo lo anterior va sobre registros almacenados. Un requerimiento que obligue a una conducta futura es otra cosa, y §7.4 es donde muerde: un operador obligado a interceptar a un usuario concreto podría, hoy, sustituir u omitir material de claves de proveedor y leer el tráfico de ese usuario incluso con el Cifrado poscuántico habilitado, con la única señal siendo una línea de log de dispositivo. La sesión sellada eleva el coste de la divulgación retrospectiva. No resiste, en su forma actual, a un operador obligado de cara al futuro.
La eliminación de cuenta es parcial. RemoveNetwork es una lista de borrado escrita a mano, no una cascada de base de datos (server/model/account_model.go:108). Retira las filas de network_user y sus registros de autenticación (contraseña, SSO, cartera, frase de recuperación), la fila network y la entrada del índice de nombres, y programa una baja de suscripción en Stripe. No borra account_payment, stripe_customer, apple_subscription_transaction, las filas solana_payment_intent completadas, network_referral, las filas de dispositivo ni las filas de auditoría; esas caducan según sus propios calendarios — los eventos de auditoría a los 180 días (server/model/audit_model.go:1018,1052) — o, para los pagos en cadena completados, no caducan en absoluto (server/model/solana_payment_intent_model.go:96,137 borra solo los intentos con un tx_signature nulo). La eliminación además escribe una fila: un evento AuditEventTypeNetworkDeleted llaveado sobre el network_id eliminado (account_model.go:108). El "eliminar su cuenta y la información personal asociada" de la política de privacidad (docs/legal/privacy.md:69) es más amplio que lo que hace el código.
No hay canario de órdenes judiciales, ni informe de transparencia, ni proceso publicado para las fuerzas del orden. Verificada su ausencia en docs/ y en el código fuente del sitio ur.io. No hay ninguna dirección para procesos legales: [email protected] está acotado a los informes de vulnerabilidades (docs/legal/vdp.md:42) y [email protected] es la dirección general de notificaciones contractuales (docs/legal/terms.md:265), y ninguna de las dos es un canal para la notificación de procesos. Una parte que quiera notificar a la empresa no tiene en los términos publicados nada sobre lo que actuar más allá de la dirección para notificaciones de San Francisco. Los propios documentos comparativos de URnetwork ya conceden la falta de canario y de informe de transparencia frente a competidores que sí publican uno, y este documento lo repite en lugar de suavizarlo.
La política de privacidad calla donde debería ser específica. Declara que "toda la información personal que recogemos de los usuarios y sobre ellos se limita a la dirección de correo electrónico o el número de teléfono" y que la recogida ocurre "directamente de ti cuando la proporcionas" (docs/legal/privacy.md:29-37).
Un borrador anterior de esta sección llamó a eso una descripción del sistema por debajo de lo real, sobre la base de que el código almacena más categorías de las que nombra la política. Ese planteamiento se retiró el 2026-08-09 por erróneo. Todo lo demás inventariado en este documento o bien es información de cuenta que el usuario crea al darse de alta o al pagar (la clave de unión de Stripe existe porque alguien aceptó que se le facturara), o bien no es información personal en absoluto: un puerto de origen, y un hash de bloque con clave que el operador no revierte. Que un hash con clave pudiera en principio ser invertido por su propio poseedor es una salvedad de ingeniería real — §5 la enuncia, y la falta de rotación es una debilidad genuina — pero un valor que nadie consulta no es una categoría de información personal que la política haya dejado de declarar.
Lo que de verdad le falta a la política es distinto y más acotado: ningún periodo de retención, ninguna declaración sobre registros, y ninguna sección sobre fuerzas del orden o divulgación gubernamental. Las cadenas retención, log, dirección IP, citación y proceso legal no aparecen en ella. Eso importa porque la disciplina de retención existe en el código y es más fuerte de lo que la política afirma — §5.1 expone las ventanas verificadas. Un usuario que lea hoy la política no puede sujetar al operador a ninguna de ellas.
11. Contra qué no defiende URnetwork
Tres cosas se sostienen, y merece la pena nombrarlas antes de la lista que sigue, para que la lista se lea como una frontera y no como un veredicto. En una ruta retransmitida un proveedor nunca averigua quién eres, y ningún ajuste ni compilación de proveedor cambia eso (§2). No hay campo alguno de destino, host, URL, SNI, puerto ni dominio en ninguna parte del registro de transferencias, así que no hay ningún registro de navegación que se pueda requerir, filtrar ni vender (§5, §10). Y cada una de estas afirmaciones es legible en código fuente público, que es la razón por la que este documento puede ser específico sobre sus propios fallos.
Todo lo de abajo es un fallo. Dicho sin rodeos; cada punto está desarrollado más arriba.
- Correlación de temporización y de tráfico de extremo a extremo. Sin relleno, sin tráfico de cobertura, sin mezclado. Un adversario que observe tanto tu red de acceso como los proveedores que transportan tu tráfico puede correlacionarlos. §8.1.
- Un adversario pasivo global. Enteramente fuera del alcance. §8.3.
- Connivencia operador–proveedor. El operador sabe quién eres, el proveedor sabe adónde fuiste, y el registro del contrato los une. El sellado estrecha lo que el operador retiene por sí solo; no rompe la unión. §6.2.
- Un operador que nunca fue honesto, sustituyendo material de claves de proveedor. La sustitución cuesta ahora un artefacto firmado, permanente y detectable por terceros, y un operador que se vuelve malicioso o es inconsistente queda atrapado. Uno que es malicioso y coherente consigo mismo desde tu primer contacto sigue ganando, porque el cliente no tiene una vista independiente del operador sobre qué firmante tiene autoridad. §7.4.
- Ningún sellado en absoluto CON el Cifrado poscuántico DESACTIVADO, el valor por defecto publicado. En ese modo el cliente nunca inicia una sesión, así que su tráfico va sin sellar desde el principio, sin indicación visible para el usuario. Corregido para el modo que lo pide, 2026-08-10: con el conmutador activado, el cliente falla en cerrado y se niega a enviar o aceptar datos de aplicación en texto plano. Lo que sobrevive en ambos modos es el indicador ausente — ninguna app muestra si una conexión dada está sellada. §2.2.
- Un proveedor malicioso manipulando tráfico sin cifrar. El HTTP en texto plano se pasa sin cambios; el proveedor está en la posición de un punto de acceso hostil. §3.
- Compromiso del extremo. Malware, un sistema operativo comprometido, una extensión de navegador hostil, o cualquiera con tu dispositivo desbloqueado. Nada en un producto de red aborda esto.
- Identificación del lado del destino. Las cookies, los inicios de sesión, la huella digital del navegador y el comportamiento de cuenta te identifican ante los sitios que visitas con independencia de cómo llegaran los paquetes. Cambiar tu dirección de salida no te hace anónimo ante un servicio en el que inicias sesión.
- Identidad por la vía de pago. Las vías de tarjeta y de tiendas de apps ponen tu identidad en manos del procesador aunque el operador no la almacene. El USDC en cadena deja un
tx_signaturepúblico. - Proveedores Sybil. Cualquiera puede proveer, sin atestación ni garantía depositada. Una sola parte puede ejecutar muchos proveedores, incluido el operador.
- La falta de sellado en las rutas del navegador y del proxy. La extensión de navegador no tiene ajuste de Cifrado poscuántico, y en esas rutas el operador ejecuta el cliente. §2.4.
- Correlación dentro de una sesión en el puente WireGuard. La dirección de túnel duradera del par pasa por NAT antes de la salida, así que ya no sigue a una cuenta entre sesiones — pero cada proveedor de una misma ventana sigue viendo la misma dirección sustituida, exactamente igual que ven una sola dirección de tun en la ruta de la app. Y si el pool de direcciones locales se agota, la reescritura se desactiva a sí misma en lugar de descartar tráfico, así que el comportamiento previo al NAT sigue siendo el modo de fallo. §9.4.
- Un asidero de identidad duradero para cualquiera que además provea. La clave Ed25519 del cliente proveedor sobrevive por diseño a la rotación del id de cliente y del id de dispositivo, y
GET /key/no está autenticado, así que cualquier parte puede agrupar los ids de cliente que comparten una clave. §9.2. - El tráfico que ve la red local durante la ventana de arranque del DNS. Documentado en el código fuente como un coste aceptado. §3.2.
- Cualquier cosa que habría encontrado una auditoría de terceros. No se ha realizado ninguna sobre el protocolo ni sobre el código de servidor.
12. Lo que este documento no pudo establecer
Una afirmación no establecida en un modelo de amenazas es una afirmación que tampoco debería estar en la documentación. Estas quedan abiertas.
- La retención y el reenvío de journald en los hosts. Los logs de servicio se depuran ahora en el descriptor antes de salir del proceso (§5), así que lo que llega a journald no debería llevar direcciones fuera de un volcado de fallo. Lo que esta revisión del código fuente no puede establecer es cuánto tiempo conserva journald lo que recibe, adónde lo reenvía, y si algún volcado de fallo — que pasa sin depurar por diseño — se retiene más tiempo que el resto. Esa es una cuestión de despliegue, no de código.
- Si la resolución de nombres en la ruta del navegador puede llegar a ocurrir localmente. La extensión configura por defecto un proxy HTTPS CONNECT, que resuelve de forma remota, y la resolución de nombres de SOCKS pasa por el marcador DoH del túnel (
connect/tun.go:517-523). No se probó el comportamiento del DNS del proxy en cada navegador bajo todas las configuraciones. - La verbosidad real en producción.
BY_LOG_Vvale 0 por defecto (server/env.go:46), lo que suprime el registro de destinos gobernado porV(1)en cliente y servidor — pero el único manifiesto de despliegue del árbol lo pone a2(xops/gitops-unused/.../api/deployment.yaml:47-48), en un directorio llamadogitops-unused. Trata la verbosidad como una bandera de tiempo de ejecución, nunca como una garantía estructural. - Independencia de la flota. El operador publica recuentos en vivo de ciudades y países desde su propio agregado sobre proveedores válidos actualmente conectados (
server/model/network_client_location_model.go:3525), y la ubicación la deriva el operador de la conexión que observa en lugar de declararla el proveedor (review/verified/ARCHITECTURE.md§6.4). Pero ninguna parte independiente ha medido la flota desde fuera, y nada verifica que los proveedores ofrecidos a un cliente concreto sean independientes entre sí o del operador. El mecanismo es comprobable en el código fuente; la población no es comprobable por un tercero hoy. - La vinculación de la clave de identidad en la creación de la cuenta. El diseño declara la expectativa de que "la vinculación (ClientId, clave pública) se registra en la creación de la cuenta" (
connect/transfer_key.go:60-85). Lo que se publica es un cliente publicando su clave a un Redis controlado por el operador, servida de vuelta por una API controlada por el operador. Si existe operativamente un registro más fuerte no pudo establecerse desde el código fuente; asume que no. - Si
RolesyPrincipalse pueblan alguna vez para clientes de consumo ordinarios. Son cadenas asignadas por el operador selladas dentro de los bytes firmados del contrato y establecidas solo paraProvideMode_Network(connect/protocol/transfer.proto:547-561;server/controller/connect_controller.go:831-838). No se encontró evidencia de que las apps las pueblen, y no se auditó a cada llamante. Si alguna vez se poblaran para tráfico de consumo serían identificadores entre sesiones de primera clase visibles para un proveedor. - Persistencia del material de claves en hosts no móviles.
ClientKeySeedse referencia solo desdesdk/local_state.go,sdk/device_local_key_material.go,sdk/device_local.goysdk/cgo/. Los hosts que no llaman a esos obtienen una clave fresca por proceso; el comportamiento de Linux, Windows, la extensión y el proxy de servidor no se verificó individualmente.
13. Reportar
Vulnerabilidades: [email protected] y la política de divulgación en ur.io/vdp. Las correcciones a este documento, incluidos los desacuerdos con cualquier veredicto que contenga, son bienvenidas por la misma vía — un hallazgo independiente que contradiga una afirmación de aquí es más útil que la afirmación.