Pinchas un enlace a un periódico local, y carga el titular, luego la foto, luego los tres primeros párrafos, y ya has empezado a leer antes de que pase nada más. Entonces la página se queda en blanco. O se convierte en un montón de enlaces sin estilo con el logo estirado a lo ancho de toda la pantalla. O sube un recuadro que te pide que aceptes el rastreo o pagues £2,99 al mes, sin tercer botón.
El artículo estaba en tu máquina. El servidor del editor ya lo había enviado entero, texto y hojas de estilo, y tu navegador ya lo había dibujado. Lo que se lo llevó fue código que el editor decidió ejecutar después, en tu ordenador, comprado a un proveedor comercial cuyo producto consiste en quitarte lo que se acaba de entregar.
En casa tengo un Pi-hole1, que bloquea los hosts de publicidad y rastreo para todos los dispositivos de la red. En una lista creciente de sitios de noticias británicos, eso bastaba para que la página quedara destruida. Así que el 22 de septiembre de 2026 construí una extensión de Chrome para impedirlo, llamada Keep The Page. El código está en GitHub en damo2929/browserplugin, con licencia MIT, y esto es lo que hace, lo que encontré dentro de esas páginas mientras la construía y los arreglos que lo empeoraron antes de que apareciera el bueno.
No es un saltador de muros de pago. Si el servidor nunca envió el artículo, nada de esto lo va a hacer aparecer. The Times siguió cerrado, y así debe ser. Lo que conserva es lo que ya te habían enviado.
El artículo llega, y luego desaparece
Vistos desde fuera, todos funcionan igual. El HTML llega completo, y luego un script, normalmente cargado desde un host de un proveedor y no del periódico, decide que no mereces verlo, y lo quita.
Qué quita, y cómo, depende del proveedor que compró el editor. Estos son los cuatro con los que me encontré, cada uno leído de una página real durante la construcción:
| Editor | Qué llega | Qué se hace luego la página a sí misma |
|---|---|---|
| Newsquest (269 cabeceras)2 | el artículo completo | levanta un muro, ejecuta una carga eval, abre un diálogo confirm() |
| notebookcheck.net | el artículo completo | borra <body> a los 7 segundos más o menos, luego diálogos, luego un bucle de recarga |
| National World (The Scotsman, Yorkshire Post)3 | el artículo más unos 68KB de su propio CSS | borra cada <link> y <style> cada 100ms, para siempre |
| Reach plc (Mirror, Daily Record, Manchester Evening News, Liverpool Echo y otros)4 | el artículo completo | lo tapa con «acepta el rastreo o paga £2,99/mes» |
El 269 no sale de una nota de prensa, que habla de «más de 200 marcas». Es el número de dominios en los propios certificados TLS de Newsquest, la lista que la extensión necesitaba para saber dónde ejecutarse.
El caso de National World es el que más debería preocupar a los editores, porque rompe la página por razones que no tienen nada que ver con la publicidad. El decapador de hojas de estilo tiene un compañero que recupera el CSS desde el host del proveedor. Mi Pi-hole bloquea ese host. Así que el decapado se ejecutó, la restauración nunca llegó, y la maquetación del propio periódico quedó dependiendo para siempre de que un proveedor de publicidad fuera alcanzable. Eso no es un muro. Es un editor que entrega el aspecto de su propia web a un tercero sin darse cuenta.
¿Puedo preguntar por qué la hoja de estilo de un periódico tiene que esperar al servidor de una empresa de publicidad antes de poder quedarse en la página? No hay ninguna razón que sirva al lector. Ninguna.
Por qué lo llamo malware
Uso la palabra a propósito, como descripción de lo que hace el código, y no como conclusión jurídica sobre nadie. Las cargas anti-adblock cumplen el sentido corriente en cuatro puntos, y cada uno se observó directamente:
| Criterio | Lo que vi |
|---|---|
| Se ejecuta sin consentimiento, contra tu interés | nadie lo pidió, y su trabajo es quitarte contenido que ya tienes |
| Destruye datos que ya te han entregado | el artículo y su CSS llegan intactos, luego código dentro de la página los borra |
| Ofuscado para resistir la lectura | tablas de cadenas permutadas como o[293 * (r + 450) % e], ejecutadas mediante eval |
| Esquiva el bloqueo y castiga la interferencia | CNAME cloaking para esquivar las listas de bloqueo DNS, comprobaciones antimanipulación que escalan a un diálogo o un bucle de recarga |
La ofuscación no es minificación. El código minificado es pequeño. Esto es código dispuesto para que no puedas buscar con grep lo que hace, y la carga pasa luego por eval para que nada en el disco coincida con lo que se ejecuta.
El cloaking es una técnica conocida con su propia literatura de investigación5. El cargador se pide desde lo que parece un subdominio del periódico, y ese nombre apunta al proveedor:
a02342.<publisher-domain> -> cdn-52-x.privacy-mgmt.com (Sourcepoint)
fb.html-load.com -> adshield-fallback-dev-wskxz.b-cdn.net
El subdominio es aleatorio para cada cabecera. Una lista de bloqueo que nombra hosts no puede seguirle el ritmo, y de eso se trata.
Y el código trata cualquier interferencia como prueba de culpa. Los mensajes de error descodificados en la carga que los mantenedores de listas de filtros atribuyen a Ad-Shield6 son literalmente Vital API blocked y Vital API blocked (eval). El cargador de Sourcepoint escribe un atributo, lo vuelve a leer al instante y lanza un error si el valor ha cambiado:
z.call(O,'src',G), O[x]('src') !== G && throw E
… catch (W) { try { await l(W) } catch (x) { o(W) } } // o() raises the dialog
Sourcepoint ha vendido esto abiertamente. Su propia documentación decía «on average about 30% of messaged users will turn off their adblockers»7. Así que no describo un script rebelde que alguien coló. Es un producto, comprado y desplegado a propósito por el editor.
Los muros consent-or-pay son otra categoría, y a esos no los llamo malware. No destruyen lo entregado ni se esconden de las listas de bloqueo. Coaccionan de otra manera, y de eso trata la siguiente sección.
Acepta 1 467 socios o paga £2,99
El muro en las cabeceras de Reach es Quantcast Choice, ahora gestionado por InMobi8 y servido desde cmp.inmobi.com. Da dos opciones. Aceptar, o pagar £2,99 al mes. No hay un «no» gratuito.
Aceptar comparte tus datos con 1 467 socios listados y escribe una cookie euconsent-v2 que dura 13 meses. Nadie lee una lista de 1 467 empresas, nadie podría sopesar qué haría cada una con los datos aunque la leyera, y la cifra por sí sola te dice qué clase de consentimiento se está pidiendo.
El RGPD británico dice que el consentimiento tiene que darse libremente, y que al juzgarlo se mira si el servicio se ha condicionado a un consentimiento para un tratamiento que no necesita9. El ICO ha publicado orientaciones según las cuales el consent or pay puede ser lícito10, orientaciones que ahora dice que están en revisión, y el Comité Europeo de Protección de Datos ha dicho que para las grandes plataformas que solo ofrecen esas dos opciones, en la mayoría de los casos no lo será11. No voy a fingir que el regulador lo ha prohibido. No lo ha hecho.
Así que aquí es donde llego, sin rodeos. Elegí quitar el muro y rechazar el consentimiento. Eso significa que leo el artículo por la rama gratuita, sin pagar y sin entregar mis datos a 1 467 empresas. Es una decisión. La extensión lo dice en su propia página de ajustes, y no la disfrazo de algo neutral. Un consentimiento que no puedes rechazar es un precio, y no voy a pagar un precio disfrazado de pregunta.
Quitar el recuadro es el tercio fácil. Los otros dos tercios están en Responder bien a la pregunta del consentimiento, porque un banner que borras sin responder vuelve cada vez.
Un día, ocho commits
Todo se construyó en un día. Unas horas husmeando en páginas antes de hacer ningún commit, y luego ocho commits entre las 20:32 y las 22:50. Lo construí con Claude Code, y buena parte del trabajo duro de leer script inline ofuscado línea a línea lo hizo el agente mientras yo miraba lo que las páginas hacían en pantalla. Ese reparto funcionó bien, y dónde falló está más abajo, porque falló de una manera que conviene conocer.
| Hora | Commit |
|---|---|
| 20:32 | primer commit: Newsquest, notebookcheck, National World, Reach, Page Six |
| 20:46 | el proveedor detrás de cada mecanismo, anotado en el README |
| 20:56 | 50 enlaces de la portada de Google News, como muestra no elegida |
| 21:02 | responder a la API de consentimiento con todas las finalidades denegadas |
| 21:11 | enlaces sociales desactivados |
| 22:50 | protecciones desactivables, MSN y Bing, rechazo del consentimiento con un clic, 77 tests |
Es una extensión Manifest V3 con dos permisos, declarativeNetRequest y storage, sin permisos de host y sin acceso propio a la red, así que no puede descargar nada, reescribir ninguna respuesta ni hablar con ningún servidor en tu nombre. Esa restricción dio forma al diseño más que ninguna otra cosa, porque el trabajo interesante tiene que ocurrir dentro de la página.
El mundo MAIN de Chrome comparte el entorno JavaScript de la página12. Un script ahí puede sustituir setTimeout, envolver document.cookie o definir window.adLight antes que la página, y eso es justo lo que hace falta para pelear con estos muros. En la práctica no puede llamar a chrome.storage. El mundo aislado sí puede, pero no ve las funciones de la página. Así que un pequeño puente lee los ajustes y los escribe en <html>, y los errores suben como un CustomEvent cuyo detalle es una cadena JSON, porque un objeto no cruza esa frontera de forma fiable.
El atributo que baja solo lista lo que has desactivado. Una instalación por defecto no escribe nada en la página. Eso importa, porque una marca permanente en <html> es justo el tipo de cosa que buscan estos SDK, y porque así un fallo del almacenamiento falla hacia defender la página y no al revés.
Encuentra la puerta, no pelees con el muro
El arreglo de Newsquest son dos líneas de razonamiento, y es con el que se midió todo lo demás.
Todo su muro depende de una sola bandera en la página:
var adLight = false; // line 1647
if (adLight !== true) { …} // line 2020: loader, eval payload, confirm()
adLight es la bandera de suscriptor de «pocos anuncios». Si es verdadera, el muro no se levanta nunca. Así que la extensión define window.adLight como true en document_start, antes de que se ejecute el propio script de la página, con un setter que ignora lo que se escriba en él:
Object.defineProperty(window, 'adLight', {
configurable: false,
enumerable: true,
get() { return true; },
set() { /* ignore the page's "false" */ }
});
Un var al principio de un script no redefine una propiedad que el objeto global ya tiene. Solo le asigna un valor13. Así que el propio var adLight = false de la página se ejecuta, cae en el setter y no hace nada. El cargador del muro no arranca nunca, la carga eval no llega nunca y no hay ninguna comprobación antimanipulación que saltar, porque no se ha manipulado nada. La bandera simplemente dijo que sí.
Es la única propiedad de toda la extensión que no es configurable. Tiene que serlo para sobrevivir a la declaración. Todo lo demás es configurable: true, para que a ninguna página se le quite para siempre una de sus propias APIs.
Cubre 269 cabeceras y no se puede desactivar en los ajustes, que explican por qué: se ejecuta antes de que chrome.storage pueda responder, y una vez puesta no se puede deshacer. Una casilla sería decoración.
Cada arreglo que lo empeoró
Esta es la sección útil, porque cada error aquí es lo primero que a uno se le ocurre probar.
| Lo que probé | Lo que pasó |
|---|---|
| Bloquear el host del cargador | el host es un CNAME first-party aleatorio por cabecera, y una petición fallida es en sí misma la señal de detección |
Vigilar setAttribute en los scripts inyectados | disparó la comprobación de relectura de Sourcepoint, que abrió el diálogo que se quería evitar |
Responder al confirm() con Cancelar | en este SDK, Cancelar significa recargar, así que provocó un bucle de recarga infinito |
Guardar una copia de <body> en DOMContentLoaded para restaurarla después | el muro vacía la página durante el análisis, así que la copia era de un body vacío |
Vigilar remove() y removeChild() | el muro limpia la página con un solo innerHTML = '', no nodo a nodo |
| Vigilarlo todo a la vez en notebookcheck | el muro escaló a un diálogo y luego a un bucle de recarga, claramente peor que no hacer nada |
| Redirigir el host del proveedor a un stub local | una regla redirect con solo declarativeNetRequest invalidó todo el conjunto de reglas y mató en silencio la regla de Newsquest en 269 sitios |
| Ejecutar los vigilantes de página en todos los sitios | parcheó prototipos globales en cada página que visitaba, incluida la de mi banco |
La redirección merece una segunda mirada. Chrome da a una regla block acceso implícito y exige permiso de host para cualquier otra cosa14. Su documentación dice que las reglas estáticas no válidas se ignoran15. Lo que yo vi fue peor: se fue el archivo entero, sin ningún error en la página, y la regla 1 simplemente dejó de existir en 269 sitios. Por eso las dos reglas de MSN viven ahora en un conjunto propio, para que un mal cambio en uno no se lleve por delante al otro.
El bucle de recarga enseñó la otra regla dura. location.reload no se puede interceptar. El objeto Location es infalsificable en el estándar HTML16, así que sus métodos no son modificables ni configurables, e intentarlo da:
Object.defineProperty(location,'reload',...)
-> TypeError: Cannot redefine property: reload
Un muro cuyo camino de fallo es «recarga la página» ya no se puede detener una vez está en ese camino. Con nada. El único arreglo es asegurarse de que nunca llegue ahí. Es la misma lección que con adLight, aprendida por las malas: cada intento de pelear con un muro ya en marcha lo empeoró, y cada arreglo que funcionó impidió que el muro arrancara.
Un solo temporizador hizo el daño
notebookcheck no tiene adLight. El muro se ejecuta siempre. Con el trazado activado, la extensión mostró lo que programaba:
dropped setTimeout(7005ms) scheduled from eval <- the body.remove()
dropped setTimeout(1251ms) / 105ms / 0ms x8
dropped setInterval(15000ms)
Uno de esos temporizadores hace todo el daño: el de los 7 segundos que quita <body>. Todo lo que viene después es reacción, porque la página vacía lanza un error, la excepción abre el diálogo, el diálogo recarga la página y todo vuelve a empezar desde arriba con una nueva tanda de temporizadores.
Así que el arreglo es una sola regla. Descarta un temporizador si se programó desde código pasado por eval, la página lleva la firma de este SDK y el manejador es una función. La pila dice de dónde vino una llamada, y eval deja su marca en ella.
before: 4 page loads, 3 confirms, reload loop
after: 1 page load, 0 confirms, alive 40,378ms, content intact
En ese momento había otros dos mecanismos en el build, una redirección del cargador a un stub y un elemento señuelo para absorber las escrituras del muro. Los dos funcionaban, y los dos trataban síntomas de ese único temporizador, cosa que solo quedó clara al probar la regla de temporizadores por separado y ver que bastaba por sí sola. Así que los dos se fueron, y con ellos un permiso y todos los permisos de host. Comprueba siempre si el último cambio basta por sí solo antes de conservar el andamiaje de alrededor.
La firma es el atributo data-sdk de la etiqueta del cargador, y encaja con una forma, l/<n>.<n>, en vez de con una versión. En un día aparecieron tres versiones. Se queda enganchada y nunca guarda un negativo, porque puede que la etiqueta del cargador todavía no se haya analizado cuando se programan los primeros temporizadores, y un «no» recordado la desarmaría para siempre en una página que sí lleva el muro.
La medición decía que bien. La pantalla, no.
The Scotsman y el Yorkshire Post se dieron por arreglados basándose en una medición que contaba texto. La página tenía 8 808 caracteres, 22 elementos bajo <body>, estables a los 2, 8 y 16 segundos. Según esa medida, estaba intacta.
No lo estaba. Yo la estaba mirando, y todas las hojas de estilo habían desaparecido, los enlaces eran una lista pelada, el logo SVG llenaba la pantalla y había una barra de desplazamiento horizontal. Todas las palabras estaban ahí, y eso es todo lo que esa medición podía ver. Tuve que señalar la pantalla y decirlo.
La causa era el decapador de la primera tabla, y no encajaba con la regla de temporizadores, porque es un script inline normal y no eval. Así que su propio código fuente es la firma: una tarea repetida cuyo cuerpo llama a querySelectorAll('link,style') y luego a remove(). Nada legítimo hace eso. Se descarta al programarse, nunca se decapa nada y no hace falta restaurar nada. El Yorkshire Post pasó de 0 hojas de estilo a 2, con 532 reglas, y se mostró bien.
Encontrarlo necesitó una traza de pila, no una suposición. Parchea Element.prototype.remove para que registre la pila cada vez que quita un STYLE o un LINK, y señaló el script inline y el forEach al primer intento.
Desde entonces, document.styleSheets.length entró en cada comprobación. Una página sin hojas de estilo está rota, tenga el texto que tenga. Medir es más difícil de lo que parece, y estas son las trampas en las que cayó el build aquel día:
| Trampa | Lo que decía | Lo que era verdad |
|---|---|---|
| longitud del texto como prueba de renderizado | «intacta» | marcado sin estilo |
| una sola muestra tras la carga | muros de Reach «ausentes» en ocho cabeceras | el muro vive unos 600ms y ya se había barrido |
| consola vacía tras navegar | «el vigilante no se dispara» | los mensajes de la carga no sobreviven a la navegación |
| muestras a 1s y 4s | notebookcheck sano | se vacía entre los 5 y los 8 segundos |
cssRules contado entre orígenes | ESPN tenía 5 reglas | las hojas de otros orígenes lanzan error, así que la cuenta sale baja |
El arreglo para el caso de los 600ms es consultar cada 100ms desde el momento en que carga la página. En Wales Online el muro apareció a los 425ms y había desaparecido a los 1 129ms.
Luego las comprobaciones se hicieron en serio. 52 artículos en 26 dominios, dos por sitio, elegidos para cubrir cada mecanismo: 52 de 52 con hojas de estilo, ningún muro en pantalla, ningún bucle de recarga. Luego 50 enlaces sacados directamente de la portada británica de Google News, no elegidos por mí: 45 se mostraron con normalidad, 2 eran hosts que mi Pi-hole bloquea a propósito, 1 era el muro de pago de verdad de The Times, y 2 eran el mismo artículo aterrizado dos veces porque Google reconstruye el orden de los enlaces en cada carga. Ninguno con cero hojas de estilo. Tres cabeceras de National World que yo nunca había probado aparecieron en esa ronda con el mismo SDK, y las tres se mostraron bien, que es justo para lo que sirve encajar con una forma en vez de con una lista de sitios.
Hay 77 tests unitarios, en el repositorio con todo lo demás. En esta máquina no hay Node, así que se ejecutan en gjs y cargan el walls.js real contra un DOM de sustitución, para que lo que se prueba sean los patrones de producción. Cada uno se comprobó rompiendo lo que vigila y viéndolo ponerse en rojo. Solo prueban decisiones. Que pasen no significa que una página se muestre, y el Scotsman es la razón de que esa frase esté en el README.
Responder bien a la pregunta del consentimiento
Borrar un banner de consentimiento deja la pregunta sin responder. Eso tiene dos consecuencias, y la primera es que el banner se reconstruye en cada carga de página. Y un editor que retiene su contenido hasta que la API de consentimiento responda se quedará colgado sin más, mientras que un proveedor que no recibe respuesta puede tratar la pregunta como si nunca se hubiera hecho. El silencio no es un rechazo.
Así que la extensión la responde, en este orden:
Primero pulsa su botón de rechazo. Solo botones cuya etiqueta entera sea un rechazo: «Reject all», «Decline», «Only essential», «Continue without accepting» y algunos más, cada uno anclado, y todo lo que pase de 40 caracteres se ignora. Un test unitario le da «I Accept», «Accept All», «Agree and close», «Allow all», «Got it», «Subscribe» y «Pay £2.99/mo» y hace fallar el build si alguno coincide. Pulsar el botón equivocado sería consentir en tu nombre, y eso es lo único que este proyecto no debe hacer nunca. En msn.com, pulsar «Reject All» hizo desaparecer el banner de Microsoft y no volvió al recargar, porque Microsoft guarda el rechazo en sus propios servidores.
Responde que no a la API de consentimiento. El marco del IAB da a cada página que pide consentimiento una función llamada __tcfapi para preguntar a qué has accedido17. Donde existe, la extensión la responde con cada finalidad, cada función especial y cada proveedor denegados, una cadena de consentimiento vacía y eventStatus: 'tcloaded', que significa que la respuesta es definitiva. Solo aparece donde ya hay un marco de consentimiento en la página, así que un sitio que nunca preguntó no la ve.
Nunca guarda el registro. document.cookie y localStorage descartan en silencio euconsent-v2, addtl_consent, OptanonConsent, didomi_token y el resto, para que un consentimiento que nunca diste no se escriba nunca ni se reproduzca ante 1 467 socios en la página siguiente. La ley británica ya exige consentimiento antes de guardar nada en tu dispositivo para esto18. La extensión hace cumplir el no.
Cada nombre de esa lista está anclado por los dos extremos, y detrás hay una historia. Las cookies de Sourcepoint empiezan por _sp_. Un patrón descuidado sp_ también pilla sp_dc y sp_t de Spotify, que son la sesión de inicio, y me habría desconectado de Spotify en cada página. Un test también fija eso.
Quitar es el recurso final. Solo para un banner que siga en pantalla después del clic, y solo para un banner que de verdad se esté mostrando. Varios proveedores dejan un envoltorio permanente en la página, haya banner o no. euronews mantiene un host de Didomi con altura cero, y arrancarlo rompe la página sin ganar nada. El de Reach estaba tres niveles por debajo de <body>, dentro de dos envoltorios que no son fijos, así que la comprobación tiene que bajar hasta la caja que sí lo es.
Botones de compartir, y un portal que ignora sus propios ajustes
Otras dos cosas de estas páginas existen para servir a alguien que no es el lector, y necesitaban un trato distinto al de los muros.
Botones de compartir y seguir. Cada enlace a Facebook, Instagram, X, TikTok o LinkedIn se reescribe a http://localhost/removeme, con el original aparcado en un atributo para que no se pierda nada. Luego una segunda pasada borra lo que es claramente mobiliario (un icono sin texto, «Share on X», todo lo que esté en un contenedor que se llame share o social) y oculta el resto. La marca está ahí para que un solo selector muestre todo lo que está a punto de irse, y un modo de solo marcar se detiene tras la primera pasada para que puedas mirar antes de fiarte de ella en un sitio.
La versión ingenua rompe páginas de tres maneras, y cada una es ahora un test:
| Versión ingenua | Lo que rompe | Lo que se hace en su lugar |
|---|---|---|
a[href*="x.com"] | también pilla netflix.com, linux.com, phoenix.com | analizar el host y comparar etiquetas enteras |
| quitar cada enlace social | «Continue with Facebook» desaparece y la gente se queda fuera | enlaces de inicio de sesión, OAuth, legales y de desarrolladores intactos |
| quitar un enlace dentro de una frase | las palabras se van con él | ocultar por defecto; una opción lo desenvuelve y conserva las palabras |
Esa última es una concesión que hice a sabiendas. Una despedida de la BBC queda así: «follow BBC Manchester on , , and .». Lo miré y aun así elegí ocultar. La opción de conservar las palabras está ahí para quien no esté de acuerdo.
MSN y Bing. Los dos funcionan con los mismos web components, unas 160 shadow roots en la portada. Una consulta simple al documento encontró 1 enlace. Recorrer las shadow roots encontró 73, incluidos los mosaicos de Facebook y X. Lo que no las recorre no ve casi nada.
MSN sí tiene sus propios ajustes de contenido. Se guardan en los servidores de Microsoft, ligados a un identificador anónimo, no en una cookie, así que desaparecen tras borrar las cookies, en un perfil nuevo y en modo incógnito. A Bing no llegan en absoluto. Y con todos desactivados y 11 700 píxeles de desplazamiento, esto era lo que seguía en el feed:
| Interruptor propio de MSN, apagado | Sigue en la página |
|---|---|
| Casual Games | el mosaico de Juegos |
| Shopping | mosaicos publicitarios de Booking, Temu y eBay |
| Comments | 501 enlaces de comentarios |
| Weather, Finance, Sports | desaparecen, hasta el próximo borrado de cookies |
Un interruptor llamado Comments que deja 501 enlaces de comentarios en la página es una afirmación hecha al usuario, y una afirmación falsa. Así que la extensión los quita ella misma, apuntando a los nombres de componente estables de MSN en lugar de a sus nombres de clase, que cambian con cada build.
Una cosa que aprendí ahí y que vale mucho más allá de MSN: quitar una imagen de la página no impide que se cargue. Chrome empieza la descarga en cuanto se asigna src, incluso para una imagen que nunca se mete en la página19. Para cuando un content script ve una tarjeta, su miniatura ya va por el cable. Así que los datos del feed se bloquean en los dos puntos de acceso que los sirven, y nunca los hosts de imágenes, porque th.bing.com también sirve la búsqueda de imágenes de Bing.
Y el feed de MSN todavía parpadea un momento en pantalla antes de desaparecer. Cuatro intentos de ocultarlo antes se midieron todos como eficaces y ninguno paró el parpadeo, lo que significa que lo que se pinta no es lo que la extensión oculta. La página de ajustes lo dice claramente. Prefiero que lo diga a que insinúe una carga limpia que no entrega.
Lo que no hace
| No va a | Porque |
|---|---|
| abrir un muro de pago de verdad | si el servidor retiene el artículo, sigue retenido |
| adivinar con un sitio | un dominio solo entra después de ver su muro ahí; dos sitios de News Corp que se suponían iguales a Page Six no lo eran, y volvieron a salir |
| tocar un sitio sin firma | en la BBC cada gancho está instalado y no se registra nada, ningún temporizador descartado, ningún nodo tocado |
| llamar a casa | sin permisos de host, sin acceso a la red, sin telemetría; el registro de errores se queda en tu navegador |
| fingir | el parpadeo de MSN no está resuelto, y una variante de Ad-Shield, wp-ls/…, todavía no encaja; su página se mostraba bien, así que queda anotado en vez de arreglado a ciegas |
Una página que te han enviado es tuya
Una vez que un servidor ha enviado una página a tu navegador, esa copia está en tu máquina. Ejecutar código sobre ella después para quitártela no es un modelo de negocio que yo reconozca. Es el viejo truco de venderte algo y seguir con la mano encima.
Los periódicos eran algo que comprabas y luego poseías: alguien en la esquina tenía un montón, dabas el dinero, y se venía a casa contigo y era tuyo, para leerlo, doblarlo, prestarlo o encender la chimenea con él. Nadie pasaba por casa una hora después a recortar la segunda página porque te habías saltado los anuncios. La versión web regaló el periódico y luego vendió al lector. Primero a los anunciantes, luego a los 1 467 socios, y ahora a un proveedor cuyo producto entero consiste en decidir si te has portado lo bastante bien como para quedarte con lo que te dieron.
Lo que no consigo tragarme es la hoja de estilo. Un periódico que deja que el servidor de una empresa de publicidad decida si su propia maquetación sobrevive ha regalado su propia portada. No lo habrá sabido, porque nadie dentro bloqueaba el host, así que el primero en enterarse fue un lector con un Pi-hole, delante de una página llena de enlaces pelados, al que un cuadro de diálogo le decía que la culpa era suya. No lo era.
El periodismo tiene un precio, y no tengo ningún problema en pagarlo. Lo que no voy a hacer es dejar que un script decida lo que ya tengo. Esa raya se traza en mi navegador, no en el suyo.
Pi-hole documentation — «The Pi-hole® is a DNS sinkhole that protects your devices from unwanted content, without installing any client-side software.» ↩︎
Newsquest — About us — «We are the leading local news publisher in the UK with a portfolio of more than 200 brands.» Las marcas no son dominios, de ahí la cifra más alta sacada de los certificados. ↩︎
Daily Business, 18 de diciembre de 2024 — «National World, owner of The Scotsman and Yorkshire Post, has reached agreement on a £65.1 million takeover by Irish publisher Media Concierge.» ↩︎
Reach plc — About us, archivado el 5 de septiembre de 2026 — «120+ brands, from household names like the Mirror, Express, Daily Record and Daily Star, to local titles like MyLondon, BelfastLive and the Manchester Evening News». ↩︎
Dimova et al., «The CNAME of the Game: Large-scale Analysis of DNS-based Tracking Evasion», PETS 2021 — el CNAME cloaking «effectively bypasses antitracking measures that rely on fixed hostname-based block lists.» ↩︎
uBlockOrigin/uAssets, incidencia #30988, clasificada por los mantenedores bajo la etiqueta «Ad-Shield», con el mensaje servido desde error-report.com: «Failed to load website properly since html-load.com is blocked.» Véase también Jacob Desforges, «Ad-Shield ad reinsertion», 12 de abril de 2026. La atribución es de la comunidad de listas de filtros; el proveedor no revela nada. ↩︎
Sourcepoint — Anti-adblock FAQs, archivado el 25 de mayo de 2022 — «Historical experience shows on average about 30% of messaged users will turn off their adblockers.» La misma página pregunta si «a CNAME applied to a 1st-party subdomain» impediría que se bloquee el script de detección. ↩︎
AdExchanger, 16 de agosto de 2023 — «InMobi acquired Quantcast’s consent management platform, called Quantcast Choice». ↩︎
UK GDPR, Article 7 — «utmost account shall be taken of whether, inter alia, the performance of a contract, including the provision of a service, is conditional on consent». Considerando 42: el consentimiento no se da libremente «if the data subject has no genuine or free choice». ↩︎
ICO — Consent or pay, publicado el 23 de enero de 2025 — «“Consent or pay” models can be compliant with data protection law if you can demonstrate that people can freely give their consent». La página dice ahora que la orientación está en revisión tras la Data (Use and Access) Act. ↩︎
EDPB Opinion 08/2024, adoptado el 17 de abril de 2024 — «In most cases, it will not be possible for large online platforms to comply with the requirements for valid consent if they confront users only with a binary choice». Trata de las grandes plataformas en línea, no de los periódicos regionales. ↩︎
Chrome for Developers — content_scripts manifest key — «Choosing the “MAIN” world means the script will share the execution environment with the host page’s JavaScript.» ↩︎
ECMAScript — CreateGlobalVarBinding — «If a binding already exists, it is reused and assumed to be initialized.» ↩︎
Chrome for Developers — declarativeNetRequest — «provides implicit access to allow , allowAllRequests and block rules», y si no «you must request host permissions before you can perform any action on a host.» ↩︎
Chrome for Developers — declarativeNetRequest — «Errors and warnings about invalid static rules are only displayed for unpacked extensions. Invalid static rules in packed extensions are ignored.» ↩︎
HTML Standard — the Location interface marca sus miembros como
[LegacyUnforgeable], lo que en Web IDL significa «the property will be non-configurable and will exist as an own property on the object itself». ↩︎IAB Tech Lab — TCF v2 CMP API — «Every consent manager MUST provide the following API function: __tcfapi(command, version, callback, parameter)». ↩︎
PECR, regulation 6 — «a person must not store information, or gain access to information stored, in the terminal equipment of a subscriber or user», con el consentimiento como condición en Schedule A1, modificado por la Data (Use and Access) Act 2025. ↩︎
HTML Standard — update the image data — se ejecuta «whenever that element is created or has experienced relevant mutations», también cuando se asigna su
src; estar en el documento no es una condición. ↩︎