Documentação
/
Receitas
/

Substituição de templates no servidor

Substituição de templates no servidor

v6.10.0+

Injete valores renderizados pelo servidor (por exemplo, expressões `{{ .Field }}` do html/template do Go) em JavaScript ofuscado por VM sem quebrar o pipeline de ofuscação.

O problema

Seu backend (Go, Rails, Django, PHP, …) serve um arquivo JavaScript cujo conteúdo precisa ser parcialmente renderizado a cada requisição - um endpoint de API, uma lista de feature flags, um blob de estado inicial, um id de build, um nonce. Você ofusca o JS com vmObfuscation: true, mas também precisa que o motor de templates substitua os placeholders na saída ofuscada depois que a ofuscação termina. Se os placeholders forem absorvidos pelo bytecode da VM (o padrão para strings e identificadores), o motor de templates não tem nada para substituir.

Qual opção devo escolher?

O valor do servidor é…Use
Uma expressão JS bruta (literal de array, objeto, número, booleano, chamada de função)reservedNames + placeholder de identificador
Uma string, e você controla os delimitadores do templatereservedStrings + placeholder em template literal
Uma string, e o motor de templates impõe certos delimitadores (por exemplo, {{ .Field }} do Go)reservedStrings + placeholder em string ou em template literal

Padrão 1 - reservedNames com um placeholder de identificador

Ideal para: injetar expressões JS brutas (arrays, objetos, números, …) sem nenhuma preocupação com escape de aspas.

Escolha um identificador distinto que nunca vá colidir com o código real, referencie-o diretamente e adicione em reservedNames uma regex que corresponda a ele. Sob ofuscação VM, o identificador é roteado pelo array de expressões reservadas e aparece literalmente na saída.

Código-fonte

(function () {
    var initialState = __INITIAL_STATE__;
    bootstrap(initialState);
})();

Opções do obfuscator

JavaScriptObfuscator.obfuscate(source, {
    vmObfuscation: true,
    reservedNames: ['^__INITIAL_STATE__$']
});

Depois da ofuscação

Em algum lugar da saída você encontrará um array de expressões reservadas parecido com este:

let _r = [__INITIAL_STATE__, /* …other entries… */];

O token __INITIAL_STATE__ sobrevive à renomeação de identificadores e não é absorvido pelo bytecode da VM.

Substituição no servidor (exemplo em Go)

tpl := template.Must(template.New("obf.js").Parse(obfuscated))
tpl.Execute(w, map[string]any{
    "InitialState": map[string]any{"user": "alice", "theme": "dark"},
})

…onde o seu template Go substitui o token por uma expressão JS bruta:

{{ `__INITIAL_STATE__` }} → {{ .InitialState | toJSON }}

Resultado:

let _r = [{"user":"alice","theme":"dark"}, /* … */];

Sem aspas, sem escape - o valor injetado é interpretado pelo motor JS como uma expressão comum.

Padrão 2 - reservedStrings com um placeholder em template literal

Ideal para: motores de templates no estilo Go / Jinja que exigem delimitadores como {{ .Field }}, que por acaso são texto JS válido quando envolvidos em crases.

O placeholder fica dentro de um template literal com um único quasi e sem interpolação. Sob ofuscação VM, isso é roteado pelo array de expressões reservadas, preservando na saída a forma bruta com crases.

Código-fonte

(function () {
    var featureFlags = JSON.parse(`{{.Config.FeatureFlags}}`);
    applyFlags(featureFlags);
})();

Opções do obfuscator

UI

Para reservar o placeholder {{.Config.FeatureFlags}} do código-fonte acima, adicione esta regex ao campo Reserved Strings - com barras invertidas simples. A UI armazena o valor literalmente, então, ao contrário do que acontece em código JS, as barras invertidas não são duplicadas:

\{\{[^}]+\}\}

Campo Reserved Strings na UI do obfuscator com a regex inserida usando barras invertidas simples

API

JavaScriptObfuscator.obfuscate(source, {
    vmObfuscation: true,
    reservedStrings: ['\\{\\{[^}]+\\}\\}']
});

Depois da ofuscação

let _r = [`{{.Config.FeatureFlags}}`, /* … */];

O placeholder é preservado byte a byte, incluindo as crases ao redor.

Substituição no servidor

Substitua {{.Config.FeatureFlags}} por uma string JSON válida - ela ficará entre crases em tempo de execução, o que não exige escape das aspas internas:

flagsJSON, _ := json.Marshal(config.FeatureFlags) // e.g. {"newCheckout":true,"darkMode":false}
out := strings.ReplaceAll(obfuscated, "{{.Config.FeatureFlags}}", string(flagsJSON))

Em tempo de execução: JSON.parse(`{"newCheckout":true,"darkMode":false}`) - funciona.

Padrão 3 - reservedStrings com um placeholder em string entre aspas

Ideal para: código-fonte que precisa permanecer em ES5 (sem template literals) ou casos em que a API ao redor espera um literal de string comum.

O placeholder é um literal de string entre aspas simples ou duplas. Sob ofuscação VM, ele é roteado pelo array de strings reservadas, que é emitido como um array JS serializado com JSON.stringify - sempre entre aspas duplas, independentemente do estilo de aspas da entrada.

Código-fonte

(function () {
    var tags = JSON.parse("{{.Page.Tags}}");
    renderTags(tags);
})();

Opções do obfuscator

UI

Para reservar o placeholder {{.Page.Tags}} do código-fonte acima, adicione esta regex ao campo Reserved Strings - com barras invertidas simples. A UI armazena o valor literalmente, então, ao contrário do que acontece em código JS, as barras invertidas não são duplicadas:

\{\{[^}]+\}\}

Campo Reserved Strings na UI do obfuscator com a regex inserida usando barras invertidas simples

API

JavaScriptObfuscator.obfuscate(source, {
    vmObfuscation: true,
    reservedStrings: ['\\{\\{[^}]+\\}\\}']
});

Depois da ofuscação

var _rs = ["{{.Page.Tags}}", /* … */];

Substituição no servidor

Como o placeholder fica dentro de uma string JS entre aspas duplas, o payload JSON injetado precisa ter os caracteres " internos escapados com \":

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)

Saída resultante:

var _rs = ["[\"news\",\"tech\",\"release\"]", /* … */];

Em tempo de execução: JSON.parse("[\"news\",\"tech\",\"release\"]")["news","tech","release"].

Se você esquecer o escape, o navegador verá aspas desbalanceadas e lançará um SyntaxError. O Padrão 2 evita isso completamente ao usar crases.

Vários placeholders em um mesmo programa

Os três padrões podem ser combinados. Uma única regex de reservedStrings com alternância pode corresponder a todos os formatos de placeholder que o seu motor de templates emite:

JavaScriptObfuscator.obfuscate(source, {
    vmObfuscation: true,
    reservedNames: ['^__INITIAL_STATE__$'],
    reservedStrings: ['\\{\\{[^}]+\\}\\}']
});

Você pode misturar placeholders de identificador (para valores JS brutos) e placeholders de string (para JSON renderizado) no mesmo código-fonte - escolha caso a caso, com base no que o servidor realmente vai injetar.

Notas de compatibilidade

  • Não reutilize placeholders entre identificadores e strings. Um nome reservado como __TOKEN__ e uma string reservada que corresponda a __TOKEN__ descrevem dois caminhos de código diferentes (array de expressões reservadas vs. array de strings reservadas). Use formatos textuais distintos para cada caso - por exemplo, uma convenção UPPER_SNAKE para placeholders de identificador e um formato envolvido por delimitadores ({{ ... }}, %{...}, <<<...>>>) para placeholders de string. Assim, um bug em uma das regex não pode corresponder silenciosamente à outra.
  • As regex de reservedStrings são aplicadas aos valores brutos das strings. A regex é comparada com o valor da string em tempo de execução, não com o texto do código-fonte. \{\{[^}]+\}\} corresponde a strings que contêm {{.something}} (ou qualquer outro formato {{...}}). Se o seu placeholder puder estar envolvido por conteúdo extra ("prefix-{{.Field}}-suffix"), a regex ainda corresponde, mas a string inteira é preservada - planeje a sua substituição levando isso em conta.
  • Funciona com qualquer motor de templates. Embora os exemplos usem a sintaxe do text/template do Go, nada na integração com o javascript-obfuscator é específico de Go. Qualquer coisa capaz de fazer uma substituição em nível de texto na saída do obfuscator funciona: Rails ERB, Django, short-tags do PHP, sed em um pipeline de CI, etc. Escolha delimitadores que o seu motor emita naturalmente e que não colidam com a sintaxe JS real.