Summary
JSDefender version 2 supports more advanced JavaScript code protection scenarios compared to version 1. In addition, there are some breaking changes, that version 1 users should be aware of. Here is a summary of the most important changes and new features:
- The new name of the command line interface (CLI) is
jsdefender
instead ofpjsd
. - You can protect multiple source files in a single session, so you do not need to execute the CLI for each file.
- Version 2 contains new protection techniques, such as:
- Self-defending: Wraps protected functions into code-tampering guard functions. If a hacker modifies the code (for example, tries to beautify it, removes or adds anything), it will no longer run correctly.
- Expression sequence obfuscation: Adjacent assignment expressions are translated into expression sequences.
- Property sparsing transform: Single object literal assignments are transformed into multiple ones to make code reading more difficult.
- Variable grouping transform: Separates variable declarations and initializations. Moves declarations at the end of their hosting lexical scopes.
- Version 2 recognizes bundles and modules. JSDefender uses this information to carry out its transformations in more advanced and sophisticated ways.
Changes in the CLI
The most crucial change in the CLI is its name: instead of pjsd
, you should now use jsdefender
to invoke the CLI. So, if you wrote this line with the old version:
pjsd input.js protected.js
you should change it to
jsdefender input.js protected.js
For single file scenarios that use command line options and no configuration file, changing the name is enough to move from version 1 to 2. However, if you protected multiple files with pjsd
, or used a configuration file with the CLI tool, you should be prepared for more changes.
Removed command line options
There are a few command line options that no longer exist in version 2:
--inclusive
: Turns on inclusive mode that protects only the scoped functions marked with the-s
/--scope
option.-s
,--scope
: Specifies the function name patterns to include (in inclusive mode), or exclude otherwise.
Instead of these switches, JSDefender now uses inline protection directives to set up partial protection. These directives are written directly into the source code and provide a more straightforward way to describe what partitions of the code get protected, and which techniques are applied for them.
--idprefix
: Set identifier prefixes for program-scoped declarations as a workaround to handle multi-file scenarios.--id-map
,--id-map-in
,--id-map-out
: Options to persist identifier declaration information between multiple sessions to manage multi-file scenarios.
All of these options were workarounds to allow you to use pjsd
for multi-file scenarios. Now that jsdefender
supports protecting multiple files in a single command line session, you do not need these options anymore.
--es5
: Disables using ES 2015 (or newer) language features to be used during obfuscation.
The pjsd
tool used this option for some scenarios (mainly for Internet Explorer compatibility). The jsdefender
CLI automatically detects the ES version in the source code and uses the protection features accordingly. If, for any reason, you still need more control over the ES version to use for protection, jsdefender
provides the --estarget
option.
-a
,- anns
,-A
,- annsoff
: These options were reserved for future extension in version 1. You do not need them in version 2.
Changed default values
In jsdefender
, the -m
,--mapout
option is turned on by default. Unless you add the -m off
or --mapout off
option to the command line, the CLI generates a maps.txt
file in the current working folder with the hierarchical map of lexical scopes. You can learn about the format of the file from the Lexical Maps section of the User Guide.
New command line options and switches
The jsdefender
tool has new command line options that were not available in pjsd
. These new options define new protection techniques:
--constarg
,--constargoff
: Turns on or off constant argument protection.--conscloaking
,--conscloakingoff
: Turns on or off console cloaking protection.--datelock
: Allows locking the source code to a particular date (and time) range.--devtools
,--devtoolsoff
: Turns on or off DevTools blocking protection.--exprseq
,--exprseqoff
: Turns on or off expression sequence transformation protection.--globobjhiding
,--globobjhidingoff
: Turns on or off global object hiding.--propsparsing
,--propsparsingoff
: Turns on or off property sparsing protection.--selfdefend
,--selfdefendoff
: Turns on or of self-defending protection.--vargroup
,--vargroupoff
: Turns on or off variable grouping protection.
The CLI provides other brand new options:
--estarget
: Allows defining the ES target of the protected code. By default, the highest ES version is used, which the tool detects while parsing source files.--randomseed
: You can define a seed value (32-bit number) to use for random number generation. Using the same value will ensure that JSDefender generates the same random numbers when running with the same command line options. This option is useful for troubleshooting.--ignore-unsafe
: The protection transformations check the input code for unsafe constructs that may break the code after protection (such as the JavaScriptwith
statement with variable renaming). By default, the tool aborts protecting unsafe code. Nonetheless, with this option, you can carry on protecting the code even if there are unsafe elements.--disable-inline
: With this option, you can disable the usage of inline protection directives, as if those were not written in the code.--nocolor
: Disables using colors in the CLI output.
Implicit configuration file
The jsdefender
CLI automatically checks the current working folder for a configuration file named jsdefender.config.json
. If that file exists, the CLI automatically loads and uses it. So, having that file in the current working folder allows invoking the CLI without any argument (i.e. you can just run jsdefender
). If you would like to use another configuration, use the configuration file name with the -c
or --config
option:
jsdefender -c myconfig.json
jsdefender --config myconfig.json
jsdefender.config.json
file in the current working folder, JSDefender ignores it, and applies myconfig.json
instead.
Simple configuration file usage
In a typical scenario, you execute jsdefender
with only the configuration file, like this:
jsdefender --config superheavy.config.json
You can make this more terse. If you have only a single filename argument, jsdefender
assumes it is the name of configuration file. So the following command line is equivalent to the previous one:
jsdefender superheavy.config.json
Protecting a single file
Migrating command line with no configuration file
If you are protecting a single file and configuring only via command line options (i.e. you have no configuration file), you can use the same command line options with jsdefender
as you used with pjsd
. For example, let's assume you have this version 1 command line to execute:
pjsd input.js protected.js -b -i -names sequential
In version 2, you need to change only the CLI name:
jsdefender input.js protected.js -b -i -names sequential
If you used the --inclusive
or -s
/--scope
option with pjsd
, you need to add inline protection directives to your source code to set up partial protection with jsdefender
. See the Inline configuration section of the User Guide.
You cannot use the --es5
option anymore. Instead, change it to --estarget es5
. Assume you have this command line to create Internet Explorer-compliant code:
pjsd input.js protected.js --es5
With jsdefender
, you need to execute:
jsdefender input.js protected.js --estarget es5
Migrating PJSD configuration files
If you use a configuration file with pjsd
, you need to migrate it to a new file format so that you can use it with jsdefender
. The new file format extends the old one with many new options:
- You can define multiple source and output files.
- There are options to set source code and output folders.
- You can create named configuration sets.
Protection techniques
For the single file scenario, the migration of the configuration file is simple. In pjsd
, the JSON configuration object has these properties to set up protection techniques:
booleanLiterals
integerLiterals
stringLiterals
domainLock
propertyIndirection
localDeclarations
debuggerRemoval
controlFlow
functionReorder
In jsdefender
, you can use them with their values. However, you must enclose them into a new object named settings
. Let's assume you have this pjsd
configuration:
{
"domainLock": {
"domainPattern": "mysuperapp.azurewebsites.net"
},
"stringLiterals": true,
"localDeclarations": {
"nameMangling": "base62"
}
}
You need to upgrade it to the use of jsdefender
this way:
{
"settings": {
"domainLock": {
"domainPattern": "mysuperapp.azurewebsites.net"
},
"stringLiterals": true,
"localDeclarations": {
"nameMangling": "base62"
}
}
}
Partial protection
The pjsd
CLI used the localContext
property in the configuration file to set up partial protection. You cannot use this property in jsdefender
, so you need to remove it from the migrated configuration file. To utilize partial configuration, learn the details from the Inline configuration section of the User Guide.
Setting ES5 mode
With the es5Mode
property that accepts a Boolean value, pjsd
allows you to set the protection target to ES5, just like in this sample:
{
"es5Mode": true,
"stringLiterals": true,
"localDeclarations": {
"nameMangling": "base62"
}
}
In jsdefender
, you can use the esTarget
configuration property with the es5
value for the same purpose. So, the migrated version of the configuration above is this:
{
"esTarget": "es5",
"settings": {
"stringLiterals": true,
"localDeclarations": {
"nameMangling": "base62"
}
}
}
Using idPrefix
The idPrefix
option of pjsd
is a workaround to provide a way to manage multi-file protection through multiple CLI sessions. You do not need this option at all for single file protection. In jsdefender
, this configuration option does not exist. If you find it in your pjsd
configuration file, then it has either been left there accidentally, or you use multi-file protection. In the first case, you can omit this option. In the second case, please read the Working with multiple files section.
Using license
You can still use the license
property to set up the license key to use for a protection session. Let's assume this is the pjsd
configuration file with license information:
{
"license": "MYLICENSE1234",
"stringLiterals": true,
"localDeclarations": {
"nameMangling": "base62"
}
}
Use this configuration format with jsdefender
:
{
"license": "MYLICENSE1234",
"settings": {
"stringLiterals": true,
"localDeclarations": {
"nameMangling": "base62"
}
}
}
Working with multiple files
The new version of JSDefender can handle multi-file scenarios out of the box. You do not need to run multiple pjsd
sessions; instead you can carry out the protection with a single jsdefender
CLI call.
jsdefender
allows setting file names in command line options only for single-file scenarios. With multiple files, to name the inputs and outputs, you need to use a configuration file. You can learn how to set up for these scenarios from the Protecting multiple files section of the User Guide.
Using the JSDefender Webpack plugin
The new version of the JSDefender Webpack Plugin works like the previous version except for the following things.
Package rename
The package is renamed from @preemptive/pjsd-webpack-plugin
to @preemptive/jsdefender-webpack-plugin
, so the package name in the package.json
should be changed as well as the package name in the webpack.config.js
.
const {
JSDefenderWebpackPlugin,
} = require("@preemptive/jsdefender-webpack-plugin");
Implicit configuration file without providing its name
The plugin now looks for an implicit configuration file in the root of the project called jsdefender.config.json
by default, just as the CLI does. That means it is not mandatory to provide the name of the configuration file in the plugin options, if it is called jsdefender.config.json
. Of course, it is possible to provide a configuration with another name.
new JSDefenderWebpackPlugin({
includeChunks: ["main", "child"],
});
new JSDefenderWebpackPlugin({
configurationFile: "jsdefender.config.json",
includeChunks: ["main", "child"],
});
// The two configurations above are identical if there is a `jsdefender.config.json` file in the project root
Configuration format
The configuration format has changed to the new format as stated in the Migrating PJSD configuration files section of this guide. This is true for the plugin constructor options and for the configuration file, if any.
new JSDefenderWebpackPlugin({
// The following plugin options are unchanged
configurationFile: "my-jsdefender-config.json", // The explicit or default (`jsdefender.config.json`) configuration file format should also be changed
includeChunks: [],
excludeChunks: [],
quietMode: true,
// A new option is added, to enable the protection in non-production modes
enableInDevelopmentMode: true,
// The inline protection configuration provided in the plugin constructor should be moved under the `settings` key instead of providing these in the config root
settings: {
// The inline protection configuration like `booleanLiterals`, `integerLiterals` etc.
},
});
Run the plugin in non-production modes
In the previous versions, the plugin was running the protection for every Webpack mode. From now on, by default the protection is skipped if the Webpack config called mode
is not set to production
. According to the Webpack documentation, the default value is production
, so if you have not changed it, the protection is going to run. In order to run the protection in non-production modes, set the enableInDevelopmentMode
option of the JSDefender Webpack Plugin to true
.
Configuration merging strategy
The configuration merging strategy is changed. In the previous version the protection configuration provided in the configurationFile
are overridden by the protection configuration provided directly in the new PjsdWebpackPlugin()
constructor. In the new version, the constructor configuration overrides the configuration provided in the explicit or implicit (jsdefender.config.json
) configuration file, if any.
jsdefender.config.json
:
{
"settings": {
"booleanLiterals": true
}
}
webpack.config.js
:
...
new JSDefenderWebpackPlugin({
settings: {
booleanLiterals: false
}
})
...
In the above case the final value of the booleanLiterals
option is going to be false
.
Default configuration
The plugin uses a default configuration as the CLI does. This can be overridden by explicitly specifying a configuration to the plugin.
Using the JSDefender Metro plugin
The new version of the JSDefender Metro Plugin works like the previous version except for the following things.
Package rename
The package is renamed from @preemptive/pjsd-metro-plugin
to @preemptive/jsdefender-metro-plugin
, so the package name in the package.json
should be changed as well as the package name in the metro.config.js
.
const jsdefenderMetroPlugin = require("@preemptive/jsdefender-metro-plugin")({...}, {...});
module.exports = jsdefenderMetroPlugin;
Implicit configuration file without providing its name
The plugin now looks for an implicit configuration file in the root of the project called jsdefender.config.json
by default, just as the CLI does. That means it is not mandatory to provide the name of the configuration file in the plugin options, if it is called jsdefender.config.json
. Of course, it is possible to provide a configuration with another name.
const jsdefenderMetroPlugin = require("@preemptive/jsdefender-metro-plugin")(
{},
{}
);
const jsdefenderMetroPlugin = require("@preemptive/jsdefender-metro-plugin")(
{ configurationFile: "jsdefender.config.json" },
{}
);
// The two configurations above are identical if there is a `jsdefender.config.json` file in the project root
Configuration format
The configuration format changed to the new format as stated in the Migrating PJSD configuration files section of this guide. This is true for the options provided through the plugin's function parameters and for the configuration file, if any.
const jsdefenderMetroPlugin = require("@preemptive/jsdefender-metro-plugin")(
{
// The following plugin options are unchanged
configurationFile: "my-jsdefender-config.json", // The explicit or default (`jsdefender.config.json`) configuration file format should also be changed
quietMode: false,
protectUserModulesOnly: false,
// A new option is added, to enable the protection in non-production modes
enableInDevelopmentMode: true,
// The inline protection configuration provided in the plugin constructor should be moved under the `settings` key instead of providing these in the config root
settings: {
// The inline protection configuration like `booleanLiterals`, `integerLiterals` etc.
},
},
{}
);
Run the plugin in development mode
In the previous versions, the plugin was running the protection in both production and development builds. From now on, by default the protection is skipped for development builds: i.e. if the --dev false
arguments are not provided to the react-native bundle
command. According to the Metro documentation, the default is --dev true
, so if you have not changed it, or you are passing the --dev true
to the bundle
command, then the protection is going to be skipped. In order to run the protection in development mode, set the enableInDevelopmentMode
option of the JSDefender Metro Plugin to true
.
If the enableInDevelopmentMode
option is set to true
, then the react-native run-android
or react-native run-ios
commands without the Release arguments will fail. If you want these not to fail, run the react-native run-android --variant=release
or react-native run-ios --configuration Release
commands respectively to run the release builds or set the enableInDevelopmentMode
option to false
.
Configuration merging strategy
The configuration merging strategy is changed. In the previous version the protection configuration provided in the configurationFile
are overridden by the protection configuration provided directly as the plugin's function parameters. In the new version, the plugin function parameter configuration overrides the configuration provided in the explicit or implicit (jsdefender.config.json
) configuration file, if any.
jsdefender.config.json
:
{
"settings": {
"booleanLiterals": true
}
}
metro.config.js
:
...
const jsdefenderMetroPlugin = require("@preemptive/jsdefender-metro-plugin")(
{
settings: {
booleanLiterals: false
}
},
{
...
}
);
...
In the above case the final value of the booleanLiterals
option is going to be false
.
Default configuration
The plugin uses a default configuration as the CLI does. This can be overridden by explicitly specifying a configuration to the plugin.