Seguridad

    Escáneres de enlaces y secretos de un solo uso

    Si un enlace de un solo uso se destruye con un GET, muere en la vista previa de Slack o del correo. Responde con metadatos y destruye solo al mostrarlo.

    W. Miller · Actualizado el · Sobre LinkPilot

    Un enlace de un solo uso que se destruye con un simple GET muere en cuanto Slack, Teams o una pasarela de correo lo abre para generar una vista previa. La solución es devolver solo metadatos con GET y destruir el secreto con un POST explícito para mostrarlo, con una frase de contraseña enviada por un segundo canal como red de seguridad. Este artículo explica qué sistemas abren enlaces sin preguntar y cómo diseñar pensando en todos ellos.

    El generador de enlaces secretos de un solo uso de LinkPilot funciona así: una petición para generar una vista previa solo recibe la estructura de la página; el secreto se descarga, y la vista se cuenta, cuando un navegador ejecuta la página.

    El error por defecto

    La mayoría de los clientes de chat y de correo intentan generar una vista previa cuando ven una URL. El proceso es este:

    1. Alguien pega el enlace secreto en Slack.
    2. El generador de vistas previas de Slack abre la URL (hace un GET; Slack documenta este comportamiento en link unfurling).
    3. El código que muestra el secreto trata la petición como la visita de una persona, destruye el secreto y devuelve el contenido.
    4. Slack recibe el contenido ya destruido (o una página genérica) y lo muestra.
    5. La persona hace clic en el enlace, ve «ya visto» y le pide otro al remitente.

    El generador de vistas previas no tenía mala intención. El código confundió «alguien ha abierto la URL» con «el destinatario ha leído el secreto». Son hechos distintos, y todo el problema de diseño consiste en mantenerlos separados.

    En el paso 3 se esconde una variante peor: si el GET del generador de vistas previas recibió el contenido real, el secreto está ahora dentro de la infraestructura de un tercero que genera vistas previas. En el mejor de los casos, se descartó después de crear la vista previa. Pero no tienes forma de confirmarlo, ni ningún evento de auditoría que te avise de que ocurrió.

    Los generadores de vistas previas de los chats, las pasarelas de seguridad del correo, la protección de los dispositivos, los proxies corporativos y las extensiones de vista previa, y casi siempre más de uno. Decir que «Slack genera vistas previas» se queda corto. Una URL pegada en un entorno de trabajo actual suele pasar por varios sistemas antes de que una persona haga clic:

    Quién la abreCuándo¿Se puede identificar?
    Generadores de vistas previas de chat (Slack, Teams, iMessage, WhatsApp, LinkedIn)En cuanto se envía el mensajeNormalmente sí, con user agents propios como Slackbot-LinkExpanding
    Pasarelas de seguridad del correo (Mimecast, Proofpoint, Microsoft Defender)Al entregar el correo, y a menudo otra vez en el momento del clicA menudo no: muchas usan user agents de navegadores reales
    Protección del dispositivo / antivirusCuando la URL llega al dispositivo del destinatarioRara vez
    Proxies web corporativosEn el momento del clic, antes de reenviar la petición de la personaRara vez
    Extensiones de navegador que muestran vistas previas de enlacesAl pasar el ratón o al cargar la páginaNo: usan la propia sesión del navegador de la persona

    El comportamiento de cada proveedor en esta tabla cambia con el tiempo, así que toma lo que se dice de cada pasarela como «según lo publicado en julio de 2026; compruébalo con la documentación actual». Lo que tienen en común no cambia: todos estos sistemas hacen un simple GET, porque es la única forma segura y universal de ver una URL que no controlas.

    Responde con metadatos al GET y destruye el secreto solo con un POST explícito. La semántica de HTTP ya lo prevé: RFC 9110, apartado 9.2.1 define GET como seguro e idempotente, es decir, una petición que cualquiera puede hacer libremente porque no debe cambiar nada. POST es el verbo de acción. Así que trata el acto de mostrar el secreto como una acción POST detrás de un botón visible:

    GET  /s/abc123   →  HTML page: "Click to reveal this secret"
    POST /s/abc123   →  burns the secret, returns the payload
    

    Ahora el GET de Slack devuelve una página neutra con metadatos. El secreto solo se destruye cuando la persona hace clic en el botón. Cualquier escáner que respete la semántica de HTTP no llega a tocar el contenido. Eso incluye a casi todos, porque hacer POST con efectos sobre URL arbitrarias rompería media web.

    Un recorrido paso a paso

    Sigamos un enlace de principio a fin. Creas un secreto que se destruye tras la primera lectura y tiene frase de contraseña, y pegas la URL en un mensaje directo de Slack y en un correo.

    1. El generador de vistas previas de Slack hace GET /s/abc123. Respuesta: una pequeña página de metadatos (estado activo, frase de contraseña obligatoria). No se registra ninguna vista. El secreto sigue vivo.
    2. La pasarela de correo del destinatario reescribe la URL del correo y la abre en el momento de la entrega para analizar su reputación. El mismo GET, los mismos metadatos. Sigue vivo.
    3. El destinatario hace clic en el enlace del correo. El escáner de la pasarela la abre una vez más en el momento del clic (GET) y después redirige al navegador.
    4. El navegador carga la página para mostrar el secreto, con otro GET. La página muestra un botón para mostrarlo y un campo para la frase de contraseña. Sigue sin registrarse ninguna vista.
    5. El destinatario escribe la frase de contraseña y hace clic en el botón. El navegador hace POST /s/abc123. Se registra la vista, se devuelve el contenido y el secreto se destruye.
    6. Quien abra la URL después recibe una respuesta explícita de «ya visto y destruido» en lugar del contenido.

    Cinco peticiones, una sola lectura. Esa proporción es normal en un entorno corporativo. Un diseño que no la tolere fallará el primer día, y el remitente acabará pegando la contraseña directamente en el chat.

    Defensas por capas

    La separación entre GET y POST

    Cubre todos los generadores de vistas previas y escáneres que se portan bien, que son la inmensa mayoría del tráfico automatizado. Es la base; todo lo que sigue añade profundidad.

    La frase de contraseña

    Cubre lo que la separación no puede: sandboxes que ejecutan JavaScript e interactúan con la página, además de clics accidentales de personas y correos reenviados. Un sandbox de detonación puede hacer clic en un botón; no puede escribir una frase de contraseña que viajó por otro canal. Envía el enlace por correo y la frase de contraseña por un mensaje directo de Slack o por SMS.

    Filtrado de bots por user agent

    Descarta los bots de vista previa conocidos (Slackbot, LinkedInBot, Twitterbot) antes de que lleguen al código que muestra el secreto. Merece la pena como protección adicional, pero nunca basta por sí solo: los escáneres de seguridad usan de forma habitual user agents de navegadores reales precisamente para que las páginas maliciosas no puedan ocultarse del análisis.

    Expiración corta

    Limita el periodo en que puede haber daños. Si un enlace duró quince minutos, los escáneres, los archivos de registro y el historial del chat se quedan con una URL que ya no lleva a nada.

    Cache-Control: no-store

    Ponlo en todas las respuestas que muestran el secreto para que ningún intermediario (proxy corporativo, CDN, caché del navegador) guarde una copia del contenido después de la única lectura legítima.

    El caso concreto de las pasarelas de seguridad del correo

    Las pasarelas corporativas merecen un apartado propio porque abren el enlace dos veces. Los productos de este tipo (Microsoft Defender Safe Links es el ejemplo documentado) reescriben todas las URL del correo entrante para que pasen por su propio dominio de análisis. Lo normal es que la URL se abra una vez en la entrega, para analizar su reputación, y otra vez en el momento del clic, antes de redirigir a la persona al destino real.

    Si cualquiera de esas dos peticiones destruyera el secreto, el destinatario vería siempre un enlace muerto, un error que a los usuarios les parece aleatorio pero que es totalmente determinista. La separación entre GET y POST absorbe las dos peticiones sin ningún problema, porque las dos son simples GET. La redirección en el momento del clic lleva después a la persona a la página para mostrar el secreto, que es donde se hace el POST.

    Casos límite que siguen dando problemas

    • Sandboxes que ejecutan JavaScript. Algunos entornos de detonación cargan la página e interactúan con ella. En teoría, un sandbox agresivo podría hacer clic en un POST detrás de un botón sin frase de contraseña. La frase de contraseña es la red de seguridad.
    • Enlace y frase de contraseña por el mismo canal. Si los dos van en un mismo correo, todas las defensas anteriores se reducen a «esperar que nadie lea ese correo». Sepáralos siempre en canales distintos.
    • Reenvíos. Cada reenvío a otro chat u otro sistema de correo provoca una nueva ronda de vistas previas. Con la separación no hacen daño, pero cada salto deja otro conjunto de registros con la URL, y eso es un argumento a favor de una expiración corta.
    • Lecturas simultáneas. Si dos personas hacen clic para mostrar el secreto a la vez, la lectura y la destrucción tienen que hacerse en un solo paso indivisible: exactamente una petición gana y la otra recibe «ya visto». Una implementación que las haga por separado puede entregar el contenido dos veces.

    Prueba tu propia herramienta

    Antes de confiar credenciales reales a cualquier servicio para compartir secretos, haz esta comprobación de dos minutos:

    1. Crea un secreto desechable con la destrucción tras la lectura activada.
    2. Ábrelo desde un terminal, que es justo lo que hace un generador de vistas previas: curl -s -o /dev/null -w "%{http_code}" "<url>"
    3. Abre el enlace en un navegador. Si ves «ya visto», la herramienta destruye el secreto con un GET y no es seguro pegar sus enlaces en un chat o un correo.
    4. Si ha sobrevivido, muéstralo de la forma normal en el navegador y vuelve a hacer curl: ahora deberías recibir una respuesta explícita de que ya no existe o se ha destruido, no el contenido.

    Cómo lo implementa LinkPilot

    Para que conste, este es el diseño que usan los enlaces secretos de LinkPilot: un GET al endpoint que muestra el secreto devuelve solo metadatos (el estado y si hace falta una frase de contraseña), y solo se registra una vista con un POST explícito. Las frases de contraseña se convierten en un hash SHA-256 en el cliente y tienen un límite de cinco intentos por IP, por minuto y por secreto. Todas las respuestas que muestran el secreto llevan Cache-Control: no-store, los secretos destruidos o expirados devuelven HTTP 410 con el contenido almacenado sobrescrito, y el historial de auditoría registra cada evento con la IP convertida en hash con una sal que cambia cada día. La IP sin procesar no se guarda nunca. Todos los detalles están en la página de arquitectura de seguridad.

    Pruébalo gratis, sin registro

    Mensaje que se autodestruye

    Envía una nota que desaparece después de leerse una vez.

    Abrir herramienta

    Límites de este diseño

    Con sinceridad, estos son sus límites: nada de lo anterior protege un secreto después de una lectura legítima, porque el destinatario puede copiar el contenido, hacer una captura de pantalla o reenviarlo. No sirve de nada si la frase de contraseña viaja con el enlace. Y no puede impedir que una persona que recibió el enlace por error lo abra antes que el destinatario previsto. Para eso está el historial de auditoría: ves la vista inesperada y rotas la credencial.

    Referencias

    Herramientas para este artículo

    Preguntas frecuentes

    LinkPilot envía un enlace secreto que desaparece en cuanto se abre.

    Cifrado en tu navegador, se muestra una vez y después se destruye. Sin cuenta.

    Crear un enlace secreto

    Sigue leyendo