Documentation
/
Recettes
/

Substitution de gabarits côté serveur

Substitution de gabarits côté serveur

v6.10.0+

Injectez des valeurs rendues côté serveur (par exemple des expressions Go html/template `{{ .Field }}`) dans du JavaScript obfusqué par VM sans casser la chaîne d'obfuscation.

Le problème

Votre backend (Go, Rails, Django, PHP, …) sert un fichier JavaScript dont le contenu doit être partiellement rendu à chaque requête - un point de terminaison d'API, une liste de feature flags, un état initial, un identifiant de build, un nonce. Vous obfusquez le JS avec vmObfuscation: true, mais vous avez aussi besoin que le moteur de gabarits substitue des marqueurs dans la sortie obfusquée après la fin de l'obfuscation. Si ces marqueurs sont absorbés dans le bytecode VM (le comportement par défaut pour les chaînes et les identifiants), le moteur de gabarits n'a plus rien à substituer.

Quelle option choisir ?

La valeur serveur est…Utilisez
Une expression JS brute (littéral de tableau, objet, nombre, booléen, appel de fonction)reservedNames + marqueur sous forme d'identifiant
Une chaîne, et vous maîtrisez les délimiteurs de gabaritreservedStrings + marqueur dans un littéral de gabarit
Une chaîne, et le moteur de gabarits impose certains délimiteurs (par exemple Go {{ .Field }})reservedStrings + marqueur sous forme de chaîne ou de littéral de gabarit

Motif 1 - reservedNames avec un marqueur sous forme d'identifiant

Idéal pour : injecter des expressions JS brutes (tableaux, objets, nombres, …) sans le moindre souci d'échappement de guillemets.

Choisissez un identifiant distinctif qui n'entrera jamais en collision avec du code réel, référencez-le directement, et déclarez dans reservedNames une expression régulière qui le reconnaît. Sous obfuscation VM, l'identifiant est routé via le tableau des expressions réservées et apparaît tel quel dans la sortie.

Source

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

Options de l'obfuscateur

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

Après obfuscation

Vous trouverez quelque part dans la sortie un tableau d'expressions réservées de ce genre :

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

Le jeton __INITIAL_STATE__ survit au renommage des identifiants et n'est pas absorbé dans le bytecode VM.

Substitution côté serveur (exemple en Go)

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

…où votre gabarit Go remplace le jeton par une expression JS brute :

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

Résultat :

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

Aucun guillemet, aucun échappement - la valeur injectée est analysée par le moteur JS comme une expression ordinaire.

Motif 2 - reservedStrings avec un marqueur dans un littéral de gabarit

Idéal pour : les moteurs de gabarits de type Go / Jinja qui imposent des délimiteurs comme {{ .Field }}, lesquels se trouvent être du texte JS valide dès lors qu'ils sont entourés d'accents graves.

Le marqueur est placé dans un littéral de gabarit à quasi unique, sans interpolation. Sous obfuscation VM, il est routé via le tableau des expressions réservées, ce qui préserve dans la sortie la forme brute entourée d'accents graves.

Source

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

Options de l'obfuscateur

Interface

Pour réserver le marqueur {{.Config.FeatureFlags}} du source ci-dessus, saisissez cette expression régulière dans le champ Reserved Strings

  • avec des barres obliques inverses simples. L'interface enregistre la valeur telle quelle : contrairement à du code JS, les barres obliques inverses ne sont donc pas doublées :
\{\{[^}]+\}\}

Champ Reserved Strings dans l'interface de l'obfuscateur, avec l'expression régulière saisie à l'aide de barres obliques inverses simples

API

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

Après obfuscation

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

Le marqueur est préservé octet pour octet, accents graves environnants compris.

Substitution côté serveur

Remplacez {{.Config.FeatureFlags}} par une chaîne JSON valide - elle se retrouvera entre accents graves à l'exécution, ce qui ne nécessite aucun échappement des guillemets internes :

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

À l'exécution : JSON.parse(`{"newCheckout":true,"darkMode":false}`) - cela fonctionne.

Motif 3 - reservedStrings avec un marqueur sous forme de chaîne entre guillemets

Idéal pour : le code source qui doit rester en ES5 (pas de littéraux de gabarit), ou les cas où l'API environnante attend un littéral de chaîne classique.

Le marqueur est un littéral de chaîne entre guillemets simples ou doubles. Sous obfuscation VM, il est routé via le tableau des chaînes réservées, lequel est émis sous forme de tableau JS sérialisé par JSON.stringify - toujours entre guillemets doubles, quel que soit le style de guillemets en entrée.

Source

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

Options de l'obfuscateur

Interface

Pour réserver le marqueur {{.Page.Tags}} du source ci-dessus, saisissez cette expression régulière dans le champ Reserved Strings - avec des barres obliques inverses simples. L'interface enregistre la valeur telle quelle : contrairement à du code JS, les barres obliques inverses ne sont donc pas doublées :

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

Champ Reserved Strings dans l'interface de l'obfuscateur, avec l'expression régulière saisie à l'aide de barres obliques inverses simples

API

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

Après obfuscation

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

Substitution côté serveur

Comme le marqueur se trouve à l'intérieur d'une chaîne JS entre guillemets doubles, les caractères " internes de la charge utile JSON injectée doivent être échappés en \" :

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)

Sortie obtenue :

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

À l'exécution : JSON.parse("[\"news\",\"tech\",\"release\"]")["news","tech","release"].

Si vous oubliez l'échappement, le navigateur verra des guillemets non appariés et lèvera une SyntaxError. Le motif 2 contourne complètement ce problème en recourant aux accents graves.

Plusieurs marqueurs dans un même programme

Les trois motifs se combinent. Une seule expression régulière reservedStrings avec une alternance peut reconnaître toutes les formes de marqueurs qu'émet votre moteur de gabarits :

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

Vous pouvez mélanger, dans un même source, marqueurs sous forme d'identifiants (pour les valeurs JS brutes) et marqueurs sous forme de chaînes (pour du JSON rendu) - choisissez au cas par cas selon ce que le serveur injectera réellement.

Notes de compatibilité

  • Ne réutilisez pas les mêmes marqueurs pour les identifiants et les chaînes. Un nom réservé comme __TOKEN__ et une chaîne réservée reconnaissant __TOKEN__ décrivent deux chemins de code différents (tableau des expressions réservées vs tableau des chaînes réservées). Utilisez des formes textuelles distinctes pour chacun - par exemple une convention UPPER_SNAKE pour les marqueurs de type identifiant et une forme encadrée par des délimiteurs ({{ ... }}, %{...}, <<<...>>>) pour les marqueurs de type chaîne. Ainsi, un bug dans l'une des expressions régulières ne pourra pas silencieusement reconnaître l'autre.
  • Les expressions régulières de reservedStrings s'appliquent aux valeurs brutes des chaînes. L'expression régulière est confrontée à la valeur de la chaîne à l'exécution, et non au texte source. \{\{[^}]+\}\} reconnaît les chaînes qui contiennent {{.something}} (ou toute autre forme {{...}}). Si votre marqueur peut être entouré de contenu supplémentaire ("prefix-{{.Field}}-suffix"), l'expression régulière correspond toujours mais la chaîne entière est préservée - prévoyez votre substitution en conséquence.
  • Compatible avec n'importe quel moteur de gabarits. Bien que les exemples utilisent la syntaxe Go text/template, rien dans l'intégration de javascript-obfuscator n'est spécifique à Go. Tout ce qui sait effectuer un remplacement textuel sur la sortie de l'obfuscateur convient : Rails ERB, Django, les balises courtes PHP, sed dans une chaîne d'intégration continue, etc. Choisissez des délimiteurs que votre moteur émet naturellement et qui n'entrent pas en collision avec la syntaxe JS réelle.