Error Scripts
A few transforms: domain lock, date lock, and self-defending protection are runtime checks that can execute code in response to the check. These transforms all have an optional errorScript
configuration property, where you can provide a script to run when the runtime check triggers. In this section, you learn how to add error scripts to your protected code.
How JSDefender Runs without the errorScript
Configuration Option
When you do not use the errorScript
configuration property, JSDefender applies its default protection:
Transform | Default protection |
---|---|
Domain lock | Raises a RangeError: Maximum call stack size exceeded error message. |
Date lock | Raises a RangeError: Maximum call stack size exceeded error message. |
Self-defending protection | Raises a SyntaxError message. |
SyntaxError
. Sometimes the random-like transformation of the code may result in other error messages.
How JSDefender Uses Your Error Scripts
The protection engine injects the string value of the errorScript
configuration property into the code as it is. Nonetheless, the protection takes care that each error script runs in its JavaScript block statement. Let's assume you have this error handling snippet:
const myMessage = "I got you!";
alert(myMessage);
It goes into the code as if you wrote this, providing a separate scope for the myMessage
constant variable:
{
const myMessage = "I got you!";
alert(myMessage);
}
Error: the code has been tampered!
message when observing a protection violation.
Where Error Scripts Are Injected
The engine injects the error scripts of domain lock and date lock transformations only once into the protected code. A well-protected section, called the JSDefender Runtime, will call this error handler if the lock tests suggest so. If you are protecting multiple files, this code always goes into the very first protected file. When you are using bundled modules or web chunks, JSDefender may inject the error handler code into multiple files. JSDefender protects the error handler script with the same settings as the file it injects the script into.
In contrast, the self-defending transformation injects the error script into every protected function. As JSDefender supports partial configuration, you can even assign separate error scripts to different protected functions.
What You Can Do with an Error Script
With JSDefender version 2.2 or earlier, you could carry out these actions:
- Provide additional actions and then let JSDefender raise an error message.
- Provide additional actions and allow the code to run as if no violation occurred.
As of version 2.3, the second option is no longer available. It was removed because that configuration allowed attackers to more easily bypass date and domain lock protections.
Writing Error Scripts
When creating your error scripts, you can use the entire toolset of JavaScript, including:
- variable, function, and class declarations
- statements
- invoking global functions
- exception handling (
try..catch..finally
)
Nonetheless, there are a few things you should be aware of:
- If you can, avoid using the
with
statement or theeval
function in your code. Those may break the code after running protection. - Handle
var
declarations carefully, as those may collide with identifiers in other lexical scopes of your code. - Do not import modules in the error script code. If you need to invoke functions or class methods, access them through global variables or objects.
- Though you can use async code with
async
andawait
, the error handler runs synchronously.
Using var
declarations
It is a good practice to use only ES 2015 let
and const
variables. If for some reason, you need to use var
declarations, those may (accidentally) collide with other variable names, and that may break your error handler. In these cases, use the JavaScript module pattern with an IIFE (Immediately-Invoked-Function-Expression). For example, do not use this script:
var myMessage = "I got you!";
alert(myMessage);
Instead, inject this:
!(function () {
var myMessage = "I got you!";
alert(myMessage);
})();
Invoking Global Functions
You can access variables in the global JavaScript scope, and call functions declared there, including the global JavaScript functions. So if you have a global function named myDateViolationHandler
, you can apply this errorScript
:
myDateViolationHandler("violated");
myDateViolationHandler
and its reference in the error handler script.
Using async
Code
The error handler is executed synchronously. Though you can add async
code into it, you cannot await
the result of those functions to decide whether to allow running the application code despite the violation, or to stop.
Take a look at this code:
function timeout(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
(async () => {
await timeout(2000);
alert("Invalid date");
return;
})();
At first sight, you may think this code allows your application to continue running after displaying the "Invalid date" message, as there's a return
. However, that returns from the async IIFE. By that time, the message is displayed and confirmed (showing it has a 2-second delay). The protection engine executes its default action and stops with a ReferenceError
message.
To allow the code to run despite the violation, you should add a closing return to your code:
function timeout(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
(async () => {
await timeout(2000);
alert("Invalid date");
return;
})();
return; // --- Go on with the violation
An Error Handler Pattern to Follow
There's a simple pattern you can follow to write excellent error-handling scripts. Here you can learn how.
JSDefender takes care that the domain and date checking protection runs before the very first line of your original code. Using this pattern allows you to store the result of the date violation. Of course, you can use a similar pattern to manage domain violation.
Add this code to your program, right at the beginning:
let dateViolated;
function signDateViolation() {
dateViolated = true;
}
if (dateViolated) {
// Add your date violation logic here
// including aborting the app, logging out the user, etc.
}
// This code runs if you let the control flow reach this point
doMyJob();
Add this errorScript
to the dateLock
configuration:
"dateLock": {
"startDate": "<your start date>",
"endDate": "<your end date>",
"errorScript": "signDateViolation(); return;"
}
As a return
statement closes the error handler, JSDefender lets the flow of the program continue even if a date violation occurred. Since JSDefender runs the date check logic before reaching the let dateViolated
declaration, you can be sure that by the time the if
statement evaluates its condition, the dateViolated
variable will be set correctly.