# 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](https://support.torproject.org/about-tor/security/attacks-on-onion-routing/):
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-08-07. 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.

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.

**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 se publica activada por defecto 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).

**Los defectos de integridad: uno corregido, otro abierto.** El sellado solía
fallar en abierto en silencio en todos los modos. **Corregido el 2026-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).
Con el conmutador desactivado sigue rigiendo el antiguo comportamiento
oportunista, y en ninguno de los dos modos hay app que informe de cuál de las dos
cosas ocurrió. Sigue abierto: ¿puede el operador sustituir la clave de un
proveedor? Hoy: sí (§7.4), así que la confidencialidad del sellado se sostiene
frente a un operador pasivo y frente a un atacante en la ruta que retire el
envoltorio, pero no frente a un operador dispuesto a fabricar una clave falsa. Los
puntos 8 y 9 de `verify/BEFORELAUNCH.md` siguen la pista de ambos; §2.2 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.** Las secciones 1 y 2 definen las partes, los tres modos
de conexión y qué ve cada parte en cada modo. 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:2310-2327`). 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:3932` `RemoteUserNatProvider` → `LocalUserNat`) |
| 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 (`connect/net_extender.go:51-55`, `connect/extender/extender.go:57-61`) |
| 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:150-155`) |
| 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:1712-1719`, `:4640`, `:2350-2356`) |

El operador es **BringYour, Inc.**, una sociedad de Delaware con dirección postal
en San Francisco (`docs/legal/ur.xyz/terms.md:16,25`;
`docs/legal/terms.md:269-271`). La sección 10 cubre qué significa eso para el
proceso legal.

## 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 | el valor por defecto nativo, de fábrica; solo apps nativas; 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 | las rutas del navegador y del proxy (§2.4), o el sellado desactivado; 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:4298-4303`;
`connect/connect.go:45-49`). 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 valor por defecto, no un ajuste —
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). Lo que es condicional ya no es si el usuario
encuentra el conmutador, y desde el 2026-08-10 tampoco 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: ACTIVADO.** El propietario del producto
dictaminó el 2026-08-07 que la sesión sellada se publica habilitada, así que la
ceguera del operador es el valor por defecto y no una elección que el usuario
tenga que hacer. Este documento está escrito contra ese estado de lanzamiento; en
el momento de escribirlo el código sigue viniendo desactivado por defecto (punto 7
de `verify/BEFORELAUNCH.md`).

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
propaga ahora 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`;
`connect/transfer.go:1500-1509`) —, 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
(`connect/transfer_encrypt.go:44-51`). El intercambio de claves es el híbrido
**X25519MLKEM768**, con X25519 convencional como respaldo
(`connect/transfer_encrypt.go:373-381,406`); el AEAD es AES-256-GCM sobre una
clave exportada de 32 bytes (`:285-297`); las identidades son Ed25519
(`:892-897`), así que "poscuántico" se acota solo al intercambio de claves. El
TLS mutuo es obligatorio y el certificado del par se comprueba en la capa de
secuencia contra el compromiso del contrato en lugar de por la pila TLS
(`:388-406`).

### 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 el handshake no se completó: un handshake fallido, una
prueba de identidad fallida o un contrato que nunca llevó la clave pública del par
lo dejan nulo. El fallo de la prueba de identidad sigue siendo no fatal en la capa
de sesión — la sesión "queda sin autenticar"
(`connect/transfer_encrypt.go:1977`) en lugar de desmontarse.

**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:2796`). 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 un
  `ErrEncryptionRequiredNotEstablished` tipado.
- **Red de seguridad del envío** (`connect/transfer.go:4125`). 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:5844`). 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`,
  `EncryptionCapabilityPrefilter`). 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. 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:9186-9197`): 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:98`) 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.

**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, el comportamiento oportunista
descrito en versiones anteriores de esta sección sigue aplicándose por completo —
el tráfico puede fluir en texto plano si una sesión nunca se establece, 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: la cabecera de `completeHandshake`
en `connect/transfer_encrypt.go:1650-1654` sigue afirmando sin matices que "el
tráfico posterior 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.

**Sigue abierto: el usuario no puede ver qué modo le tocó a una conexión.** No hay
ningún indicador por conexión de sellado-o-no en ninguna app. Las únicas
señales son líneas de log de dispositivo —
`peer identity proof verified — cipher is now usable`
(`connect/transfer_encrypt.go:1954`) en caso de éxito, un `Errorf` en caso de
fallo, y un evento `NotifyRequiredSendBlocked` cuando la compuerta rechaza. El SDK
expone un hook de cambio, así que lo que falta es interfaz, no fontanería.

### 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:710-711`). El flujo se fuerza a un stream P2P
(`connect/ip_remote_multi_client_probe.go:1176-1178`) sobre un canal de datos
WebRTC/ICE (`connect/transport_p2p_webrtc.go:986`), 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:1861-1863`;
`sdk/local_state.go:607-616`).

Dos casos forzados: los pares de la misma red (tus propios dispositivos) siempre
permiten el directo (`connect/ip_remote_multi_client.go:1822-1825`), y los
dispositivos alojados lo fuerzan a desactivado (`sdk/device_local.go:1516-1533`).
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:556`, `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:128-131`).
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:461-463`) — 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 — `HttpUpgradeUnencrypted` es 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:2430-2447`), 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:1234-1240`), 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:117-119`,
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:554,604-624`).

**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:4481-4487`), 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:870-876,6653-6683`). El operador no
tiene hoy manejador para ese tipo de mensaje, así que no se almacena nada al
recibirlo (`server/controller/connect_controller.go:236-250`; 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:1120`), 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:760`), 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_profiles.go:14-30,50-57`), y existe
un transporte con forma de DNS para redes donde solo escapa el DNS
(`connect/transport_pt.go:18-45`). Estos son mecanismos para **atravesar
cortafuegos locales y regionales**, clasificados por debajo de los transportes
directos (`connect/transport.go:541-546`).
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:57-61`). 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:51-55`). 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. En el cliente publicado los transportes
directos compiten primero y los marcadores de extensor se expanden cuando aquellos
fallan (`connect/net_http.go:675`); un cliente configurado con extensores
personalizados los usa como su única ruta (`connect/net_http.go:357,469`).

**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** — `server/ip.go:201` abre desde disco un
`mmdb/ip-ipinfo.mmdb` empaquetado 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
(`server/model/network_client_location_model.go:1392-1414`); 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-67`). 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:40-44`); 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-85`; `server/model/verify_model.go:111`). El puerto
de origen se almacena en claro junto al hash (`server/db_migrations.go:1934`). 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:961-975`), y las filas
de auditoría se retiran a los 180 días (`server/model/audit_model.go:984`,
programado en `server/taskworker/taskworker.go:57,253`).

**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:15-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:13-16,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:84`; el borrado elimina en cascada `network_client_location` en la misma sentencia (`model/network_client_model.go:2295-2320`) |
| Un cliente de nivel superior inactivo (sin autenticación, sin conexión) → se desactiva | **30 días** | `TopLevelClientIdleExpiration` (`model/network_client_model.go:2289`) |
| Un cliente desactivado → borrado en duro, con cascadas | **+30 días** | `NetworkClientReapAfterDeactivate` (`:2269`) |
| 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:161` |
| Filas de auditoría | **180 días** | `model/audit_model.go:984` |

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` 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 un `mmdb/ip-ipinfo.mmdb` empaquetado desde el disco local
(`server/ip.go:201`), e `ip.go` no contiene ningún cliente HTTP ni hace ninguna
llamada de red. 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.

**Huecos conocidos en la postura de "no registramos tu dirección", no gobernados
por la verbosidad.** `server/http.go:415,455` construyen `http.Server` sin
`ErrorLog`, así que la biblioteca estándar de Go escribe
`http: TLS handshake error from <client IP>` al stderr de producción en cada
conexión a medio abrir — exactamente el agujero que `proxy/http.go:176,250` cierra
con `ErrorLog: discardLog`. El SNI suministrado por el cliente se registra a nivel
ERROR (`server/tls.go:98,195`), una dirección de llamante en crudo en
`server/proxy/proxy_device.go:274`, y la identidad del par WireGuard en
`server/proxy/server.go:699`. Hasta que se corrijan, "nunca registramos tu IP" no
es una afirmación que URnetwork pueda hacer, y este documento no la hace. Detalle
completo: `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:67`), la plataforma puntúa y clasifica a los
candidatos (`server/model/network_client_location_model.go:2250-2254,2430-2447`),
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:73-75`).
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:138-158`) — 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:234-251`) | 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:13,214-233`), `IntermediaryIds` en
`CreateContract` (`connect/protocol/transfer.pb.go:1516`), aceptación del cliente
en `connect/ip_remote_multi_client_api.go:296-312`, fontanería de servidor en
`server/controller/connect_controller.go:560`. 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:3186-3190,3296-3303`) — 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:76-100`, 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:501-513`) y se
almacena del lado del servidor en Redis en `ckey_<clientId>` **sin caducidad** —
Redis es la fuente de verdad, no hay tabla SQL
(`server/model/network_client_key_model.go:14-59`).

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:810-813`). La entrada se elimina cuando
el id de cliente se purga (`network_client_key_model.go:61-71`, 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:2874-2889,2926-2935`; `sdk/local_state.go:418-461`) — 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:1787-1816`).

### 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:486-498`). 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:3930-3934,4006-4018`).
- **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-proof` y la envía como un `EncryptedControl`
  (`connect/protocol/transfer.proto:460-476`). El AEAD se retiene fuera de la ruta
  de envoltura hasta que la prueba del par verifica
  (`connect/transfer_encrypt.go:1492-1498`, `: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:6119-6129`;
`connect/transfer_encrypt.go:1780-1791`).

### 7.4 ¿Puede el operador sustituir la clave de un proveedor? Hoy: sí.

**Dicho llanamente: un operador que sustituya el certificado, la firma del
certificado y `destination_client_public_key` al unísono derrota ambas defensas y
puede interponerse como máquina en medio en una sesión sellada.** Las notas de
diseño lo dicen con las palabras del propio repositorio
(`connect/DESIGNNOTES.md` §3.7): *"Agujero residual: una plataforma que sustituya
cert + firma + `destination_client_public_key` al unísono sigue ganando en el lado
de la vinculación del certificado; el movimiento de cierre es alimentar
`SetPeerClientPublicKey` desde la consulta fuera de banda en lugar de desde el
contrato. Esa comprobación cruzada es actualmente solo de registro."*

El movimiento de cierre previsto existe y está cableado, pero no actúa. Una ruta
`GET /key/<client_id>` no autenticada sirve la clave publicada
(`server/api/api.go:123-124`; `server/controller/connect_controller.go:826-849`),
y el SDK publicado instala un recuperador por sesión contra ella para cada cliente
de ventana y cada cliente proveedor (`sdk/device_local_provider.go:398-420`,
alcanzado desde `sdk/device_local.go:3434` y `:90`). En el primer establecimiento
de clave el cliente recupera la clave del par fuera de banda y compara. Ante una
discordancia registra:

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

(`connect/transfer_encrypt.go:1871-1876`). La clave suministrada por el contrato
sigue siendo la de confianza (`:520-525`, `:1839-1849`). Promover el registro a
rechazo se describe tanto en el comentario de ajustes como en las notas de diseño
como el siguiente paso de endurecimiento — eso es **intención de diseño**, no una
propiedad publicada.

Tres precisiones más que un auditor debería retener:

1. **El canal fuera de banda está fuera de banda respecto del pipeline de
   contratos, no respecto del operador.** La recuperación va al mismo host de la
   API del operador (`sdk/device_local_provider.go:404`) y lee el mismo Redis en
   el que escribe quien redacta el contrato. Incluso después de que el registro
   pase a ser rechazo, la comprobación atrapa a un operador que sea *inconsistente
   entre dos de sus propios canales*, no a uno que sea consistente. Cerrar eso
   exige un ancla que el operador no controle — ids derivados de claves o un
   registro de transparencia, ambos recogidos como aplazados.
2. **La omisión es tan eficaz como la sustitución, y más silenciosa.** La
   verificación del certificado se omite sin quedar fijada cuando el conjunto de
   confianza está vacío, incluso cuando los contratos llevan un
   `ProvideTlsCertificate` vacío (`connect/transfer.go:4010-4016`), y la prueba de
   identidad no puede verificarse en absoluto cuando la clave pública del par está
   ausente (`connect/transfer_encrypt.go:1706-1708`). En cualquiera de los dos
   casos el cifrador nunca llega a ser usable y el tráfico corre en texto plano
   (§2.2) sin ninguna señal visible para el usuario. Un operador que quiera leer el
   tráfico de un usuario concreto mientras el conmutador está activado no necesita
   falsificar nada; necesita omitir un campo.
3. **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:768-779`;
   `sdk/device_local.go:2866-2872`). 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. Sí significa que la
ceguera del operador al contenido, en la ruta sellada, descansa actualmente en que
el operador distribuya las claves honestamente — lo cual es una propiedad de
*política* con un mecanismo criptográfico construido casi del todo por detrás, no
todavía una propiedad que se sostenga frente a un operador hostil. Cualquier
documento de URnetwork que describa la sesión sellada como algo que hace
incondicional la ceguera del operador la está exagerando, y 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:305,340`). Ningún
  protobuf de `connect/protocol/` lleva un campo de relleno, y el encuadre es un
  prefijo de longitud pelado de 4 bytes (`connect/message_framer.go:28-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:387-392`). Todo `jitter` de 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
  `WritePacketsPerSecond` del transporte con forma de DNS
  (`connect/transport_pt.go:63,232-246`), 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:112-215`,
`connect/transport_pt.go:18-45`) 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:138-158,1234-1240`). 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:461-463`; 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 en
`connect/ip_remote_multi_client_api.go:326-345`, liberado en `:410,473`).

**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:1204-1208`), y
una identidad de ventana almacenada se reproduce contra el mismo destino hasta que
caduca: `windowIdentitiesStaleAfter = 4 * time.Hour`
(`sdk/window_identity_store.go:44-52`). 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:3434-3437`), que no lleva `ClientKeySeed`
(`connect/transfer.go:166-183`), así que `ClientKeyManager` genera un par de
claves nuevo (`connect/transfer_key.go:96`). 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:46-52`), 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:9149-9157`).

**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:566-571,1070-1090`) — 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:323-348`). 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:742,1009-1020,1151`). 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:418-461`, `.device_local_key_material`, el JSON
`client_key_seed`; aplicado en `sdk/device_local_key_material.go:54-70`). 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:636-647`).
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:639-644`).

Esa clave se publica, se sella en cada contrato que nombre a este cliente como
destino (`server/controller/connect_controller.go:413-415,468`), y es legible por
cualquiera: `GET /key/<client_id>` no está autenticado por diseño
(`server/api/api.go:123-124`; `server/controller/connect_controller.go:826-829`).
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_id` nunca 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:64-67`). Un cliente de nivel superior se
  desactiva tras 30 días de inactividad (`TopLevelClientIdleExpiration`, `:2289`) y
  se borra en duro 30 días después (`NetworkClientReapAfterDeactivate`, `:2269`),
  en cascada hacia la fila del dispositivo y el `ckey` de Redis (`:2537`).
- El `device_id` es **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:35-37,188`),
  refrescado por el SDK a la mitad de su vida — unas 12 horas — con jitter
  (`sdk/device_token_manager.go:107-124,165`). **Refrescar el token no rota el id
  de cliente**; se vuelve a acuñar la misma reclamación
  (`server/model/network_client_model.go:102-125`).
- `audit_contract_event` registra la identidad del cliente y la del proveedor por
  contrato (`server/db_migrations.go:234-251`). `client_reliability` une 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:2083-2106`) 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-195`), con
  `network_id` sobreviviendo como carga útil de índice (`:63,631-634`). La
  retención es de 30 días
  (`server/model/network_client_reliability_model.go:42,687-721`; las particiones
  diarias se descartan enteras).
- `network_client.auth_time` es un último-visto duradero retenido 30 días
  (`server/model/network_client_model.go:2269,2289`); 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 en `user_auth_attempt`, direcciones de cartera, filas de pago, una arista
  persistente de quién-invitó-a-quién en `network_referral`, y filas de
  `audit_provider_event` que llevan cadenas literales de país, región y ciudad
  contra `network_id` y `device_id`, no atadas a la purga de conexiones a las 8
  horas (`review/verified/PRIVACY-ENFORCEMENT.md` §1.4).
- El `User-Agent` se registra por la lista de permitidos de cinco cabeceras
  (`server/http_log.go:15-29`). Es la única entrada de las cinco que es una
  superficie de huella digital.

### 9.4 El puente WireGuard te enlaza entre proveedores

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:1034,1038-1118`), escrita en la
configuración como la dirección de la interfaz (`:756-771`), y **no se normaliza
antes de la salida** — el propio FIXME del código fuente dice *"actualmente la
ipv4 del cliente se hilvana hasta los proveedores de salida / esto puede permitir
rastrear una única ipv4 de cliente a través de múltiples proveedores"*
(`proxy/wg.go:23-25`). Es una dirección privada, no tu IP real, y el sitio web de
destino nunca la ve. Pero cada proveedor de tu ventana ve la misma dirección de
origen, así que **unos proveedores en connivencia pueden saber que esos flujos
pertenecen a un solo cliente** — precisamente la propiedad que la ventana
multiproveedor proporciona por lo demás. Combinado con el resolvedor fijo
`1.1.1.1` (§3.2) y la ausencia de sesión sellada en esa ruta, el puente WireGuard
es la forma menos privada de usar la red. Las apps y el SDK son la más privada.

## 10. Requerimientos legales

**Jurisdicción.** BringYour, Inc. es una sociedad de Delaware
(`docs/legal/ur.xyz/terms.md:16,25`) con dirección postal en San Francisco
(`docs/legal/terms.md:269-271`). Los términos de ur.io fijan la ley aplicable y la
sede judicial en el condado de Harris, Texas (`docs/legal/terms.md:315-321`); los
términos de ur.xyz fijan Delaware (`ur.xyz/terms.md:223-229`). 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-260`). 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:984,1018`) — o, para los pagos en cadena
completados, no caducan en absoluto
(`server/model/solana_payment_intent_model.go:386-400` 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:255-257`). 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. El único puntero de proceso legal que existe dirige
a quien notifique un proceso a obtener el agente registrado de Delaware desde la
División de Sociedades de Delaware (`docs/legal/ur.xyz/terms.md:262`);
`security@ur.io` está acotado a los informes de vulnerabilidades
(`docs/legal/vdp.md:42`) y `notice@ur.io` es la dirección general de notificaciones
contractuales. Los propios documentos comparativos de URnetwork ya conceden esto
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.

1. **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.
2. **Un adversario pasivo global.** Enteramente fuera del alcance. §8.3.
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.
4. **Un operador obligado u hostil que sustituya u omita material de claves de
   proveedor.** Hoy la comprobación cruzada que lo atraparía registra y continúa.
   §7.4.
5. **Degradación silenciosa de la sesión sellada — CON el Cifrado poscuántico
   DESACTIVADO.** En ese modo cualquier fallo (de handshake, de prueba de
   identidad, de clave ausente) recae en texto plano 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.
6. **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.
7. **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.
8. **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.
9. **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_signature` público.
10. **Proveedores Sybil.** Cualquiera puede proveer, sin atestación ni garantía
    depositada. Una sola parte puede ejecutar muchos proveedores, incluido el
    operador.
11. **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.
12. **Vinculabilidad entre proveedores en el puente WireGuard.** Una sola
    dirección de túnel estable a través de cada proveedor de tu ventana. §9.4.
13. **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/<client_id>` no está
    autenticado, así que cualquier parte puede agrupar los ids de cliente que
    comparten una clave. §9.2.
14. **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.
15. **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.

- **Qué registra el LB de entrada.** La configuración de cabeceras de nginx está
  en el árbol (`xops/.../connect/ingress.yaml:9`) y el servicio connect limita la
  tasa sobre una dirección hasheada
  (`server/connect/transport_rate_limit.go:65-80`), pero la propia configuración de
  logs de acceso de nginx no se auditó. Cualquier afirmación de extremo a extremo
  de "no retenemos tu dirección" depende de ella.
- **Si las líneas de log con dirección y SNI no gobernadas de §5 llegan a
  almacenamiento duradero.** Se escriben a stderr; adónde va el stderr en
  producción, y cuánto tiempo se conserva, es una cuestión de despliegue que esta
  revisión del código fuente no puede responder. El tailer de monitorización sí
  extrae patrones `ip:port` de las líneas de log y los reemite dentro de hallazgos
  (`server/monitor/tailer.go:136,328,333`), lo cual es evidencia de que al menos
  algunas de esas líneas las leen sistemas que las retienen.
- **Si el dispositivo proxy del lado del servidor de WireGuard reescribe el origen
  del paquete antes de entregar los paquetes a los proveedores.** La dirección de
  túnel estable y la vista del operador del extremo público real
  (`server/proxy/wg_handoff.go:56-67`) están ambas verificadas; la ruta completa a
  través de `server/proxy/proxy_device.go` y `OpenProxyDevice` no se ha leído de
  principio a fin (`review/verified/ARCHITECTURE.md`, punto no verificable 3).
- **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:1155-1175`). No se probó el
  comportamiento del DNS del proxy en cada navegador bajo todas las
  configuraciones.
- **La verbosidad real en producción.** `BY_LOG_V` vale 0 por defecto
  (`server/env.go:41-49`), lo que suprime el registro de destinos gobernado por
  `V(1)` en cliente y servidor — pero el único manifiesto de despliegue del árbol
  lo pone a `2` (`xops/gitops-unused/.../api/deployment.yaml:47-48`), en un
  directorio llamado `gitops-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:1607-1641`), 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:20-30`). 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 toda ruta de envío del dispositivo usa un cliente de ventana.** El llaveado
  efímero de la ruta de ventana está verificado (§9.1). El cliente de nivel
  superior del dispositivo lleva la semilla duradera y habilita el cifrado
  incondicionalmente (`sdk/device_local_provider.go:90-98`), así que cualquier
  envío que viaje sobre el cliente de nivel superior — relaciones entre pares de
  red, rutas de retorno de compañeros — va firmado con la clave estable y lleva el
  `client_id` estable de nivel superior. Esas rutas no se enumeraron de forma
  exhaustiva. Lee "los identificadores de salida son efímeros" como acotado a la
  ruta del cliente de ventana.
- **Si `Roles` y `Principal` se 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 para `ProvideMode_Network`
  (`connect/protocol/transfer.proto:408-415`;
  `server/controller/connect_controller.go:474-481`). 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.** `ClientKeySeed` se
  referencia solo desde `sdk/local_state.go`,
  `sdk/device_local_key_material.go`, `sdk/device_local.go` y `sdk/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.
- **Si la sede judicial de Texas de los Términos de ur.io es intencionada**, dada
  una entidad de Delaware, una dirección de California y una sede de Delaware en
  los términos de ur.xyz. Ningún documento del árbol lo explica. Esta es una
  pregunta para los abogados, no un hallazgo.

## 13. Reportar

Vulnerabilidades: `security@ur.io` y la política de divulgación en
[ur.io/vdp](https://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.
