構成ファイルの形式
JSDefender の構成ファイルは JSON の形式になっています。既定では、JSDefender は現在の作業ディレクトリから jsdefender.config.json
というファイルを検索します。
次の JSON コード スニペットは、構成ファイルの概要を示しています。
{
// 入力ファイルとその保護を指定します
"inputs": [],
// 入力ファイルの取得元となるソース ディレクトリを指定します
"sourceDir": "<mySourceDir>",
// 保護されたファイルの移動先となるフォルダー
"outDir": "<myOutput>",
// 割り当てファイルを指定するか、または割り当てファイルを無効にします
"mapFile": "<my-map.json> または 'off'",
// JSDefender Runtime をこのファイルに出力します
"runtimeFile": "<myRuntime>",
// JSDefender Runtime を差し込む方法を指定します
"runtimeInjection": "<injection mode>"
// JSDefender をメッセージ非表示モードで実行します。既定:無効
"quietMode": true,
// ライセンス キーを指定します。ライセンス キーは通常、環境変数または CLI 引数で指定することをお勧めします。
"license": "<license key>",
// 同じコマンド ライン オプションの設定
"esTarget": "<es5、es2015、など>",
"randomSeed": 12345,
"ignoreUnsafeConstructs": false,
"disableInline": false,
// グローバルに使用する保護オプション
"settings": {},
// 名前付き構成セット(説明は後出)
"namedSets": {}
}
inputs
:このセクションは、入力ソース ファイルの一覧を定義し、ファイルごとの保護を構成します。コマンド ライン引数に 1 つの入力ファイルと 1 つの出力ファイルだけを指定する場合を除き、このセクションには少なくとも 1 つのエントリを指定する必要があります。前者の場合は、inputs
を省略したり空の一覧を指定したりできます。sourceDir
:任意の入力ディレクトリ。指定した場合、入力ファイルは作業ディレクトリ直下にあるこのフォルダーからの相対パスにあるものと見なされます。このセクションを省略した場合は、入力ファイルは作業ディレクトリからの相対パスにあるものと見なされます。メモ:このフォルダー内の JavaScript ファイルが JSDefender によって収集されることはありません。それらのファイルはinputs
セクションに 1 つずつ指定する必要があります。outDir
:任意の出力ディレクトリ。作業ディレクトリからの相対位置になります。JSDefender は、保護されたファイルを元の名前でこのフォルダーに格納します。既定値:protected
。mapFile
:任意の割り当てファイル名。この値を指定することで、既定の割り当てファイル名(lexical-map.json
)が変更されます。"off"
を指定した場合は、割り当てファイルの生成が無効になります。runtimeFile
:任意のファイル名。JSDefender Runtime を別のファイルに入れたい場合にのみ、この値を設定します。runtimeInjection
:保護されたコードに JSDefender Runtime を差し込む方法を指定します。使用可能な値は次のとおりです。firstNonModule
:モジュール以外の最初のソース ファイルに Runtime を差し込みます(既定)。separateSource
:runtimeFile
オプションで指定されたファイルに Runtime を差し込みます。all
:Runtime を必要とするすべての保護されたファイルに差し込みます。
quietMode
、license
、estarget
、randomSeed
、ignoreUnsafeConstructs
、disableInline
:これらはすべて省略可能(任意)です。それぞれ類似のコマンド ライン オプションと同じ操作を意味しています。settings
:任意の保護構成。この構成はすべての入力ファイルに対して使用されますが、特定ファイルの特定構成があれば、それによって上書きされます。この値は、保護設定で説明されているいくつかのプロパティを持つオブジェクトです。namedSets
:名前付き構成セットの、任意のハッシュ オブジェクト。頻繁に使用される構成設定をグループ化するのに使用できます。詳細については、名前付き構成セット セクションを参照してください。
入力ファイル
構成ファイルの inputs
プロパティは、入力ファイルを配列として表します。この配列の各要素は、文字列か、詳細を指定するオブジェクトのいずれかになります。要素が文字列の場合は、値には sourceDir
内の場所を指定します。単純な文字列の代わりに、次のプロパティを持つ記述子オブジェクトを使用することもできます。
in
:必須。単純な文字列を使用する場合と同様に、入力ファイルの名前を指定します。out
:任意。outDir
内の既定の場所と名前の代替となる、入力の名前またはパスを指定します。protection
:任意。名前付きセットの文字列キーを使用できるようにします。このプロパティを設定すると、settings
プロパティ内の保護オプションの代わりに、名前付きセット内の保護オプションが使用されます。isModule
:任意のブール値。対応する入力ファイルをモジュールとして読み込む(たとえば、<script src="..." type="module"></script>
タグを使用する)場合は、true に設定します。
例:
// 単純なシナリオ
{
"inputs": [ "code1.js", "code2.js" ]
}
// vendor1.js ファイルを保護しません
{
"inputs": [
"code1.js",
"code2.js",
{
"in": "vendor1.js",
"protection": "off"
},
"vendor2.js"
]
}
// vendor ファイルを出力のサブフォルダーに移動します
{
"inputs": [
"code.js",
{
"in": "vendor1.js",
"out": "/vendors/vendor1.js"
},
{
"in": "vendor2.js",
"out": "/vendors/vendor2.js"
},
]
}
保護設定
構成ファイルの settings
プロパティが保持するオブジェクトでは、各プロパティは保護の変換を指名し、プロパティの値はその変換のパラメーターを指定します。次の JSON コード スニペットは、settings
のスキーマを示しています。
{
"boleanLiterals": "<value>",
"constantArgument": "<value>",
"controlFlow": "<value>",
"dateLock": "<value>",
"debuggerRemoval": "<value>",
"domainLock": "<value>",
"exprSequence": "<value>",
"functionReorder": "<value>",
"integerLiterals": "<value>",
"localDeclarations": "<value>",
"propertyIndirection": "<value>",
"propertySparsing": "<value>",
"selfDefending": "<value>",
"stringLiterals": "<value>",
"variableGrouping": "<value>"
}
ここで、"<value>"
はプロパティ値のプレースホルダーに過ぎず、実際にはプロパティごとに異なる値が入ります。すべてのプロパティは任意です。構成から除外することもできます。プロパティを除外すると、保護エンジンはプロパティ値を別の構成エンティティから継承します。
<value>
を null
または false
に設定すると、関連する保護が無効になります。<value>
に true
を指定すると、その保護が既定の設定で有効になります。ただし、既定値がない domainLock
と dateLock
は除きます。
プロパティ constantArgument
、debuggerRemoval
、exprSequence
、propertyIndirection
、propertySparsing
、stringLiterals
、variableGrouping
、variableMasking
には、それぞれブール値のみを設定できます。したがって、これらのプロパティは、有効にするか無効にするかを選択できる以外のオプションはありません。
以下に、他のプロパティの構成の詳細を示します。
booleanLiterals
このプロパティの値には、ブール値か、またはこの変換のランダム化を有効または無効(既定)にする randomize
プロパティを持つオブジェクトを指定できます。いくつかの例を見てみましょう。
// この変換を無効にします
{
"booleanLiterals": false
}
// この変換をランダム化なしで有効にします
{
"booleanLiterals": true
}
// この変換をランダム化なしで有効にします
{
"booleanLiterals": {}
}
// この変換をランダム化ありで有効にします
{
"booleanLiterals": {
"randomize": true
}
}
// この変換をランダム化なしで有効にします
{
"booleanLiterals": {
"randomize": false
}
}
controlFlow
このプロパティの値には、ブール値(関連する変換を有効または無効にする)か、または以下の任意のプロパティを持つオブジェクトを指定できます。
randomize
:制御フローの難読化に使用される値のランダム化を有効または無効(既定)にするブール値を指定できます。injectFakeCode
:制御フローを実装する有限オートマトンへのフェイク コードの分岐を有効または無効(既定)にするブール値を指定できます。
例:
// この変換を無効にします
{
"controlFlow": false
}
// この変換を有効にします(No. 1)
{
"controlFlow": true
}
// この変換を有効にします(No. 2)
{
"controlFlow": {}
}
// ランダム化を有効にします
{
"controlFlow": {
"randomize": true
}
}
// ランダム化とフェイク コードの差し込みを有効にします
{
"controlFlow": {
"randomize": true,
"injectFakeCode": true
}
}
dateLock
この保護オプションは、値を false
に設定することで無効にはできますが、true
に設定しても、適用する既定値がないため有効にはできません。以下を記述すると、エラーが発生します。
{
"dateLock": true
}
dateLock は、3 つの任意のプロパティを持つ構成オブジェクトを受け付けます。
startDate
:ロック期間の開始日(当日を含む)。指定しない場合は、endDate
以前のすべての日付が受け付けられます。endDate
:ロック期間の終了日(当日を含まない)。指定しない場合は、startDate
以後のすべての日付が受け付けられます。errorScript
:任意の文字列。このプロパティには、有効な JavaScript コード スニペットを指定できます。このスクリプトは、コードが指定された日付範囲外で実行されたことが保護によって検出されたときに実行されます。詳細については、エラー スクリプト セクションを参照してください。
どのプロパティも省略可能ですが、少なくとも 1 つは指定する必要があります。どちらの日付プロパティも、ISO-8601 の日付書式(YYYY-MM-DDTHH:MM:SSZ
。T
は日付と時刻の区切り文字)を使用する文字列を必要とします。プロパティ値の時刻部分は省略可能です。
例:
// 2020 年 4 月 30 日 11:00 AM UTC からコードをロックします
{
"dateLock": {
"endDate": "2020-04-30T11:00"
}
}
// 2020 年 5 月 15 日 3 時 PM UTC からこのコードの実行を許可します
{
"dateLock": {
"startDate": "2020-05-15T15:00"
}
}
// 2020 年 4 月 1 日にのみ、このコードの実行を許可します
{
"dateLock": {
"startDate": "2020-04-1",
"endDate": "2020-04-02"
}
}
// 2020 年にのみ、このコードの実行を許可します。その期間以外の日付に
// 実行した場合には、警告ダイアログを表示します
{
"dateLock": {
"startDate": "2020/01/01",
"endDate": "2021-01-01",
"errorScript": "alert('It is not 2020 anymore')"
}
}
domainLock
この保護オプションは、値を false
に設定することで無効にはできますが、true
に設定しても、適用する既定値がないため有効にはできません。以下を記述すると、エラーが発生します。
{
"domainLock": true
}
このプロパティは、文字列を受け付けるか、以下のプロパティを持つ構成オブジェクトを受け付けます。
domainPattern
:必須の文字列。このプロパティに完全なドメイン名(例:www.mydomain.net
)または部分的なドメイン名(例:.mydomain.net
)を指定することで、mydomain.net
の任意のサブドメインでコードを実行できます。errorScript
:任意の文字列。このプロパティには、有効な JavaScript コード スニペットを指定できます。このスクリプトは、コードが無効なドメインで実行されたことが保護によって検出されたときに実行されます。詳細については、エラー スクリプト セクションを参照してください。
.mydomain.net
の場合は、www.mydomain.net
と一致します。しかし、ハッカーが www.mydomain.net.example.com
などのドメインを登録して、この保護を破ることはできません。この名前では指定されたパターンと一致しないためです。パターンに mydomain.net
を指定すると、mydomain.net
にあるコードが読み込まれますが、そのサブドメインにあるコードは読み込まれません。
文字列値を使用した場合は、domainPattern
プロパティと同じ意味になります。複数のドメインにコードをバインドできます。複数のドメイン パターンはセミコロンで区切って指定します。
例:
// preemptive.com で実行されるコード(No. 1)
{
"domainLock": {
"domainPattern": "preemptive.com"
}
}
// preemptive.com で実行されるコード(No. 2)
{
"domainLock": "preemptive.com"
}
// mycompany.com と myorg.org の任意のサブドメインで実行されるコード(No. 1)
{
"domainLock": {
"domainPattern": "mycompany.com;.myorg.org"
}
}
// mycompany.com と myorg.org の任意のサブドメインで実行されるコード(No. 2)
{
"domainLock": "domainPattern": "mycompany.com;.myorg.org"
}
// Acme Company の Web サイトで実行され、
// このサイト以外の場所で実行されるとアラートを表示するコード
{
{
"domainLock": {
"domainPattern": ".acmecompany.com",
"errorScript": "alert('Invalid domain')"
}
}
functionReorder
このプロパティの値には、ブール値か、またはこの変換のランダム化を有効または無効(既定)にする randomize
プロパティを持つオブジェクトを指定できます。いくつかの例を見てみましょう。
// この変換を無効にします
{
"functionReorder": false
}
// この変換を有効にします(No. 1)
{
"functionReorder": true
}
// この変換を有効にします(No. 2)
{
"functionReorder": {}
}
// ランダム化を有効にします
{
"functionReorder": {
"randomize": true
}
}
// ランダム化を無効にします
{
"functionReorder": {
"randomize": false
}
}
integerLiterals
このプロパティの値には、変換を有効または無効にするブール値か、または以下の設定を持つオブジェクトを指定できます。
randomize
:整数リテラルの置き換えの制御に使用される値のランダム化を有効または無効(既定)にする、任意のブール値を指定できます。radix
:整数リテラルの置き換え時に使用する基数を指定する、任意の文字列を指定できます。指定できる値は"binary"
、"decimal"
、"hexadecimal"
、"octal"
です。lower
:任意の値。難読化対象となる最小の整数リテラル(0 ~ 65535。既定値:0)を指定できます。upper
:任意の値。難読化対象となる最大の整数リテラル(0 ~ 65535。既定値:8)を指定できます。
いくつかの例を見てみましょう。
// 整数リテラルをランダム化なしで置き換えます
{
"integerLiterals": true
}
// 整数リテラルをランダム化ありで置き換えます
{
"integerLiterals": {
"randomize": true
}
}
// 整数リテラルを 8 進法形式に置き換えます
{
"integerLiterals": {
"radix": "octal"
}
}
localDeclarations
このプロパティでは、変換を有効または無効にするブール値か、または以下の設定を持つオブジェクトを受け付けます。
nameMangling
:変数の名前変更時に使用する変換を記述する任意の文字列を指定できます。使用可能な値は"sequential"
、"hexadecimal"
、"base52"
、"base62"
です。既定値は"base52"
です。excludeIds
:任意の識別子文字列の一覧。指定した名前を持つ最上位の宣言は、名前変更されません。
例:
// ローカル宣言を有効にします
{
"localDeclarations": true
}
// 名前改変 "base62" を使用します
{
"localDeclarations": {
"nameMangling": "base62"
}
}
// 名前改変 "sequential" を使用する際に
// 最上位の識別子 "myFunc" および "acmeVar" を保持しておきます
{
"localDeclarations": {
"nameMangling": "sequential",
"excludeIds": [ "myFunc", "acmeVar" ]
}
}
処理対象から除外する識別子が 1 つである場合は、配列の区切り文字を省略できます。
{
"localDeclarations": {
"nameMangling": "base62",
"excludeIds": "getName"
}
}
excludeIds
オプションでは、名前変更対象からファイル レベルのすべての宣言の識別子を除外するショートカットとして、"*" 文字列を使用できます。
{
"localDeclarations": {
"nameMangling": "base62",
"excludeIds": "*"
}
}
より柔軟性を与えるために、文字列ではなく正規表現を使用することができます。識別子パターンが "r:" で始まっている場合、JSDefender はそれを、変数名と照合する正規表現であると解釈します。たとえば、次の構成は、識別子に her
が含まれるすべての宣言の名前変更を許可しません。
{
"localDeclarations": {
"nameMangling": "base62",
"excludeIds": "r:her"
}
}
次の例では、Func
で終わる識別子が除外されます。
{
"localDeclarations": {
"nameMangling": "base62",
"excludeIds": "r:Func$"
}
}
シナリオによっては、特定の最上位の識別子の名前を変更すると、実行されるコードが失敗する可能性があるため、名前を保持しておく必要があります。この目的のために、関数識別子 getName
を保持したいとします。保持する ID の名前を一覧表示するには、localDeclarations
設定の excludeIds
プロパティを使用します。
jsdefender.config.json
:
{
"inputs": ["file1.js", "file2.js"],
"settings": {
"localDeclarations": {
"nameMangling": "sequential",
"excludeIds": ["getName"]
},
"stringLiterals": false
}
}
保護された出力に反映されているとおり、JSDefender は getName
を保持しています。
file1.js
:
function _0x000000() {
var _0x000001 = getName(); // 保持される ID
return "Hello, " + _0x000001 + ", from multifile demo!";
}
file2.js
:
function getName() {
// 保持される ID
return "Developer";
}
var _0x000003 = document.getElementById("msg");
_0x000003.textContent = _0x000000();
lexical-map.json
ファイルが既定で作成されます。この機能の詳細については、語彙割り当てセクションを参照してください。
selfDefending
この保護オプションは、ブール値、0 ~ 4 の整数、または構成オブジェクトを受け付けます。値 false
および 0
が保護を無効にするのに対し、true
(1 と等価)と 0 以外の整数は保護を有効にします。
次のプロパティを持つ構成オブジェクトを使用することもできます。
level
:0 ~ 4 の整数。errorScript
:任意の文字列。このプロパティには、有効な JavaScript コード スニペットを指定できます。このスクリプトは、コードが改ざんされたことが保護によって検出されたときに実行されます。詳細については、エラー スクリプト セクションを参照してください。
1
を指定すると単一のラッパーが使用され、2
、3
、4
を指定すると、マトリョーシカ人形のようにコードの周囲にラッパーが追加されます。
例:
// 自己防御型の保護を有効にします(No. 1)
{
"selfDefending": true
}
// 自己防御型の保護を有効にします(No. 2)
{
"selfDefending": 1
}
// 自己防御型の保護を有効にします(No. 3)
{
"selfDefending": {
"level": 1
}
}
// 3 重の自己防御型の保護を有効にします
{
"selfDefending": 3
}
// ... または
{
"selfDefending": {
"level": 3
}
}
// 2 重の保護(エラー メッセージあり)
{
"selfDefending": {
"level": 3,
"errorScript": "alert('Code tampered!!!')"
}
}
この特別な保護には、保護されたコードが元のコードより何倍も長くなるという代償が伴います。入れ子になったコードの整合性がチェックされるため、関数実行のオーバーヘッドがかなり増える可能性があります。
JSDefender は、一度きりの実行用として認識された即時関数にのみこの保護を適用します。他の関数(または関数のような構成体)がこの保護オプションを使用できるようにするには、コードに明示的なインラインの保護ディレクティブを追加します。例を挙げると、まず、構成ファイルで selfDefending
を true
に設定していても、次の最上位関数は保護されません。
function add(a, b) {
return a + b;
}
この関数を保護するには、明示的に保護を有効にします。
function add(a, b) {
"@jsdefender { selfDefending: true }";
return a + b;
}
名前付き構成セット
保護の変換の一連のオプションを定義できる場所は、構成の settings
プロパティだけではありません。部分的な保護を実行するには、インライン ディレクティブを使って、ソース コード内の複数の場所でそれらのオプションを宣言します。コードでは、適切に構成された少数の構成オプション セットのみを使用するのが普通です。コードの至るところで同じオプション セットを何度も使用するのは、非常に非効率的でエラーが発生しやすくなります。
このため、JSDefender では、名前付き構成セットを利用できるようにしています。オプションの namedSets
プロパティはハッシュ オブジェクトです。このオブジェクト内の各プロパティ名は一意の構成キーです。プロパティ値は、settings
と同じ構造を持つオブジェクトであり、キーに関連付けられている一連の保護の変換を宣言します。例を見てみましょう。
{
"namedSets": {
// パフォーマンスへの影響を最小限に抑えます
"light": {
"booleanLiterals": true
},
// パフォーマンスへの影響は軽微です
"medium": {
"booleanLiterals": {
"randomize": true
},
"integerLiterals": {
"radix": "hexadecimal"
},
"stringLiterals": true
},
// より強力な保護。ただし、パフォーマンスに影響します
"heavy": {
"booleanLiterals": {
"randomize": true
},
"integerLiterals": {
"radix": "hexadecimal"
},
"stringLiterals": true,
"propertyIndicrection": true,
"localDeclarations": {
"nameMangling": "sequential"
},
"controlFlow": true
}
}
}
この構成スニペットでは 3 つの名前付きセット、light
、medium
、heavy
を指定しています。これらのキーの値を何度も使用する代わりに、プロパティ名を使用するだけでよいのです。いくつかの例を見てみましょう。
inputs
セクションに、特定のファイルに適用する保護設定を定義できます。
{
"inputs": [
"functions.js",
{
"in": "code.js",
"protection": "medium"
},
{
"in": "myIP.js",
"protection": "heavy"
}
]
}
この入力宣言は、code.js
ファイルで medium
設定を使用する一方で、myIP.js
で heavy
オプションを使用するように指定しています。
myIP.js
内のあるコード セクションは、真の知的財産を含んでいないため、medium
設定と併用できるとします。コードでは、インラインの保護ディレクティブを使用して、レベルを設定することができます。
function mySimpleFunctionWithNoRealIP() {
"@jsdefender medium"; // ここで "medium" オプションを設定します
// ...
}
後で、medium
では 8 進数の整数リテラルを使用すると決めた場合、この変更は、構成プロパティ namedSets
内の単一の場所で実行できます。
{
"namedSets": {
// ...
"medium": {
"booleanLiterals": {
"randomize": true
},
"integerLiterals": {
"radix": "octal" // この行を変更しました
},
"stringLiterals": true
},
// ...
}
}
特別な名前付きセット
JSDefender には、構成ファイル内とインラインの保護ディレクティブ内の両方で、名前付きセットのキーが必要とされるあらゆる場所に適用できるいくつかの特別な名前付きセットが定義されています。
off
:保護を無効にします。disable-inline
:インラインの保護ディレクティブを無視します。default
:JSDefender の既定の構成オプションを使用します。
構成のマージ
JSDefender は実行される際、コードの特定部分に適用する保護構成設定を決定するために、次の手順を使用します。
- JSDefender が構成ファイルを読み取る:
- コマンド ライン オプションで明示的な構成ファイルが指定された場合、JSDefender はそのファイルを読み取ります。
- 明示的に指定されておらず、現在の作業ディレクトリに
jsdefender.config.json
ファイルが存在する場合は、JSDefender はそのファイルを取り込みます。 - 上記以外の場合、JSDefender は既定の設定を使用します。
- JSDefender は、上記のステップで読み取られた構成オプションを、コマンド ライン オプションに指定された構成オプションで上書きします。このため、コマンド ライン引数は構成ファイルの設定よりも優先されます。
- 入力ファイルごとに最上位の構成設定を選択する:
- 入力ファイルの設定で
protection
プロパティが使用されている場合、JSDefender はそのプロパティに指定された名前付きセットを使用します。 - そのプロパティが使用されていない場合は、ステップ 1 と 2 で計算された構成の
settings
部分の保護を使用します。
- 入力ファイルの設定で
- 特定の入力ファイルに最上位のインライン保護構成を適用する:
protection
に対してdisable-inline
を指定していない限り、最上位のインラインの保護ディレクティブは、直前のステップで計算された最上位のファイル保護設定とマージされます。インラインの保護ディレクティブは、最上位のファイル設定よりも優先されます。 - 入れ子になったインライン保護構成を適用する:入れ子になったスコープ(例:関数内のスコープ)のインライン保護構成ディレクティブは、その親スコープとマージされます。入れ子になったスコープが優先されます。