Evita que userscripts y extensiones parcheen tu app web

Un userscript, un parche de Tampermonkey o una extensión tiene que anclarse a un nombre, una cadena o una forma reconocible de tu JavaScript. Qué les hacen a esos anclajes la ofuscación estructural y una forma de build nueva.

Un userscript, un parche de la comunidad o una extensión modifica tu app buscando texto en el JavaScript que publicas y reescribiéndolo. Cambia lo que hay que buscar, en cada versión, y el parche deja de aplicarse.

A qué se ancla un parche

Quien parchea necesita un anclaje estable: un nombre de función, una cadena literal, una forma reconocible que pueda volver a encontrar en el siguiente archivo que descargue. La minificación deja los tres en su sitio. Un bundle minificado sigue teniendo nombres de función, acortados pero consistentes de un build a otro, los mismos literales de cadena y la misma estructura general, así que un parche escrito para una versión sigue coincidiendo con la siguiente.

Por eso un buen parche sobrevive durante meses. Su autor lo prueba contra tu build actual, lo distribuye a sus usuarios, y sigue funcionando en cada versión que no toque sus anclajes.

El cambio estructural elimina la forma que busca un parche

Renombrar por sí solo solo movería el anclaje. A partir de medium, AfterPack reescribe el propio programa: el flujo de control se aplana y se reordena, los valores se recalculan en cada uso en lugar de almacenarse, y el código que era independiente sale entrelazado. Lo que se publica es un equivalente funcional de lo que escribiste, un programa distinto que se comporta igual, elegido por la seed de ese build.

Así que un parche que antes insertaba una línea en checkAccess primero tiene que localizar el código que hace lo que hacía checkAccess. Ya no es una función con ese nombre, ni una función con esa forma. Cómo funciona AfterPack explica el mecanismo.

Las cadenas que busca quien parchea dejan de existir

strings.encode está activado con cualquier objetivo de complexity positivo, así que los literales de cadena de tu JavaScript no aparecen como texto en el artefacto. El nombre de la función, el tipo de mensaje, la ruta de la API, la cadena del derecho de acceso, el texto que se muestra cuando se cierra una barrera: un grep sobre el archivo publicado no devuelve ninguno de ellos, y tampoco lo hace un buscar y reemplazar escrito contra alguno.

Esos literales son el anclaje más barato que puede usar un parche, porque sobreviven a la minificación sin cambios y sirven de documentación de lo que hace el código que los rodea. La codificación es la parte del build que los elimina.

Cada versión vuelve a mover los anclajes

La seed es nueva en cada build salvo que fijes una, así que los nombres, las codificaciones y la forma difieren de tu última versión. Un parche pasa entonces a ser un coste recurrente para quien lo mantiene: el trabajo se rehace contra cada build que publicas, y un script ya distribuido a los usuarios deja de funcionar en el momento en que despliegas.

Sube la protección donde trabaja quien parchea

medium sube la protección en todo el build:

{
  "preset": "medium"
}

Para el código al que realmente apunta un parche, como una comprobación de derechos de acceso, sube a hard:

$ npx afterpack@latest dist/ --preset=hard

Deja producción con una semilla aleatoria. Una semilla constante es lo que permite que un mismo parche siga coincidiendo versión tras versión; Estrategia de semilla cubre los casos en que fijar una es seguro. Si publicas pocas veces, vuelve a compilar de forma programada para que la forma siga rotando; consulta Rota en un calendario.

Confirma que los anclajes han desaparecido

Busca en el artefacto que vas a desplegar los nombres y las cadenas a los que apuntaría un parche. No debería aparecer nada.

Abre el Protection Map, que se escribe cuando protectionMap.enabled está activado, y confirma que el código al que iría un parche está cubierto con la complejidad que esperas. Después ejecuta verify como último paso antes de desplegar: un build escribe un recibo de protección en su salida, y verify vuelve a calcular el hash de cada archivo que nombra el recibo y falla cuando alguno ya no coincide.

Lo que esto no impide

Los parches continúan. Alguien que lea el nuevo build puede escribir un nuevo parche contra él, y nada en un build impide que una extensión modifique tu página en un navegador que controla su dueño.

Un userscript que se ancla al DOM en lugar de a tu JavaScript, buscando clases CSS, la estructura de los elementos o el texto visible, no se ve afectado por lo que AfterPack hace al bundle. Lo que cambia es todo parche que tiene que encontrar código: a su autor le cuesta el trabajo de nuevo en cada versión, y sus usuarios ejecutan un script roto hasta que reciben una actualización.

Siguiente