Por qué el hash de una URL puede guardar una clave, y cuándo esa protección falla

En la barra de direcciones, el signo de interrogación y la almohadilla parecen dos signos de puntuación. Lo que va tras ? se escribe en la línea de petición HTTP, así que el anfitrión, el proxy inverso y el registro de acceso lo ven. El fragmento tras # se deja a esta pestaña; por defecto no viaja con esa petición del documento. Poner la clave de descifrado detrás de # bloquea «el anfitrión vio la clave». No bloquea «alguien reenvió el enlace entero».

Empieza por la conclusión: la almohadilla sirve para una clave porque el navegador tira el fragmento al montar la petición HTTP, no porque haya aparecido un protocolo de cifrado nuevo. La comprobación es contrastar la barra de direcciones con la línea de petición en Network. Cuando pegas la URL completa en un chat, un ticket o el historial del navegador, esa capa ya no vale frente a quien pueda ver la cadena.

Responde a la intención de búsqueda en una frase

El hash de una URL puede guardar una clave porque RFC 9110 dice que el URI de destino de la petición no incluye el fragmento. Cuando el navegador pide el documento al servidor, quita # y todo lo que sigue. El anfitrión, la CDN y el registro de acceso están diseñados para ver la ruta y la consulta tras ?, no la clave que va detrás de la almohadilla.

Esta nota es para desarrolladores, operaciones y quien quiera separar «el servidor no lo ve» de «el historial del chat no lo ve» antes de mandar un enlace de un solo uso a un compañero. No sustituye las instrucciones de Burn-Link ni repite cómo crear un enlace. Lo que te llevas es en qué capa entra cada signo, cómo contrastarlos en Network y qué protección cae al reenviar la URL entera.

La consulta entra en HTTP; el hash se queda en la pestaña

Una misma dirección puede llevar consulta y fragmento. La forma es https://example.com/s.html?id=abc#key. Tras el signo de interrogación, id=abc es la consulta. Tras la almohadilla, key es el fragmento. Al parsear la cadena, el fragmento va desde el primer # hasta el final. Una almohadilla literal más a la izquierda hay que escribirla como %23; si no, el parseo corta demasiado pronto.

Para la petición del documento, el navegador monta Host, ruta y consulta. La nota de MDN sobre el fragmento de URI es directa: el fragmento no se envía al servidor al pedir el recurso; el cliente lo trata después de recibir el documento. En HTML suele servir para bajar hasta un id. En una página de lectura única se aprovecha el mismo hueco para llevar la clave que esta pestaña necesita para descifrar en local.

Así, «el servidor solo guarda texto cifrado» solo se sostiene si el identificador de búsqueda viaja en la consulta, la clave de descifrado viaja en el fragmento y los scripts de la página no vuelven a escribir location.hash en una petición posterior. Falte cualquiera de esas tres, y la almohadilla es decoración.

Las especificaciones describen el comportamiento por defecto, no un cifrado extra

RFC 3986 §3.5 define el fragmento como identificador de un recurso secundario encima del principal, interpretado por el cliente según el tipo de documento. RFC 9110 §7.1 añade que el URI de destino excluye cualquier fragmento de la referencia, porque los identificadores de fragmento quedan reservados al procesamiento en el cliente. Leídas juntas: lo que va tras # no forma parte del destino de la petición HTTP.

Por eso los registros de acceso no tienen #. Operaciones no escondió un campo. La línea de petición nunca contuvo ese tramo. Intentar contar «a qué encabezado hizo clic el usuario» desde los logs del servidor falla por la misma razón. Intentar demostrar «la clave no llegó al servidor» con esos mismos logs encaja con la especificación.

Referer sigue la misma regla. La página de MDN sobre Referer dice que la cabecera puede llevar origen, ruta y consulta, pero no debe incluir un fragmento de URL ni usuario y contraseña. Referrer Policy vacía el fragmento antes de empaquetar una URL en Referer. Así, cuando la página de lectura pide después una imagen o un script, la clave tras # no se entrega por defecto a un anfitrión de terceros. Ese recorte es comportamiento por defecto del navegador, no un protocolo inventado por este sitio.

Los scripts de la página sí pueden leer el fragmento. El URL.hash de MDN es # más el fragmento; la página actual también puede usar location.hash. La página de lectura usa esa interfaz para tomar la clave y luego llama a Web Crypto para descifrar con AES-256-GCM. La misma interfaz se puede malusar: un script puede pegar el hash a una consulta o mandarlo en un payload de analítica. La especificación bloquea la línea de petición que el navegador monta por defecto. No bloquea «la página leyó el hash y lo envió igual».

No experimentes con una contraseña real, una API Key ni una cadena de conexión sin enmascarar. Prepara una frase de prueba de un solo uso. Estás comprobando líneas de petición y cuerpos, no volviendo a exponer una credencial.

Contrasta la barra de direcciones con la línea de petición en Network

Un eslogan no es una prueba. Una comprobación que puedes terminar ahora es abrir el panel Network y poner la cadena completa de la barra de direcciones al lado de cada URL de petición. La nota anterior usaba un canario para buscar texto en claro. Esta solo vigila el fragmento: el tramo tras # debe aparecer en la barra de direcciones y no debe aparecer en la línea de petición.

  1. Abre la página de lectura y confirma que la barra de direcciones tiene forma s.html?id={id}#{key}: la consulta solo lleva el id y la clave solo va detrás de la almohadilla.
  2. Abre Network, marca Preserve log y deja el filtro en All. No ocultes todavía la petición del documento, las APIs posteriores ni la analítica.
  3. Pega la clave que va tras # en el filtro como canario. Un acierto en rojo significa parar y leer esa petición: la implementación metió el fragmento en una consulta, o un script lo leyó y lo envió.
  4. Si no hay acierto, abre una a una la petición del documento e interfaces como /secrets. La línea de petición solo debería llevar id; el cuerpo no debería contener el texto original.

Puedes hacer la misma comprobación al crear. Después de que este dispositivo cifre con AES-256-GCM, los campos de salida deberían ser texto cifrado, caducidad y número de lecturas. La clave aleatoria debería aparecer solo en el enlace para compartir que montas a continuación, no en el JSON del POST. El Burn-Link de FastPwd sigue ese límite: crear y leer están listos para usar, sin cuenta. Una promesa sigue siendo solo una promesa. Fíate de lo que muestre esta sesión de Network.

Separa %23 de un # de verdad. Los caracteres que van tras %23 en la línea de petición se enviaron como ruta o consulta, así que el servidor los ve. Solo un # sin codificar empieza un fragmento. Escribir la clave como ?key= o /%23key puede parecer «está escondida en el hash». Para un log, ya es una consulta en claro.

Los scripts de la página y la analítica aún pueden leer el hash

Que una petición HTTP por defecto no lleve fragmento no es lo mismo que «ningún script de esta pestaña puede verlo». El JavaScript del documento actual tiene location.hash. La página de lectura tiene que leerlo para descifrar en local. Si un script de analítica reporta el location.href entero, la clave aparece en la consulta o el cuerpo de ese tracker.

Por eso la búsqueda del canario no puede quedarse en la API de negocio. Filtra la ruta de analítica por separado y abre consulta y cuerpo. El título de la página y la ruta pueden aparecer. La clave tras #, una contraseña que estés auditando y el texto antes de enmascarar, no. La analítica de visitas de FastPwd pasa por el tracker del propio sitio y, por defecto, no envía desde localhost. Aun así, usa el mismo canario para confirmar que el informe no lleva la clave del fragmento.

Las extensiones, el DLP de empresa y algunos «navegadores con IA» también pueden leer la barra de direcciones completa. No viajan por la línea de petición HTTP que estás mirando, así que el panel Network no prueba que se hayan quedado fuera. Reduce la afirmación a una frase que un compañero pueda reutilizar: «He buscado en la línea de petición y en el payload de analítica; la clave tras # no está.» No lo conviertas en «ningún software de este ordenador ha visto nunca este hash».

Cuándo falla esta capa de protección

El fragmento bloquea «esta petición del documento entregó la clave al anfitrión». En los casos de abajo, quien ve la URL completa ya tiene la clave. La especificación no puede ayudar.

Primero: pegar el enlace entero en un chat, un correo o un ticket. El cuerpo del mensaje contiene lo que va tras #. El otro cliente, el historial de la sala, la búsqueda de tickets y una exportación posterior pueden ver la cadena. El servidor puede seguir sin recibir la clave por HTTP. Quien pueda abrir el hilo ya la tiene. La URL completa es una credencial al portador.

Segundo: historial del navegador y sincronización. Un fragmento # que apareció en la barra de direcciones suele acabar en el historial local. Un navegador con la sincronización activa puede copiar el mismo registro a tus otros dispositivos. Cerrar la pestaña no borra el historial.

Tercero: una redirección puede llevarse el fragmento. Si el destino de Location no trae fragmento propio, muchos navegadores añaden el fragmento original a la dirección nueva. La clave aparece entonces en una barra de direcciones que creías que ya «había dejado la página sensible». Si la página de lectura redirige a un anfitrión de analítica o a un dominio de inicio de sesión, lee primero la línea de petición final en Network y luego mira qué queda en la barra de direcciones.

Cuarto: las vistas previas de enlaces y los escáneres de seguridad piden la parte anterior a la almohadilla. Cuando una app de chat monta una vista previa, no debería enviar el fragmento al anfitrión previsualizado, pero sí pedirá s.html?id=…. Si la implementación quema en el primer GET del documento o en el primer GET del texto cifrado, el robot de vista previa puede gastar el recuento de lecturas antes de que el destinatario pulse. El destinatario ve entonces «ya se ha quemado». La clave nunca se dio al anfitrión de la vista previa: se quemó el recuento, no el secreto del fragmento.

La página de lectura de FastPwd separa esos dos pasos: abrir la página solo pregunta si el texto cifrado sigue ahí; no se lo lleva. Después de que el destinatario pulse «Abrir y ver», la página pide el texto cifrado por id y descifra en local con la clave que va tras #. Ni la página de creación ni la de lectura necesitan cuenta. Los robots de vista previa suelen pedir la página de aterrizaje y no pulsan el botón por ti, lo que baja la probabilidad de «previsualizar es quemar». No garantiza que todo escáner se detenga en la página de aterrizaje. El recuento sigue siendo un contador en el servidor, no parte del fragmento.

Quién mira ¿Ve la clave tras #? ¿Sigue en pie esta capa?
Línea de petición del documento Por defecto, no Sí; contrástala en Network
Registro de acceso del anfitrión / CDN Por defecto, no Sí, si la clave nunca entró en la consulta
Referer hacia un tercero Por defecto, no Sí; la especificación quita el fragmento
Script de la página / analítica Puede leer location.hash Depende de la implementación; busca el canario en el cuerpo
Cuerpo de chat, correo o ticket Ve la cadena entera No vale frente a la persona a la que se lo reenviaste
Historial del navegador / sincronización Suele sí No vale frente a quien pueda abrir este navegador
Robots de vista previa de enlaces No deberían recibir el fragmento Pueden consumir un recuento de lecturas

Qué puedes demostrar y qué no

Esta comprobación sostiene una afirmación estrecha. Sostiene: en este navegador, en esta apertura, la petición del documento y las líneas de petición de interfaz que observaste no llevaban la clave tras #, y el cuerpo de la petición de creación no contenía el texto original.

No sostiene: que un administrador del chat nunca guardó el enlace entero; que una extensión nunca leyó la barra de direcciones; que el destinatario no hará una captura; que el texto cifrado no reaparecerá en otra copia de seguridad después de recogerlo. La lectura única borra el texto cifrado en el servidor. No borra el texto en claro ya descifrado en pantalla, ni la URL completa que sigue en un hilo de chat.

No leas el fragmento como «solo el destinatario previsto puede abrir esto». Quien tenga la cadena entera ?id=…#clave puede descifrar en la página de lectura pública. La página de lectura no tiene puerta de inicio de sesión. Ese es el diseño: la otra persona no tiene que registrarse primero. El coste es que la cadena entera hay que guardarla como una credencial, no como un enlace de documento que se puede reenviar en un grupo.

Preguntas frecuentes

¿Lo que va detrás de # en una URL se envía al servidor?

Por defecto, no. RFC 9110 indica que el URI de destino no incluye el fragmento. Cuando el navegador pide el documento, quita la almohadilla y todo lo que sigue. El anfitrión, el proxy inverso y el registro de acceso están diseñados para ver solo la ruta y la consulta tras ?. Si la clave tras # aparece en la línea de petición, es un error de implementación, no una excepción de la especificación.

¿Por qué no poner la clave de descifrado en la cadena de consulta?

Lo que va tras ? se escribe en la línea de petición HTTP. El anfitrión, la CDN y los registros de acceso lo ven. En cuanto la clave entra en la consulta, «el servidor solo guarda texto cifrado» ya es falso en la capa de logs. Un id puede vivir en la consulta, porque es como se pide el texto cifrado. Una clave, no.

¿Sigue siendo seguro pegar un enlace con # en un chat?

El servidor sigue sin ver la clave en esa petición HTTP. El historial del chat, los tickets y el historial del navegador sí. La URL completa es una credencial al portador: quien tenga la cadena entera puede descifrar en la página de lectura. Si necesitas más aislamiento, envía el enlace que lleva el id y la clave que va tras # por dos canales distintos: no sueltes la cadena entera en la misma sala.

¿Hace falta una cuenta para abrir la página de lectura de Burn-Link?

No. La página de lectura es pública para el destinatario. Recoge el texto cifrado con el id de la consulta y descifra en local con la clave que va tras #. FastPwd no tiene cuentas ni almacén de contraseñas. Crear y leer están listos para usar. Una puerta de inicio de sesión no arreglaría el límite del fragmento, y no debería colarse en esta comprobación.

Compruébalo ahora con un enlace de prueba desechable

Si quieres una página que ya separa consulta y fragmento, empieza por el Burn-Link de FastPwd. Está listo para usar: no hace falta cuenta. Escribe una frase de prueba que puedas tirar y genera un enlace cifrado en este dispositivo. En la petición de creación deberías ver texto cifrado más campos de recuento y caducidad. El enlace para compartir es el que tiene forma s.html?id={id}#{key}.

Abre la página de lectura con esa cadena. La barra de direcciones debería mostrar ? y #. En Network, la petición del documento y la consulta de estado solo deberían llevar id. Después de pulsar «Abrir y ver», la petición que recoge el texto cifrado tampoco debería llevar la clave tras #. El texto en claro solo debería aparecer en la página después del descifrado local. El algoritmo es AES-256-GCM; el trabajo corre en Web Crypto. Un solo texto tiene un tope de 32 KB. Para un archivo entero, usa la caja de cifrado: un archivo de hasta 5 GB, salida .lock / .enc.

El ejercicio no pretende demostrar que un sitio es «absolutamente seguro». Pretende mantener dos hechos separados. Capa uno: el anfitrión no ve el fragmento, según la especificación. Capa dos: quien ve el enlace entero tiene la clave. En cualquier página que afirme «la clave va detrás de la almohadilla», los pasos no cambian: contrasta la barra de direcciones con la línea de petición, mira la analítica por separado y pregúntate si esta URL va a acabar en un hilo de chat.

Antes de enviar el siguiente, quédate con tres líneas. Primera: un id puede entrar en la consulta; una clave solo puede entrar en el hash. Segunda: una línea de petición limpia solo significa que el servidor no recibió la clave. Tercera: guarda el enlace entero como una credencial; no lo reenvíes como una URL de documento cualquiera. Una observación que puedas repetir es la que merece entrar en vuestras propias notas de entrega.

Para seguir leyendo

La nota anterior convierte «¿salió el texto en claro de esta pestaña?» en una comprobación repetible en Network. Léela junto a esta: Cómo comprobar que el cifrado local del navegador no envió el original. Este artículo solo separa ? de # y nombra la capa que cae al reenviar el enlace entero.