Sustitución de plantillas del lado del host
Inyecta valores renderizados en el servidor (por ejemplo, expresiones `{{ .Field }}` de html/template 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
por petición: un endpoint de API, una lista de feature flags, un blob de estado inicial, un id de build, un nonce. Ofuscas el
JS con vmObfuscation: true, pero también necesitas que el motor de plantillas sustituya los marcadores de posición en la salida
ofuscada después de que termine la ofuscación. Si los marcadores de posición se absorben en el bytecode de la VM (el comportamiento por defecto para cadenas e
identificadores), el motor de plantillas no tiene nada que sustituir.
¿Qué opción elijo?
| El valor del servidor es… | Usa |
|---|---|
| Una expresión JS en bruto (literal de array, objeto, número, booleano, llamada a función) | reservedNames + marcador de posición de identificador |
| Una cadena, y controlas los delimitadores de la plantilla | reservedStrings + marcador de posición de literal de plantilla |
Una cadena, y el motor de plantillas fuerza ciertos delimitadores (por ejemplo, Go {{ .Field }}) | reservedStrings + marcador de posición de cadena o de literal de plantilla |
Patrón 1 - reservedNames con un marcador de posición de identificador
Mejor para: inyectar expresiones JS en bruto (arrays, objetos, números, …) sin ninguna preocupación por el escapado de comillas.
Elige un identificador distintivo que nunca colisione con código real, referéncialo directamente e incluye en reservedNames una regex que
coincida con él. Bajo la ofuscación VM, el identificador se enruta a través del array de expresiones reservadas y
aparece literalmente en la salida.
Código fuente
(function () {
var initialState = __INITIAL_STATE__;
bootstrap(initialState);
})();
Opciones del ofuscador
JavaScriptObfuscator.obfuscate(source, {
vmObfuscation: true,
reservedNames: ['^__INITIAL_STATE__$']
});
Después de la ofuscación
En algún punto de la salida encontrarás un array de expresiones reservadas similar a:
let _r = [__INITIAL_STATE__, /* …other entries… */];
El token __INITIAL_STATE__ sobrevive al renombrado de identificadores y no se absorbe en el bytecode de la VM.
Sustitución del lado del host (ejemplo con Go)
tpl := template.Must(template.New("obf.js").Parse(obfuscated))
tpl.Execute(w, map[string]any{
"InitialState": map[string]any{"user": "alice", "theme": "dark"},
})
…donde tu plantilla de Go reemplaza el token por una expresión JS en bruto:
{{ `__INITIAL_STATE__` }} → {{ .InitialState | toJSON }}
Resultado:
let _r = [{"user":"alice","theme":"dark"}, /* … */];
Sin comillas, sin escapado: el valor inyectado lo parsea el motor JS como una expresión ordinaria.
Patrón 2 - reservedStrings con un marcador de posición de literal de plantilla
Mejor para: motores de plantillas al estilo Go / Jinja que requieren delimitadores como {{ .Field }} que resultan ser texto JS
válido cuando se envuelven en backticks.
El marcador de posición vive dentro de un literal de plantilla de un solo cuasi, sin interpolación. Bajo la ofuscación VM esto se enruta a través del array de expresiones reservadas, preservando la forma en bruto con backticks en la salida.
Código fuente
(function () {
var featureFlags = JSON.parse(`{{.Config.FeatureFlags}}`);
applyFlags(featureFlags);
})();
Opciones del ofuscador
UI
Para reservar el marcador de posición {{.Config.FeatureFlags}} del código fuente anterior, añade esta regex al campo Reserved Strings,
con barras invertidas simples. La interfaz almacena el valor literalmente, así que, a diferencia del código JS, las barras invertidas no se
duplican:
\{\{[^}]+\}\}

API
JavaScriptObfuscator.obfuscate(source, {
vmObfuscation: true,
reservedStrings: ['\\{\\{[^}]+\\}\\}']
});
Después de la ofuscación
let _r = [`{{.Config.FeatureFlags}}`, /* … */];
El marcador de posición se preserva byte a byte, incluidos los backticks que lo rodean.
Sustitución del lado del host
Reemplaza {{.Config.FeatureFlags}} por una cadena JSON válida: quedará dentro de backticks en tiempo de ejecución, que no
requieren escapar las comillas internas:
flagsJSON, _ := json.Marshal(config.FeatureFlags) // e.g. {"newCheckout":true,"darkMode":false}
out := strings.ReplaceAll(obfuscated, "{{.Config.FeatureFlags}}", string(flagsJSON))
En tiempo de ejecución: JSON.parse(`{"newCheckout":true,"darkMode":false}`) - funciona.
Patrón 3 - reservedStrings con un marcador de posición de cadena entrecomillada
Mejor para: código fuente que debe permanecer en ES5 (sin literales de plantilla), o casos en los que la API circundante espera un literal de cadena normal.
El marcador de posición es un literal de cadena entrecomillado con comillas simples o dobles. Bajo la ofuscación VM se enruta a través del
array de cadenas reservadas, que se emite como un array JS serializado con JSON.stringify: siempre con comillas dobles, independientemente
del estilo de comillas de la entrada.
Código fuente
(function () {
var tags = JSON.parse("{{.Page.Tags}}");
renderTags(tags);
})();
Opciones del ofuscador
UI
Para reservar el marcador de posición {{.Page.Tags}} del código fuente anterior, añade esta regex al campo Reserved Strings,
con barras invertidas simples. La interfaz almacena el valor literalmente, así que, a diferencia del código JS, las barras invertidas no se duplican:
\{\{[^}]+\}\}

API
JavaScriptObfuscator.obfuscate(source, {
vmObfuscation: true,
reservedStrings: ['\\{\\{[^}]+\\}\\}']
});
Después de la ofuscación
var _rs = ["{{.Page.Tags}}", /* … */];
Sustitución del lado del host
Como el marcador de posición está dentro de una cadena JS entrecomillada con comillas dobles, el payload JSON inyectado debe tener sus "
internas escapadas con \":
raw, _ := json.Marshal(page.Tags) // e.g. ["news","tech","release"]
// Escape " for embedding inside a JS double-quoted string.
escaped := strings.ReplaceAll(string(raw), `"`, `\"`)
out := strings.ReplaceAll(obfuscated, "{{.Page.Tags}}", escaped)
Salida resultante:
var _rs = ["[\"news\",\"tech\",\"release\"]", /* … */];
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 esto
por completo usando backticks.
Múltiples marcadores de posición en un mismo programa
Los tres patrones se componen entre sí. Una única regex de reservedStrings con una alternancia puede coincidir con cada forma de marcador de posición que tu
motor de plantillas emita:
JavaScriptObfuscator.obfuscate(source, {
vmObfuscation: true,
reservedNames: ['^__INITIAL_STATE__$'],
reservedStrings: ['\\{\\{[^}]+\\}\\}']
});
Puedes mezclar marcadores de posición de identificador (para valores JS en bruto) y marcadores de posición de cadena (para JSON renderizado) en el mismo código fuente: elige por marcador de posición según lo que el servidor vaya a inyectar realmente.
Notas de compatibilidad
- No reutilices marcadores de posición entre identificadores y cadenas. Un nombre reservado como
__TOKEN__y una cadena reservada que coincida con__TOKEN__describen dos rutas de código diferentes (el array de expresiones reservadas frente al array de cadenas reservadas). Usa formas textuales distintas para cada uno; por ejemplo, una convenciónUPPER_SNAKEpara los marcadores de posición de identificador y una forma envuelta en delimitadores ({{ ... }},%{...},<<<...>>>) para los marcadores de posición de cadena. Así un fallo en una regex no podrá coincidir silenciosamente con la otra. - Las regex de
reservedStringsse ejecutan contra los valores de cadena en bruto. La regex se compara con el valor en tiempo de ejecución de la cadena, no con el texto fuente.\{\{[^}]+\}\}coincide con cadenas que contienen{{.something}}(o cualquier otra forma{{...}}). Si tu marcador de posición pudiera estar envuelto en contenido adicional ("prefix-{{.Field}}-suffix"), la regex sigue coincidiendo pero se preserva la cadena completa: planifica tu sustitución en consecuencia. - Funciona con cualquier motor de plantillas. Aunque los ejemplos usan la sintaxis
text/templatede Go, nada en la integración de javascript-obfuscator es específico de Go. Cualquier cosa que pueda hacer un reemplazo a nivel de cadena sobre la salida del ofuscador funcionará: Rails ERB, Django, short-tags de PHP, sed en un pipeline de CI, etc. Elige delimitadores que tu motor emita de forma natural y que no colisionen con la sintaxis JS real.
