JSDefender handles scenarios when you use multiple JavaScript files in an application.
If you use multiple files, often, you cannot protect them separately, as there might be a correlation among them. JSDefender applies its protection on multiple files correctly when you specify their load order properly.
Specifying multiple files
To protect multiple files, you need to specify them in the inputs
section of the configuration file, indicating the first file that is going to be loaded in the web page. So, let's assume you work with the two files used in the previous example:
file1.js
:
function getGreeting() {
var name = getName();
return "Hello, " + name + ", from multifile demo!";
}
file2.js
:
function getName() {
return "Developer";
}
var element = document.getElementById("msg");
element.textContent = getGreeting();
Now, use this configuration file (it assumes that file1.js
and file2.js
are in the current working folder, just as jsdefender.config.json
):
jsdefender.config.json
:
{
"inputs": ["file1.js", "file2.js"],
"settings": {
"localDeclarations": {
"nameMangling": "sequential"
},
"stringLiterals": false
}
}
After running the jsdefender
command line, these are the protected output files:
file1.js
:
function _0x000000() {
var _0x000001 = _0x000002(); // ID declared in file2.js
return "Hello, " + _0x000001 + ", from multifile demo!";
}
file2.js
:
function _0x000002() {
// file1.js has a reference to this ID
return "Developer";
}
var _0x000003 = document.getElementById("msg");
_0x000003.textContent = _0x000000();
Look at the _0x000002
identifier, the renamed version of getName
in the original code. It was correctly replaced in both protected files.
Why the Order of Files May Matter
Expanding on what you learned at the beginning of this document, if you inject JSDefender runtime only into a single file, you need to make sure that you provide to JSDefender the first file to be loaded on the web page in the input list. The order of the subsequent inputs does not matter afterward.
When the runtimeInjection
mode is in default (firstNonModule
), JSDefender is injected in the first input file that is not marked with the isModule
flag.
If the runtimeInjection
mode is set to separateSource
, JSDefender runtime is injected into a separate source file, which should be loaded first on the web page.
But when runtimeInjection
mode is set to all
, JSDefender runtime is injected into all the inputs. This means that all the files will use their own runtime, in which case the inputs' order is not important at all.
Issues with Multiple Files Loaded in a Single Page
Let's look at a short example where the separate protection of two files loaded in the same web page may break working code.
Local declaration renaming (name mangling) is an excellent protection option, as it changes meaningful identifiers to a meaningless string of letters and digits. However, when you have multiple files, using JSDefender separately on those files may break the code, as the following example demonstrates.
Assume, you have two files, file1.js
, and file2.js
. The browser loads these files into an HTML page in this order:
...
<script src="file1.js"></script>
<script src="file2.js"></script>
...
file1.js
:
function getGreeting() {
return "Hello from multifile demo!";
}
file2.js
:
var element = document.getElementById("msg");
element.textContent = getGreeting();
You can protect these two files by running the JSDefender CLI twice, once for each file. As a result, you get these files after local declaration renaming:
file1.js
(protected):
function _0x000000() {
return "Hello from multifile demo!";
}
file2.js
(protected):
var _0x000000 = document.getElementById("msg");
_0x000000.textContent = getGreeting();
There are two issues:
- Each module contains a declaration,
_0x000000
. Because of the JavaScript declaration hoisting mechanism, the second_0x000000
declaration (infile2.js
) overwrites the first one. - As
getGreeting
is not a local declaration infile2.js
(it is just an identifier reference), it is not renamed.
This code will break when the browser loads the page.
Take a look at the next scenario that again uses two JavaScript files:
file1.js
:
function getGreeting() {
var name = getName();
return "Hello, " + name + ", from multifile demo!";
}
file2.js
:
function getName() {
return "Developer";
}
var element = document.getElementById("msg");
element.textContent = getGreeting();
After invoking JSDefender for each file separately, this is what you get:
file1.js
(protected):
function _0x000000() {
var _0x000001 = getName();
return "Hello, " + _0x000001 + ", from multifile demo!";
}
file2.js
(protected):
function _0x000000_() {
return "Developer";
}
var _0x000001 = document.getElementById("msg");
_0x000001.textContent = getGreeting();
You can immediately see that this code breaks, too:
- Each file contains a declaration,
_0x000000
. Because of JavaScript declaration hoisting, the declaration infile2.js
overwrites the first one. - As
getName
is not a local declaration infile1.js
(it is just an identifier reference), it is not renamed. - As
getGreeting
is not a local declaration infile2.js
(it is just an identifier reference), it is not renamed.
So, please make sure that you take care of file order when using JSDefender with multiple files.