· engineering · 7 min read
Obfuscation in JavaScript
How, why and whether to obfuscate your frontend code
Table of Contents
What can we do to protect the client-side intellectual property in our web applications?
It’s a reasonable enough question. A lot of time and money are invested in developing applications, and it’s only natural that businesses will want to prevent unauthorized copying, reverse engineering, and the exposure of sensitive, valuable, business logic.
This issue is particularly relevant to web applications. Increasingly, over the past decade or more, as much logic as possible has been moved to the frontend. Driven by the rise of single page applications, demands for better, faster interactivity, and a pragmatic desire to shift resource consumption of servers to the client, this application logic is almost entirely delivered as JavaScript.
JavaScript has an obvious flaw in this context though: it is transmitted in plain text to all users, available to anyone with a modicum of tech knowledge to investigate, modify and potentially abuse.
Given that we wish to keep as much of our logic as possible running on the client, while keeping it secret, away from prying eyes, what can be done?
Two strategies spring to mind: we either need to make our code illegible through obfuscation, or deliver it in a different, non plain-text format, such as WebAssembly (WASM).
In this article, we’ll look at the pros, cons, and methods of protecting frontend code, touching briefly on WASM, but focusing primarily on JavaScript obfuscation.
To obfuscate?
The reasons for code obfuscation in a business sense are fairly obvious. They boil down to two main motivations: hindering competition, and increasing security.
- Hindering competition:
Your code can reveal a lot about your business. It doesn’t just show how you implement current features, for copying or reverse engineering, but can also disclose information on future features being prepared and offer insights into the quality and size of your development team. This information can reflect the internal state of your business and its resources, all potentially valuable to competitors. - Improving security:
In an ideal world, revealing the inner workings of your frontend code shouldn’t be a security risk. The backend should never trust data from the client, and authentication methods should be secure. However in reality there could be flaws, exploits, or sensitive information lurking in your frontend code that a malicious actor could exploit.
However, it is important to note that this is “security through obscurity” and this should never be considered an alternative to a solid security posture.
Or not to obfuscate?
While obfuscation can be tempting for protecting intellectual property, given the choice many developers would prefer to avoid it due to practical and philosophical concerns. The main drawbacks include:
It’s harder to maintain obfuscated code:
Illegible code, is of course, harder to understand and debug. While in pre-production environments the code would be legible, the reality is that debugging often occurs in production. Reporting and understanding errors is inherently harder if you cannot access the underlying code. To make matters worse, it’s also unlikely, but possible, that errors, or performance issues, are introduced by the obfuscation process itself.It’s potentially less secure:
Obfuscated code is of course more secure than it’s plain text alternative as it makes finding exploits more difficult.
Yet a reliance on security through obscurity can provide a false sense of security, leading to a lessened emphasis on genuine security measures in an organization and an overall decrease in security. Additionally it prevents security researchers inspecting your code to identify report issues.It’s not fair:
We are all standing on the shoulders of giants. The majority of developers rely on and value open source principles; knowledge sharing, curious reverse engineering, and studying existing code help developers to learn and improve. The developers who have produced the code to be obfuscated have all benefitted from the open code of others, thus making code intentionally hard to understand may be seen as an act of pulling the ladder up after oneself.
This concern can be addressed with empathy, and the considered obfuscation of only those areas genuinely in need of protection.
Now we’ve considered the arguments for and against obfuscating our code, and assuming we still want to obscure some or all of it, let’s look at how these methods work:
WebAssembly (WASM).
Code written in languages such as C++, Rust, or even JavaScript itself can be compiled into WASM, a bytecode format that can be run directly in the browser.
With WASM, we’re not obfuscating code to be illegible; we’re delivering it an format that is inherently difficult to read and reverse engineer, as the source code is never delivered to the browser. As an added bonus, applications distributed in WASM are highly performant.
Of course, nothing’s foolproof, a determined attacker may be able to gleam some information about the original code, but it’s still inherently more secure than delivering code as JavaScript, whether obfuscated or not.
A potential downside of using WASM is that it wouldn’t be a drop in replacement, using it in an existing application might add significant complexity, and require re-architecting your frontend and delivery pipelines, and potentially upskilling developers.
Obfuscation with JavaScript
Obfuscation employs several techniques to make the code less readable and harder to analyze. Here are some of the basic techniques:
- Variable and Function Renaming: Changing the names of variables, functions, and classes to meaningless names (e.g., a, b, c1).
- Encryption: Converting strings into encoded or encrypted forms, which are then decoded at runtime.
- Altering the code: Transforming the logical flow of the program to make it more complex and less predictable, splitting the code into non-intuitive parts and adding pointless conditions and unused code that do not affect the program’s functionality but increase complexity.
These processes are mostly one-way transformations. While deobfuscating code is extremely difficulty and time-consuming, it’s not entirely impossible for skilled attackers with the right resources.
Basic implementation
There’s a great npm package javascript-obfuscator
, simply install it to your dev dependencies, and add obfuscation to your build script or pipeline. There are a lot of options to enable/configure various obfuscation methods, and information on the potential performance impact of each.
The same package powers obfuscator.io where you can try various options, and obfuscate snippets of javascript.
Increasing complexity
Though JavaScript obfuscation is easy to implement, and difficult to reverse engineer, there are companies, such as Jscrambler, who go a step beyond obfuscation. They offer more advanced solutions, such as environment checks, and inserting tamper detection code to further guard against interference.
In highly sensitive areas, and businesses that are required to comply with extremely strict security standards, this sort of protection may be warranted, but it would be worth looking into alternative ways of mitigating the risk beforehand.
Side step the issue.
The problem we’re trying to solve, is obscuring code that runs on the client. In some situations, this is unavoidable.
Yet in many situations there’s a much cheaper, simpler, securer, and more maintainable solution.
Before obscuring your code, consider simply moving logic back to the server, keeping it away from the client in the first place.
Conclusion
Whether or not to obscure your frontend code is a decision that needs to be based on several pragmatic reasons.
Realistically, not all companies will have code that is innovative or sensitive enough that the benefits of obscuration out weigh the drawbacks, and those that do may well be better served using alternatives to code obfuscation, for example using WASM, or by simply not exposing it to the client in the first place.
If you’re still considering obfuscating your code, and have evaluated the necessity and alternatives, be sure to keep the security aspects in mind, and be ready to deal with any philosophical arguments that may arise.
About James Babington
A cloud architect and engineer with a wealth of experience across AWS, web development, and security, James enjoys writing about the technical challenges and solutions he's encountered, but most of all he loves it when a plan comes together and it all just works.
No comments yet. Be the first to comment!