Documentación
/
Recetas
/

Sustitución de plantillas en el servidor

Sustitución de plantillas en el servidor

Pro
v6.10.0+

Inyecta valores renderizados en el servidor (por ejemplo, marcadores `{{ .Field }}` al estilo de las plantillas de Go) en JavaScript ofuscado con VM sin romper el pipeline de ofuscación.

El problema

Tu backend (Go, Rails, Django, PHP, …) sirve un archivo JavaScript cuyo contenido debe renderizarse parcialmente en cada solicitud: un endpoint de la API, una lista de feature flags, un bloque de estado inicial, un id de compilación, un nonce. Ofuscas el JS con vmObfuscation: true, pero también necesitas que el motor de plantillas sustituya los marcadores en la salida ofuscada después de que termine la ofuscación. reservedNames y reservedStrings mantienen un marcador visible en la salida para que pueda reemplazarse. Sin ellas, un marcador de cadena se absorbe en el bytecode de la VM y un marcador de identificador se emite en varios lugares reescritos, así que el motor de plantillas no encuentra nada que sustituir o rompe la salida.

Considera cargar los valores como datos

Si puedes, evita por completo editar la salida protegida: carga la configuración de ejecución como datos antes de que se ejecute el bundle protegido. Mantenla en un script separado u obtenla de un endpoint autenticado. Así la salida protegida queda intacta y vmSelfDefending puede seguir activada. En cualquier caso, los datos que se entregan a un navegador son visibles para ese navegador.

JavaScript

Usa los patrones siguientes cuando los valores realmente tengan que sustituirse dentro del archivo ofuscado.

Una nota sobre los ejemplos en Go: hacen un reemplazo simple de cadenas (strings.ReplaceAll / strings.NewReplacer) sobre la salida ofuscada y gestionan su propio escapado, que se muestra en cada patrón. No pasan por el paquete html/template de Go, que aplicaría encima su propio escapado contextual de JavaScript y escaparía dos veces el contenido. Si renderizas con html/template, elimina el escapado manual y deja que el motor escape una sola vez.

¿Qué opción elijo?

El valor del servidor es…Usa
Un valor JS sin procesar (array, objeto, número, booleano: cualquier valor JSON)Patrón 1: reservedNames + marcador de identificador
Una cadena (el caso habitual: JSON renderizado, un id de compilación, un nonce)Patrón 2: reservedStrings + marcador de plantilla literal
Una cadena, cuando el código debe seguir siendo ES5 o la API que lo rodea necesita un literal de cadena entre comillasPatrón 3: reservedStrings + marcador de cadena entre comillas

La sustitución posterior a la ofuscación es incompatible con vmSelfDefending: true. Consulta las Notas de compatibilidad al final de esta receta.

Patrón 1: reservedNames con un marcador de identificador

Ideal para: inyectar expresiones JS sin procesar (arrays, objetos, números, …) sin preocuparte de escapar comillas.

Elige un identificador distintivo que nunca vaya a coincidir con código real, haz referencia a él directamente y añade una regex que lo capture en reservedNames. Con la ofuscación VM, el identificador pasa por el array de expresiones reservadas y aparece literalmente en la salida, por ejemplo:

JavaScript

Reemplaza el identificador por una expresión completa serializada en JSON. No pegues el valor entre comillas ni entre backticks: el identificador no está dentro de un literal de cadena, así que el motor JS analiza el valor inyectado como una expresión normal. Usa un serializador de confianza, escapa < cuando el script esté incrustado en HTML y prueba valores que contengan comillas, barras invertidas, saltos de línea, backticks y ${...}.

JavaScript

Patrón 2: reservedStrings con un marcador de plantilla literal

Ideal para: motores de plantillas al estilo de Go o Jinja que exigen delimitadores como {{ .Field }}, que resultan ser texto JS válido cuando se envuelven en backticks.

El marcador vive dentro de una plantilla literal de un solo quasi, sin interpolaciones. Con la ofuscación VM pasa por el array de expresiones reservadas, lo que conserva en la salida la forma original con backticks.

Código fuente

JavaScript

Opciones del ofuscador

Interfaz

Para reservar el marcador {{.Config.FeatureFlags}} del código anterior, añade esta regex al campo Reserved Strings, con barras invertidas simples. La interfaz guarda el valor literalmente, así que, a diferencia del código JS, las barras invertidas no se duplican:

Texto

Campo Reserved Strings en la interfaz del ofuscador con la regex introducida con barras invertidas simples

API

JavaScript

Tras la ofuscación

JavaScript

El marcador se conserva byte a byte, incluidos los backticks que lo rodean.

Sustitución en el servidor

Reemplaza {{.Config.FeatureFlags}} por una cadena JSON escapada para una plantilla literal. Los backticks no exigen escapar las " internas, pero una barra invertida, un backtick o ${ dentro del contenido seguirían modificando o cerrando el literal, así que escapa esos tres:

Código

En tiempo de ejecución: JSON.parse(`{"newCheckout":true,"darkMode":false}`), que funciona.

¿Por qué preferir plantillas literales a comillas simples o dobles para valores de cadena? Los backticks no exigen escapar " dentro del contenido JSON. Esto importa porque la mayoría de los valores renderizados en el servidor son JSON, y el JSON está lleno de comillas dobles. Con un marcador entre comillas dobles tendrías que escapar cada " interna (consulta el Patrón 3); con backticks solo hay que escapar las escasas barras invertidas, backticks y ${ del contenido.

Patrón 3: reservedStrings con un marcador de cadena entre comillas

Ideal para: código fuente que debe seguir siendo ES5 (sin plantillas literales), o casos en los que la API que lo rodea espera un literal de cadena normal.

El marcador es un literal de cadena entre comillas simples o dobles. Con la ofuscación VM pasa por el array de cadenas reservadas, que se emite como un array JS serializado con JSON.stringify: siempre entre comillas dobles, sea cual sea el estilo de comillas de la entrada.

Código fuente

JavaScript

Opciones del ofuscador

Interfaz

Para reservar el marcador {{.Page.Tags}} del código anterior, añade esta regex al campo Reserved Strings, con barras invertidas simples. La interfaz guarda el valor literalmente, así que, a diferencia del código JS, las barras invertidas no se duplican:

Texto

Campo Reserved Strings en la interfaz del ofuscador con la regex introducida con barras invertidas simples

API

JavaScript

Tras la ofuscación

JavaScript

Sustitución en el servidor

Como el marcador está dentro de una cadena JS entre comillas dobles, el contenido JSON inyectado debe tener escapadas sus barras invertidas y sus caracteres " internos:

Código

Salida resultante:

JavaScript

En tiempo de ejecución: JSON.parse("[\"news\",\"tech\",\"release\"]") → ["news","tech","release"].

Si olvidas el escapado, el navegador verá comillas desequilibradas y lanzará un SyntaxError. El Patrón 2 evita por completo las comillas dobles al usar backticks.

Varios marcadores en un mismo programa

Los tres patrones se pueden combinar. Una sola regex de reservedStrings con una alternancia puede capturar todas las formas de marcador que emita tu motor de plantillas; aquí, los estilos {{ .Field }} y %{ .Field }:

JavaScript

Puedes mezclar marcadores de identificador (para valores JS sin procesar) y marcadores de cadena (para JSON renderizado) en el mismo código fuente: elige para cada marcador según lo que el servidor vaya a inyectar realmente.

Notas de compatibilidad

vmSelfDefending rompe la sustitución posterior a la ofuscación

VM Self Defending detecta cualquier cambio en la salida ofuscada después de generarla, incluida una sustitución legítima de plantillas, y el código protegido se niega entonces a ejecutarse.

Si dependes de la sustitución de plantillas en el servidor, establece vmSelfDefending: false. Deja también selfDefending desactivada: no tiene ningún efecto con la ofuscación VM, y sin VM también impide cualquier cambio en la salida.

Consejos sobre los marcadores

  • No reutilices marcadores entre identificadores y cadenas. Un nombre reservado como __TOKEN__ y una cadena reservada que coincida con __TOKEN__ describen dos rutas de código distintas (array de expresiones reservadas frente a array de cadenas reservadas). Usa formas textuales distintas para cada uno: por ejemplo, una convención UPPER_SNAKE para los marcadores de identificador y una forma envuelta en delimitadores ({{ ... }}, %{...}, <<<...>>>) para los marcadores de cadena. Así, un error en una regex no puede coincidir silenciosamente con el otro.
  • Las regex de reservedStrings se aplican a los valores de cadena sin procesar. La regex se compara con el valor de la cadena en tiempo de ejecución, no con el texto fuente. \{\{[^}]+\}\} coincide con cadenas que contienen {{.something}} (o cualquier otra forma {{...}}). Si tu marcador puede ir envuelto en contenido adicional ("prefix-{{.Field}}-suffix"), la regex sigue coincidiendo, pero se conserva la cadena entera: planifica tu sustitución en consecuencia.
  • Funciona con cualquier motor de plantillas. Aunque los ejemplos usan sintaxis de Go, nada en la integración de javascript-obfuscator es específico de Go. Cualquier herramienta capaz de hacer un reemplazo a nivel de cadena sobre la salida del ofuscador servirá: Rails ERB, Django, las etiquetas cortas de PHP, sed en un pipeline de CI, etc. Elige delimitadores que tu motor emita de forma natural y que no choquen con la sintaxis real de JS.