Подстановка значений в шаблон на стороне сервера
Как подставлять значения, отрисованные на сервере (например, плейсхолдеры в стиле шаблонов Go `{{ .Field }}`), в JavaScript с VM-обфускацией, не ломая процесс обфускации.
Проблема
Ваш бэкенд (Go, Rails, Django, PHP, …) отдаёт JavaScript-файл, часть содержимого которого нужно формировать
для каждого запроса: эндпоинт API, список фича-флагов, начальное состояние, идентификатор сборки, nonce. Вы обфусцируете
JS с vmObfuscation: true, но шаблонизатор должен подставить значения в плейсхолдеры обфусцированного
результата после завершения обфускации. reservedNames и reservedStrings оставляют плейсхолдер видимым в результате,
чтобы его можно было заменить. Без них строковый плейсхолдер попадает в байт-код VM, а плейсхолдер-идентификатор
выводится в нескольких переписанных местах, так что шаблонизатор либо не находит, что заменять, либо ломает результат.
Возможно, лучше загружать значения как данные
По возможности вообще не редактируйте защищённый результат: загружайте конфигурацию времени выполнения как данные до запуска
защищённого бандла. Держите её в отдельном скрипте или получайте с эндпоинта, требующего аутентификации. Так защищённый результат остаётся нетронутым, и
vmSelfDefending можно не выключать. Данные, переданные в браузер, в любом случае видны этому браузеру.
Используйте описанные ниже паттерны, только когда значения действительно нужно подставлять в обфусцированный файл.
О примерах на Go: они выполняют простую замену строк (strings.ReplaceAll / strings.NewReplacer) в
результате обфускации и сами занимаются экранированием, показанным для каждого паттерна. Они не пропускаются через
пакет Go html/template - он добавил бы сверху собственное контекстное экранирование JavaScript, и полезная нагрузка
была бы экранирована дважды. Если вы всё же рендерите через html/template, уберите ручное экранирование и позвольте
движку экранировать один раз.
Какую опцию выбрать?
| Значение с сервера… | Что использовать |
|---|---|
| Готовое JS-значение (массив, объект, число, логическое значение - любое значение JSON) | Паттерн 1 - reservedNames + плейсхолдер-идентификатор |
| Строка (типичный случай - отрисованный JSON, идентификатор сборки, nonce) | Паттерн 2 - reservedStrings + плейсхолдер в шаблонном литерале |
| Строка, когда код должен оставаться на ES5 или окружающий API требует строкового литерала в кавычках | Паттерн 3 - reservedStrings + плейсхолдер в строке в кавычках |
Подстановка после обфускации несовместима с vmSelfDefending: true. См.
Замечания о совместимости в конце этого рецепта.
Паттерн 1 - reservedNames с плейсхолдером-идентификатором
Лучше всего подходит для: подстановки готовых JS-выражений (массивов, объектов, чисел, …) без каких-либо забот об экранировании кавычек.
Выберите характерный идентификатор, который никогда не пересечётся с реальным кодом, обращайтесь к нему напрямую и добавьте
соответствующее ему регулярное выражение в reservedNames. При VM-обфускации идентификатор проходит через массив зарезервированных выражений и
попадает в результат дословно, например:
Замените идентификатор полным выражением, сериализованным в JSON. Не вставляйте значение в кавычки или обратные кавычки:
идентификатор находится не внутри строкового литерала, поэтому JS-движок разбирает подставленное значение как обычное
выражение. Используйте надёжный сериализатор, экранируйте <, если скрипт встраивается в HTML, и проверьте значения, содержащие
кавычки, обратные слеши, переводы строк, обратные кавычки и ${...}.
Паттерн 2 - reservedStrings с плейсхолдером в шаблонном литерале
Лучше всего подходит для: шаблонизаторов в стиле Go / Jinja, требующих разделителей вроде {{ .Field }}, которые оказываются допустимым JS-текстом,
если заключить их в обратные кавычки.
Плейсхолдер находится внутри шаблонного литерала из одной части, без интерполяции. При VM-обфускации такой литерал проходит через массив зарезервированных выражений, и в результате сохраняется его исходная форма с обратными кавычками.
Исходный код
Опции обфускатора
Интерфейс
Чтобы зарезервировать плейсхолдер {{.Config.FeatureFlags}} из исходного кода выше, добавьте это регулярное выражение в поле Reserved Strings
с одинарными обратными слешами. Интерфейс сохраняет значение как есть, поэтому, в отличие от JS-кода, обратные слеши не
удваиваются:

API
После обфускации
Плейсхолдер сохраняется байт в байт, вместе с окружающими обратными кавычками.
Подстановка на стороне сервера
Замените {{.Config.FeatureFlags}} на JSON-строку, экранированную для шаблонного литерала. Внутри обратных кавычек не нужно экранировать
", но обратный слеш, обратная кавычка или ${ в данных всё равно изменят или завершат литерал, поэтому экранируйте
эти три последовательности:
Во время выполнения: JSON.parse(`{"newCheckout":true,"darkMode":false}`) - работает.
Почему для строковых значений лучше шаблонные литералы, а не одинарные или двойные кавычки? В обратных кавычках не нужно экранировать "
внутри JSON-данных. Это важно, потому что большинство значений, отрисованных на сервере, - это JSON, а в JSON полно двойных
кавычек. С плейсхолдером в двойных кавычках пришлось бы экранировать каждую внутреннюю " (см. паттерн 3); с обратными кавычками
экранировать нужно лишь редкие обратные слеши, обратные кавычки и ${.
Паттерн 3 - reservedStrings с плейсхолдером в строке в кавычках
Лучше всего подходит для: исходного кода, который должен оставаться на ES5 (без шаблонных литералов), или случаев, когда окружающий API ожидает обычный строковый литерал.
Плейсхолдер - это строковый литерал в одинарных или двойных кавычках. При VM-обфускации он проходит через
массив зарезервированных строк, который выводится как JS-массив, сериализованный через JSON.stringify, - всегда в двойных кавычках, независимо
от стиля кавычек во входном коде.
Исходный код
Опции обфускатора
Интерфейс
Чтобы зарезервировать плейсхолдер {{.Page.Tags}} из исходного кода выше, добавьте это регулярное выражение в поле Reserved Strings
с одинарными обратными слешами. Интерфейс сохраняет значение как есть, поэтому, в отличие от JS-кода, обратные слеши не удваиваются:

API
После обфускации
Подстановка на стороне сервера
Поскольку плейсхолдер находится внутри JS-строки в двойных кавычках, в подставляемых JSON-данных нужно экранировать обратные слеши и
внутренние символы ":
Итоговый результат:
Во время выполнения: JSON.parse("[\"news\",\"tech\",\"release\"]") → ["news","tech","release"].
Если забыть об экранировании, браузер увидит несбалансированные кавычки и выбросит SyntaxError. Паттерн 2 полностью обходит
проблему двойных кавычек за счёт обратных кавычек.
Несколько плейсхолдеров в одной программе
Все три паттерна можно сочетать. Одно регулярное выражение reservedStrings с альтернацией может соответствовать всем формам плейсхолдеров, которые выводит
ваш шаблонизатор, - здесь это оба стиля, {{ .Field }} и %{ .Field }:
В одном исходном файле можно сочетать плейсхолдеры-идентификаторы (для готовых JS-значений) и строковые плейсхолдеры (для отрисованного JSON): выбирайте вид для каждого плейсхолдера в зависимости от того, что сервер будет подставлять на самом деле.
Замечания о совместимости
vmSelfDefending ломает подстановку после обфускации
VM Self Defending обнаруживает любое изменение обфусцированного результата после сборки, включая легитимную подстановку в шаблон, и тогда защищённый код отказывается работать.
Если вы используете подстановку в шаблон на стороне сервера, задайте vmSelfDefending: false. Оставьте выключенным и selfDefending: при
VM-обфускации он ни на что не влияет, а без VM тоже запрещает любые изменения результата.
Советы по плейсхолдерам
- Не используйте один и тот же плейсхолдер и для идентификаторов, и для строк. Зарезервированное имя вроде
__TOKEN__и зарезервированная строка, соответствующая__TOKEN__, проходят двумя разными путями (массив зарезервированных выражений и массив зарезервированных строк). Используйте для них разные текстовые формы: например, соглашениеUPPER_SNAKEдля плейсхолдеров-идентификаторов и форму с разделителями ({{ ... }},%{...},<<<...>>>) для строковых плейсхолдеров. Тогда ошибка в одном регулярном выражении не сможет незаметно захватить плейсхолдеры другого вида. - Регулярные выражения
reservedStringsприменяются к самим значениям строк. Регулярное выражение сопоставляется со значением строки во время выполнения, а не с исходным текстом.\{\{[^}]+\}\}соответствует строкам, которые содержат{{.something}}(или любую другую форму{{...}}). Если плейсхолдер может быть окружён дополнительным текстом ("prefix-{{.Field}}-suffix"), регулярное выражение всё равно сработает, но сохранена будет вся строка - учитывайте это при подстановке. - Работает с любым шаблонизатором. Хотя в примерах используется синтаксис Go, в интеграции с javascript-obfuscator нет ничего специфичного для Go. Подойдёт всё, что умеет выполнять строковую замену в результате обфускатора: Rails ERB, Django, короткие теги PHP, sed в CI-конвейере и т. д. Выбирайте разделители, которые ваш шаблонизатор выводит естественным образом и которые не конфликтуют с настоящим синтаксисом JS.
