Dokumentacja
/
Przepisy
/

Podstawianie szablonów po stronie serwera

Podstawianie szablonów po stronie serwera

v6.10.0+

Wstrzykiwanie wartości renderowanych po stronie serwera (np. wyrażeń `{{ .Field }}` z Go html/template) do JavaScriptu zobfuskowanego przez VM bez psucia procesu obfuskacji.

Problem

Backend (Go, Rails, Django, PHP, …) serwuje plik JavaScript, którego zawartość musi być częściowo renderowana przy każdym żądaniu — endpoint API, lista flag funkcji, blob ze stanem początkowym, identyfikator builda, nonce. Kod JS jest obfuskowany z vmObfuscation: true, ale silnik szablonów musi podstawić placeholdery w zobfuskowanym wyniku po zakończeniu obfuskacji. Jeśli placeholdery zostaną wchłonięte do kodu bajtowego VM (co jest domyślne dla łańcuchów znaków i identyfikatorów), silnik szablonów nie ma czego podstawić.

Którą opcję wybrać?

Wartość z serwera to…Zastosuj
Surowe wyrażenie JS (literał tablicy, obiekt, liczba, wartość logiczna, wywołanie funkcji)reservedNames + placeholder w postaci identyfikatora
Łańcuch znaków, a ograniczniki szablonu są pod Twoją kontroląreservedStrings + placeholder w postaci literału szablonowego
Łańcuch znaków, a silnik szablonów wymusza określone ograniczniki (np. Go {{ .Field }})reservedStrings + placeholder w postaci łańcucha lub literału szablonowego

Wzorzec 1 — reservedNames z placeholderem w postaci identyfikatora

Najlepszy do: wstrzykiwania surowych wyrażeń JS (tablic, obiektów, liczb, …) bez żadnych problemów z escapowaniem cudzysłowów.

Należy wybrać charakterystyczny identyfikator, który nigdy nie skoliduje z prawdziwym kodem, odwołać się do niego bezpośrednio i dodać pasujące do niego wyrażenie regularne do reservedNames. Przy obfuskacji VM identyfikator jest kierowany przez tablicę zarezerwowanych wyrażeń i pojawia się w wyniku dosłownie.

Kod źródłowy

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

Opcje obfuskatora

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

Po obfuskacji

Gdzieś w wyniku znajdzie się tablica zarezerwowanych wyrażeń podobna do:

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

Token __INITIAL_STATE__ przetrwa zmianę nazw identyfikatorów i nie zostaje wchłonięty do kodu bajtowego VM.

Podstawianie po stronie serwera (przykład w Go)

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

…gdzie szablon Go zastępuje token surowym wyrażeniem JS:

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

Wynik:

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

Bez cudzysłowów, bez escapowania — wstrzyknięta wartość jest parsowana przez silnik JS jako zwykłe wyrażenie.

Wzorzec 2 — reservedStrings z placeholderem w postaci literału szablonowego

Najlepszy do: silników szablonów w stylu Go / Jinja, które wymagają ograniczników takich jak {{ .Field }} — te akurat są poprawnym tekstem JS, gdy opakuje się je w backticki.

Placeholder znajduje się wewnątrz literału szablonowego z jednym quasi, bez interpolacji. Przy obfuskacji VM trafia on do tablicy zarezerwowanych wyrażeń, co zachowuje w wyniku surową postać z backtickami.

Kod źródłowy

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

Opcje obfuskatora

UI

Aby zarezerwować placeholder {{.Config.FeatureFlags}} z powyższego kodu źródłowego, należy dodać to wyrażenie regularne do pola Reserved Strings — z pojedynczymi ukośnikami wstecznymi. UI zapisuje wartość dosłownie, więc w odróżnieniu od kodu JS ukośniki nie są podwajane:

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

Pole Reserved Strings w UI obfuskatora z wyrażeniem regularnym wpisanym z pojedynczymi ukośnikami wstecznymi

API

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

Po obfuskacji

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

Placeholder jest zachowany bajt w bajt, wraz z otaczającymi go backtickami.

Podstawianie po stronie serwera

Należy zastąpić {{.Config.FeatureFlags}} poprawnym łańcuchem JSON — w czasie wykonania znajdzie się on wewnątrz backticków, które nie wymagają escapowania wewnętrznych cudzysłowów:

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

W czasie wykonania: JSON.parse(`{"newCheckout":true,"darkMode":false}`) — działa.

Wzorzec 3 — reservedStrings z placeholderem w postaci łańcucha w cudzysłowach

Najlepszy do: kodu źródłowego, który musi pozostać w ES5 (bez literałów szablonowych), lub przypadków, w których otaczające API oczekuje zwykłego literału tekstowego.

Placeholder to literał tekstowy w apostrofach lub cudzysłowach. Przy obfuskacji VM trafia on do tablicy zarezerwowanych łańcuchów, która jest emitowana jako tablica JS zserializowana przez JSON.stringifyzawsze w podwójnych cudzysłowach, niezależnie od stylu cudzysłowów w kodzie wejściowym.

Kod źródłowy

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

Opcje obfuskatora

UI

Aby zarezerwować placeholder {{.Page.Tags}} z powyższego kodu źródłowego, należy dodać to wyrażenie regularne do pola Reserved Strings — z pojedynczymi ukośnikami wstecznymi. UI zapisuje wartość dosłownie, więc w odróżnieniu od kodu JS ukośniki nie są podwajane:

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

Pole Reserved Strings w UI obfuskatora z wyrażeniem regularnym wpisanym z pojedynczymi ukośnikami wstecznymi

API

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

Po obfuskacji

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

Podstawianie po stronie serwera

Ponieważ placeholder znajduje się wewnątrz łańcucha JS w podwójnych cudzysłowach, wstrzykiwany payload JSON musi mieć wewnętrzne znaki " poprzedzone escapem \":

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)

Wynikowy kod:

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

W czasie wykonania: JSON.parse("[\"news\",\"tech\",\"release\"]")["news","tech","release"].

W razie pominięcia escapowania przeglądarka zobaczy niezbilansowane cudzysłowy i zgłosi SyntaxError. Wzorzec 2 całkowicie omija ten problem dzięki backtickom.

Wiele placeholderów w jednym programie

Wszystkie trzy wzorce można ze sobą łączyć. Pojedyncze wyrażenie regularne reservedStrings z alternatywą może dopasować każdą postać placeholdera emitowaną przez silnik szablonów:

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

W tym samym kodzie źródłowym można mieszać placeholdery w postaci identyfikatorów (dla surowych wartości JS) i w postaci łańcuchów (dla renderowanego JSON) — wybór należy podejmować osobno dla każdego placeholdera, w zależności od tego, co faktycznie wstrzyknie serwer.

Uwagi dotyczące zgodności

  • Nie należy używać tych samych placeholderów dla identyfikatorów i łańcuchów. Zarezerwowana nazwa taka jak __TOKEN__ i zarezerwowany łańcuch pasujący do __TOKEN__ opisują dwie różne ścieżki w kodzie (tablica zarezerwowanych wyrażeń kontra tablica zarezerwowanych łańcuchów). Dla każdej z nich należy stosować odrębną postać tekstową — na przykład konwencję UPPER_SNAKE dla placeholderów w postaci identyfikatorów i postać otoczoną ogranicznikami ({{ ... }}, %{...}, <<<...>>>) dla placeholderów w postaci łańcuchów. Dzięki temu błąd w jednym wyrażeniu regularnym nie dopasuje się po cichu do drugiego.
  • Wyrażenia regularne reservedStrings działają na surowych wartościach łańcuchów. Wyrażenie regularne jest dopasowywane do wartości łańcucha w czasie wykonania, a nie do tekstu źródłowego. \{\{[^}]+\}\} dopasowuje łańcuchy, które zawierają {{.something}} (lub dowolną inną postać {{...}}). Jeśli placeholder może być otoczony dodatkową treścią ("prefix-{{.Field}}-suffix"), wyrażenie regularne nadal się dopasuje, ale zachowany zostanie cały łańcuch — podstawianie trzeba zaplanować z uwzględnieniem tego faktu.
  • Działa z dowolnym silnikiem szablonów. Choć przykłady używają składni Go text/template, nic w integracji z javascript-obfuscator nie jest specyficzne dla Go. Zadziała wszystko, co potrafi wykonać zamianę na poziomie tekstu w wyniku obfuskatora: Rails ERB, Django, krótkie tagi PHP, sed w pipelinie CI itd. Należy wybrać ograniczniki, które silnik emituje naturalnie i które nie kolidują z prawdziwą składnią JS.