Upgrading JSDefender 1.x to 2.x

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 of pjsd.
  • 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 JavaScript with 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
Note: In this example, even if there's a 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.
Note: You can learn more about the new format in the Configuration file format section of the User Guide.

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.

PreEmptive Protection™ JSDefender™ Copyright 2021 PreEmptive Solutions, LLC