Podstawianie szablonów po stronie serwera
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:
\{\{[^}]+\}\}

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.stringify — zawsze 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:
\{\{[^}]+\}\}

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_SNAKEdla 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
reservedStringsdział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.
