Cómo funciona el ofuscador de JavaScript AfterPack

Qué hace la salida estándar (Free) con tu código, cómo resiste la reversión y qué puede recuperar realmente un atacante.

AfterPack es un protector de JavaScript que actúa después del build. Toma el bundle que produce tu cadena de herramientas y lo reescribe en algo que una persona, o un agente de IA, no puede leer, buscar ni modificar sin un esfuerzo desproporcionado. Se integra en tu build actual con una línea, o se ejecuta sobre el directorio de salida sin ningún plugin.

Sin capas de ofuscación que se puedan pelar

La mayoría de los ofuscadores entregan dos cosas que un atacante puede explotar: una pila de capas aplicadas una tras otra y estructuras prefabricadas que una herramienta reconoce a simple vista (un array de cadenas, aunque esté cifrado, un intérprete de VM propio, un arranque de decodificador reconocible). La receta para revertirlo es mecánica: identificar una capa o estructura por su firma, quitarla y repetir hasta que aparezca el original. webcrack y restringer hacen exactamente eso, en microsegundos.

AfterPack no entrega nada de eso. No hay ninguna capa que pelar ni ninguna estructura fija que identificar: cada parte de la salida depende de todas las demás, y la forma completa cambia con cada build, así que una herramienta escrita contra un build no sirve para el siguiente. Cómo se compara con esas herramientas tiene su propia página.

Los agentes de IA cambiaron la amenaza

Un modelo de última generación no se limita a resumir código minificado: agentes como Claude Code ya desofuscan la salida de los ofuscadores tradicionales. En un experimento público, un agente devolvió tanto javascript-obfuscator como una herramienta comercial (JScrambler) a una lógica legible en unos diez minutos (el experimento está descrito en nuestro blog).

El renombrado, un array de cadenas y unas cuantas capas ya no aportan protección: son exactamente los patrones que mejor manejan estas herramientas y modelos. El modelo de amenaza clasifica qué adversario vence a qué técnica. Lo que sigue funcionando es una salida sin estructura fija que aprender y con una forma nueva en cada build: lo que una herramienta aprende de un build no se aplica al siguiente.

Qué hace ya la ofuscación estándar

AfterPack se ejecuta después de tu bundler, sobre la salida compilada, y nunca toca tu código fuente, mediante un plugin de framework o la CLI posterior al build. Inicio rápido es la versión de cinco minutos.

AfterPack in your build pipelineYour source.ts / .tsx / .jsBundlerNext / Vite / …AfterPackpost-build sealPolymorphic bundledifferent every releaseCDNship itrandom seed per release → a structurally different artifact every time

Free aplica todo esto:

  • Codifica tus literales de cadena. Con cualquier objetivo de complejidad positivo, el mínimo de cadenas está activado, así que un literal de cadena se reescribe en un decodificador en tiempo de ejecución elegido por la semilla en lugar de quedar en el artefacto como texto. Está activado por defecto y es la mayor diferencia respecto al código minificado. Los prólogos de directivas, los operandos de typeof, los nombres de propiedad que necesita el runtime y los nombres de exportación siguen siendo legibles, y algunos valores de cadena del código fuente pueden quedar dentro de bundles de terceros muy minificados; el build informa de cuántos sobrevivieron y el Protection Map los enumera.
  • Reescribe tu lógica en formas equivalentes pero que destruyen información: aritmética booleana mixta, predicados opacos, una VM de bytecode para expresiones enteras. Un atacante puede recuperar un programa que funciona, no tu original; la elección algorítmica desaparece. transforms.<kind>.enabled activa o desactiva cada familia.
  • Remodela el control y la estructura: aplanamiento del flujo de control, ramas muertas, anidamiento de ámbitos, entrelazado de dependencias cruzadas.
  • Lo cambia todo en cada build, mediante el polimorfismo por build (abajo).

El valor por defecto sin configuración es light: el mínimo de cadenas y los identificadores renombrados, sin inflación estructural. Subir el preset a medium o superior añade el trabajo estructural descrito arriba; la legibilidad de las cadenas ya está en su mínimo sea cual sea el preset. Todo esto se ejecuta en todos los niveles, Free incluido; Niveles es donde se define la frontera entre Free y Pro.

Un programa nuevo en cada build

AfterPack deriva su salida de una semilla por build que es aleatoria por defecto. Dos builds del mismo código fuente salen estructuralmente distintos: nombres distintos, codificaciones distintas, una forma distinta en todas partes.

A partir de medium, esto va más allá de una reorganización cosmética. AfterPack cambia la estructura del propio programa, no solo su texto: el flujo de control se aplana y se reordena, los valores se recalculan en cada uso en lugar de almacenarse, y partes del bundle que eran independientes salen entrelazadas entre sí. Lo que se publica es un equivalente funcional de lo que escribiste, un programa distinto que se comporta igual, y la semilla decide qué equivalente obtienes esta vez.

Así que no hay ninguna estructura fija que identificar, y un desofuscador escrito contra un build no sirve para el siguiente. Al atacante le queda revertir a mano un build concreto, y ese trabajo no se transfiere: la siguiente versión vuelve a ser un programa distinto. Publicar a menudo repite el coste cada vez. Builds y versiones explica cómo convertirlo en un hábito.

Para una salida reproducible, por aciertos de caché o auditorías, fija la semilla con --seed=git en la CLI, o con seed: "git" en un plugin. Ambos la derivan de tu commit. Consulta Builds y versiones.

Qué puede recuperar realmente un atacante

No todas las transformaciones elevan el coste de la reversión de la misma forma. AfterPack clasifica su protección en clases de reversión; saber cuál cubre una región te dice con qué se queda un atacante.

Son clases de atacante estático: describen a qué se enfrenta alguien que lee los bytes, con grep, herramientas de AST o un LLM que explica código sin ejecutarlo. Un atacante que ejecuta el artefacto bajo un runtime instrumentado es un caso aparte y más fuerte, tratado en Alcance más abajo y en la página del modelo de amenaza.

Un build en medium o superior alcanza las dos clases disponibles en todo el bundle; un build light se queda en la primera, renombrado y codificado. El Protection Map colorea cada token según su complejidad y nombra la clase del código que selecciones.

Las sobrescrituras por región, preset=hard y preset=extreme, suben el objetivo de complejidad de una región y fuerzan su mínimo de cadenas, concentrando ese mismo trabajo donde tú lo pides.

Cómo sobreviven los secretos

Proteger un secreto usa un mecanismo distinto al de destruir la procedencia. Un secreto (una clave de API, una semilla de licencia, un umbral) aparece en cualquier programa que se comporte igual, así que destruir cuál era el original que escribiste no lo oculta.

Lo que oculta un secreto de un lector estático o de un LLM a ciegas es la no materialización: el valor nunca aparece en la salida. transforms.comparisonHardening.enabled la consigue para un literal con el que tu código compara, reescribiendo la comparación como una comprobación de digest unidireccional, de modo que solo se publica el digest. preset=extreme sobre un literal secreto hace que recuperarlo sea caro, y el literal sigue existiendo en la salida.

Un atacante que ejecuta tu código bajo un runtime instrumentado puede llegar a cualquier secreto que use el propio código. Ningún ofuscador en tiempo de build cambia eso: si el programa en ejecución necesita el valor, un atacante dinámico decidido puede observarlo. La no materialización vence al lector estático y al LLM a ciegas, y eleva el coste de volver a extraerlo en cada rotación. No hace irrecuperable un secreto del lado del cliente. Los secretos de alto valor deben estar en el servidor, donde el cliente nunca los tiene.

Alcance

Una comprobación de licencia en una región /* @afterpack preset=hard */, publicada a menudo con una semilla variable, no compensa las horas de ingeniería necesarias para romperla antes de que llegue la siguiente versión. Para la mayoría de los productos, ese es el listón adecuado.

La defensa es económica. Con tiempo ilimitado, cualquier código del lado del cliente puede revertirse en cierta medida. El objetivo es llevar el coste por encima del valor y hacer que ese coste se repita en cada versión. Cuando un activo vale más que cualquier esfuerzo de reversión, mantenlo en el servidor, donde el cliente nunca lo tiene. AfterPack refuerza esa arquitectura; no la sustituye. La guía de buenas prácticas explica cómo invertir la protección donde cuenta.

Siguiente

  • Clases de reversión: la tabla de tres clases en profundidad, por transformación.
  • Modelo de amenaza: la escala de adversarios de A0 a A5 y el eje estático frente a dinámico.
  • Protection Map: la vista por token que demuestra qué clase afectó a cada línea.
  • Buenas prácticas: qué marcar, qué dejar sin tocar y cómo rotar builds.
  • Niveles: la frontera completa entre Free / Pro / Team / Enterprise.
  • Inicio rápido: instala el plugin y publica salida protegida en minutos.
  • Frameworks: el plugin para tu stack, o la vía de la CLI cuando no existe uno.
  • Configuración: cada clave mencionada arriba y qué superficie puede establecerla.
  • Presets: la escala minify/light/medium/hard/extreme.
  • Directivas: la gramática de marcadores detrás de preset=hard y skip.