Security engineering
Published
It’s a fair worry, and a good one to ask before you ship: if you run a strict Content Security Policy — no unsafe-eval, no unsafe-inline — will obfuscating your JavaScript force you to punch a hole in it? For one whole class of tools, yes. For modern obfuscation of the kind we do, no. The distinction is worth understanding, because “we had to allow unsafe-eval for the obfuscator” is a real regression teams sometimes accept without realizing they didn’t have to.
The thing CSP actually blocks
A strict CSP’s two most valuable restrictions are unsafe-eval (no eval(), no new Function(), no string-to-code execution) and unsafe-inline (no inline <script> without a nonce or hash). They exist because string-to-code execution is the classic injection primitive — if an attacker can get a string into eval, they run code. Turning those off is one of the highest-leverage things a security team can do, so anything that forces them back on is a genuine cost.
Why old “packers” break under it — and modern obfuscation doesn’t
The confusion comes from an older generation of tools. Classic JavaScript packers (the eval(function(p,a,c,k,e,d){...}) style) work by shipping the real code as a string and calling eval() to unpack it at runtime. That is fundamentally incompatible with a strict CSP: no unsafe-eval, no unpack, broken page. If your mental model of “obfuscation” is that pattern, the worry is justified.
Modern obfuscation doesn’t work that way. Its transforms are ordinary JavaScript operations, not string-to-code execution:
- String encryption / string arrays decode via array indexing and normal string functions — no
eval.
- Control-flow flattening is a
switch-driven state machine — regular control flow, no eval.
- VM bytecode runs marked functions on an inlined interpreter — a normal function that switches over opcodes. An interpreter is code that reads data, not code that executes strings; it needs no
eval.
Our engine emits none of eval, new Function, or other string-to-code calls as part of these transforms, so the protected output runs under a strict CSP without unsafe-eval. (As always, test your specific build against your specific policy before you ship — but the technique itself doesn’t require the weakening.) If a tool tells you to add unsafe-eval to accommodate its output, that’s a property of that tool, not of obfuscation in general.
Which of our own options do need unsafe-eval
Being precise matters more here than sounding good, so: the transforms above are eval-free, and there are exactly two optional transforms in our engine that are not. Both are off unless you turn them on, and neither is part of any preset you get by default.
SelfCompression (Compressor) packs the output into a self-extracting wrapper. The bootstrap rebuilds your code from a string, which means eval and new Function.
ReorderCodeEval (Code Transposition Eval) is eval-based by design — it stores the transposed wrapper as an encoded string and reconstructs it at runtime.
Three runtime wrappers belong on the same list, and it is worth naming them because teams removing unsafe-eval often check only the transforms. SelfDefending, SelfHealing and AntiMonkeyPatching evaluate code of their own while your program runs, and the engine emits an explicit build warning to that effect when you enable them: they require a policy that permits dynamic evaluation, so test the deployed policy before shipping. Two related notes. Those same three wrappers are skipped automatically when your source is an ES module, with their own warning, so that import and export linking stays valid — which means on a modern module build they may not be doing anything in the first place. And AntiMonkeyPatching's clean-realm comparison creates a hidden iframe, so a policy restricting frame-src affects it too.
If you run a strict policy, leave all five off. You lose nothing structural: Flat Transform, Code Transposition, the string transforms, member indirection, and VM protection all deliver protection without string-to-code execution. The practical failure mode is worth knowing too: a blocked eval shows up as a CSP violation report, not a JavaScript exception you can catch, so the symptom is a silently dead feature rather than an error in your logs.
The same advice applies with more force inside a Web Worker or Service Worker, and in a browser extension, where eval is forbidden outright rather than merely discouraged.
The CSP question that is about you, not the obfuscator
There is one CSP interaction that has nothing to do with the transforms and everything to do with how you include the script: inline vs. external. If you paste an obfuscated bundle straight into an inline <script>...</script>, you’ll need unsafe-inline or a per-response nonce/hash — but that’s true of any inline script, obfuscated or not. The clean answer is the same as for unprotected code: serve it as an external .js file from your own origin, which a strict CSP allows via script-src 'self'. Obfuscation doesn’t change that calculus.
Two smaller notes while you’re here: don’t obfuscate the CSP nonce-wiring or config itself, and don’t let “we’re adding obfuscation” become the reason a strict policy quietly gets relaxed. If anything, obfuscation and a strict CSP are complementary — one raises the cost of reading your code, the other shrinks the runtime attack surface.
The short version
Eval-based packers break strict CSP; modern obfuscation doesn’t, because its transforms are ordinary code, not string-to-code execution. Keep unsafe-eval off, serve your protected code as an external file so you can keep unsafe-inline off too, and verify the result against your own policy. You should not have to weaken your CSP to protect your JavaScript.
Frequently asked questions
Does obfuscated JavaScript violate a Content Security Policy?
Not by itself. A policy constrains where code may be loaded from and which dynamic evaluation features may be used. It has no opinion about how readable the code inside an allowed file is, so a protected external script served from an allowed origin is treated exactly like an unprotected one.
Which options actually interact with a policy?
Only the ones that produce dynamic evaluation. Two options in the set emit code that evaluates a string at run time, and one of them force-enables that path. Those are the only settings that collide with a policy that forbids dynamic evaluation. Everything else, including renaming, string tables, string encryption, control-flow changes and dead code, produces ordinary JavaScript that any policy permits.
Can we keep a strict policy and still protect our code?
Yes, by leaving the two evaluation-producing options disabled. That is the default position rather than a compromise, and it leaves the substantial majority of the option set available. Confirm it on a real build rather than from configuration by loading the protected page with the policy applied and watching for violation reports.
Does protection affect inline script hashes or nonces?
It affects hashes, and the reason is worth stating precisely. A hash covers exact bytes, so if a transformation changes an inline block the previously computed hash no longer matches and the browser blocks it. The fix is ordering: compute hashes from the files you are actually deploying, after protection, not before. A nonce is applied at response time and is unaffected.
How does this interact with subresource integrity?
The same way, and with the same fix. Integrity attributes are computed over exact file contents, so they have to be generated from the protected artifact rather than the pre-protection one. Generating them from the wrong directory produces a page that blocks its own scripts, which is a deployment failure rather than a policy question.
What should we check on a protected release?
Load the protected build with your real policy in enforcing mode, exercise the paths that load code dynamically, and confirm the violation report endpoint is quiet. If you see violations mentioning dynamic evaluation, check whether either of the two evaluation-producing options is enabled. If you see integrity or hash failures, check which directory your hashes were generated from.
Related reading:
Does obfuscation break eval and new Function? ·
Obfuscation and Permissions-Policy ·
Does obfuscation break cross-origin isolation? ·
Does obfuscation slow down your app? ·
A bug bounty report says your bundle was reversed ·
Does obfuscating JavaScript hurt your SEO? ·
Your source maps are publishing your source code ·
Does obfuscation break Trusted Types? ·
Can someone put your protected app in an iframe? · obfuscation will not fix a vulnerability · Obfuscating htmx and Alpine apps.