ホスト側でのテンプレート置換
サーバーでレンダリングした値(Go テンプレート風の `{{ .Field }}` プレースホルダーなど)を、難読化の処理を壊すことなく VM 難読化された JavaScript に埋め込みます。
問題
バックエンド(Go、Rails、Django、PHP など)が配信する JavaScript ファイルで、API エンドポイント、機能フラグの一覧、初期状態のデータ、ビルド ID、nonce などの内容の一部をリクエストごとにレンダリングする必要があるとします。JS は vmObfuscation: true で難読化しますが、テンプレートエンジンには難読化が終わった後で、難読化済みの出力にあるプレースホルダーを置換させる必要もあります。reservedNames と reservedStrings を使うと、プレースホルダーが出力に見える形で残り、置換できるようになります。これらがないと、文字列のプレースホルダーは VM のバイトコードに取り込まれ、識別子のプレースホルダーは書き換えられた複数の箇所に出力されるため、テンプレートエンジンは置換する対象を見つけられないか、出力を壊してしまいます。
値をデータとして読み込むことを検討する
可能であれば、保護された出力をそもそも編集しないようにしてください。保護されたバンドルが実行される前に、実行時の設定をデータとして読み込みます。設定は別のスクリプトに置くか、認証付きのエンドポイントから取得します。こうすれば保護された出力はそのまま保たれるため、vmSelfDefending を有効のままにできます。ブラウザーに届けられたデータは、どちらの方法でもそのブラウザーからは見えます。
値をどうしても難読化済みのファイルに置換する必要がある場合は、以下のパターンを使用してください。
Go のサンプルについての注意:サンプルは難読化済みの出力に対して単純な文字列置換(strings.ReplaceAll / strings.NewReplacer)を行い、エスケープはパターンごとに示すとおり自前で処理します。Go の html/template パッケージは通していません。通すと、その上に独自の文脈に応じた JavaScript エスケープが適用され、ペイロードが二重にエスケープされてしまいます。html/template でレンダリングする場合は、手動のエスケープを省き、エンジンに一度だけエスケープさせてください。
どのオプションを選ぶべきか
| サーバーの値の種類 | 使用するもの |
|---|---|
| 生の JS 値(配列、オブジェクト、数値、真偽値など、任意の JSON 値) | パターン 1:reservedNames + 識別子のプレースホルダー |
| 文字列(一般的なケース:レンダリングした JSON、ビルド ID、nonce) | パターン 2:reservedStrings + テンプレートリテラルのプレースホルダー |
| 文字列で、コードを ES5 のままにする必要がある、または周囲の API が引用符付きの文字列リテラルを必要とする | パターン 3:reservedStrings + 引用符付き文字列のプレースホルダー |
難読化後の置換は vmSelfDefending: true と併用できません。このレシピの最後にある互換性に関する注意を参照してください。
パターン 1:reservedNames と識別子のプレースホルダー
適した用途:引用符のエスケープを一切気にせずに、生の JS 式(配列、オブジェクト、数値など)を埋め込む場合。
実際のコードと決して衝突しない特徴的な識別子を選んで直接参照し、それに一致する正規表現を reservedNames に登録します。VM 難読化では、この識別子は予約済み式の配列を経由し、出力にそのままの形で現れます。例:
識別子を、JSON にシリアライズした完全な式で置き換えます。値を引用符やバッククォートの中に貼り付けないでください。識別子は文字列リテラルの中にないため、埋め込んだ値は JS エンジンによって通常の式としてパースされます。信頼できるシリアライザーを使い、スクリプトを HTML に埋め込む場合は < をエスケープし、引用符、バックスラッシュ、改行、バッククォート、${...} を含む値でテストしてください。
パターン 2:reservedStrings とテンプレートリテラルのプレースホルダー
適した用途:{{ .Field }} のような区切り文字を必要とする Go や Jinja 形式のテンプレートエンジン。これらの区切り文字は、バッククォートで囲むとたまたま有効な JS テキストになります。
プレースホルダーは、quasi が 1 つだけで補間を含まないテンプレートリテラルの中に置きます。VM 難読化では、これは予約済み式の配列を経由し、出力でもバッククォートを含む元の形が保たれます。
ソース
難読化ツールのオプション
UI
上のソースの {{.Config.FeatureFlags}} プレースホルダーを予約するには、Reserved Strings フィールドにこの正規表現を、バックスラッシュを 1 つずつにして追加します。UI は値をそのまま保存するため、JS コードの場合とは異なり、バックスラッシュを二重にしません。

API
難読化後
プレースホルダーは、前後のバッククォートも含めてバイト単位でそのまま保たれます。
ホスト側での置換
{{.Config.FeatureFlags}} を、テンプレートリテラル用にエスケープした JSON 文字列で置き換えます。バッククォートの中では " をエスケープする必要はありませんが、ペイロード内のバックスラッシュ、バッククォート、${ はリテラルの内容を変えたり終わらせたりしてしまうため、この 3 つはエスケープしてください。
実行時には JSON.parse(`{"newCheckout":true,"darkMode":false}`) となり、正しく動作します。
文字列値にシングルクォートやダブルクォートではなくテンプレートリテラルを使うのはなぜですか? バッククォートなら、JSON ペイロード内の " をエスケープする必要がありません。サーバーでレンダリングする値の多くは JSON で、JSON にはダブルクォートが大量に含まれるため、これは重要です。ダブルクォートのプレースホルダーでは内側の " をすべてエスケープする必要があります(パターン 3 を参照)。バッククォートなら、まれに現れるバックスラッシュ、バッククォート、${ だけをエスケープすれば済みます。
パターン 3:reservedStrings と引用符付き文字列のプレースホルダー
適した用途:ES5 のまま(テンプレートリテラルなし)にしておく必要があるソースコードや、周囲の API が通常の文字列リテラルを想定している場合。
プレースホルダーは、シングルクォートまたはダブルクォートで囲んだ文字列リテラルです。VM 難読化では、これは予約済み文字列の配列を経由します。この配列は JSON.stringify でシリアライズされた JS 配列として出力されるため、入力時のクォートの種類に関係なく常にダブルクォートになります。
ソース
難読化ツールのオプション
UI
上のソースの {{.Page.Tags}} プレースホルダーを予約するには、Reserved Strings フィールドにこの正規表現を、バックスラッシュを 1 つずつにして追加します。UI は値をそのまま保存するため、JS コードの場合とは異なり、バックスラッシュを二重にしません。

API
難読化後
ホスト側での置換
プレースホルダーはダブルクォートで囲まれた JS 文字列の中にあるため、埋め込む JSON ペイロードのバックスラッシュと内側の " をエスケープする必要があります。
出力結果:
実行時には JSON.parse("[\"news\",\"tech\",\"release\"]") → ["news","tech","release"] となります。
エスケープを忘れると、ブラウザーには引用符の対応が崩れたコードが渡され、SyntaxError がスローされます。パターン 2 はバッククォートを使うことで、ダブルクォートの問題そのものを回避しています。
1 つのプログラムに複数のプレースホルダーを置く
3 つのパターンはすべて組み合わせられます。選択(|)を含む 1 つの reservedStrings 正規表現で、テンプレートエンジンが出力するあらゆる形のプレースホルダーに一致させることができます。ここでは {{ .Field }} と %{ .Field } の両方の形式です。
同じソースの中で、識別子のプレースホルダー(生の JS 値用)と文字列のプレースホルダー(レンダリングした JSON 用)を混在させることもできます。サーバーが実際に埋め込む内容に応じて、プレースホルダーごとに選んでください。
互換性に関する注意
vmSelfDefending は難読化後の置換を壊します
VM Self Defending は、ビルド後の難読化済み出力に対するあらゆる変更を検知します。正当なテンプレートの置換も例外ではなく、その場合、保護されたコードは実行を拒否します。
ホスト側でのテンプレート置換を使う場合は、vmSelfDefending: false を設定してください。selfDefending もオフのままにしてください。VM 難読化では効果がありませんが、VM を使わない場合も出力へのあらゆる変更を禁止します。
プレースホルダーのヒント
- 識別子と文字列でプレースホルダーを使い回さないでください。
__TOKEN__のような予約名と、__TOKEN__に一致する予約文字列は、2 つの異なるコードパス(予約済み式の配列と予約済み文字列の配列)を表します。それぞれに異なる文字列の形を使ってください。たとえば、識別子のプレースホルダーにはUPPER_SNAKEの規約を、文字列のプレースホルダーには区切り文字で囲んだ形({{ ... }}、%{...}、<<<...>>>)を使います。そうすれば、一方の正規表現の不具合が、気づかないうちにもう一方に一致することはありません。 reservedStringsの正規表現は生の文字列値に対して照合されます。 正規表現はソースのテキストではなく、文字列の実行時の値に対して照合されます。\{\{[^}]+\}\}は{{.something}}(またはその他の{{...}}形式)を含む文字列に一致します。プレースホルダーが余分な内容で囲まれている可能性がある場合("prefix-{{.Field}}-suffix")、正規表現は一致しますが文字列全体が保持されるため、それを踏まえて置換を計画してください。- どのテンプレートエンジンでも動作します。 例では Go の構文を使っていますが、javascript-obfuscator との連携に Go 固有の部分はありません。難読化ツールの出力に文字列レベルの置換を行えるものなら何でも動作します。Rails ERB、Django、PHP の短縮タグ、CI パイプラインでの sed などです。エンジンが自然に出力し、実際の JS 構文と衝突しない区切り文字を選んでください。
