A JSFuck decoder takes a JSFuck-encoded payload, source written using only the six characters [, ], (, ), !, and +, and statically recovers the original JavaScript without running it. JSFuck itself is public knowledge: it exploits JavaScript's implicit type coercion rules to spell out any program from just those six symbols. Decoding it safely means simulating those coercion rules on paper, not handing the payload to a browser console, Node, or an online JS sandbox.
Paste JSFuck source, the bracket-only []()!+ blob, and get the original JavaScript back as readable text. The payload does not get a chance to run. If you got here because you were halfway through writing a python jsfuck decoder, or searching for a jsfuck decoder library to pip install, that instinct is close to right and worth pausing on before you finish it. A decode script that works by calling exec(), shelling out to node, or driving a headless browser to render the page is not decoding JSFuck. It is running it, and if the sample is malicious, running it is the one step you cannot take back: a cookie is already read, a beacon has already fired, a redirect has already happened, before your script even finishes printing a result. Static decoding skips that step. Read on for how JSFuck payloads actually execute, why the usual quick-check moves, console paste, online evaluator, Python-to-JS bridge, cross the same line in slightly different ways, and what decoding an alert(), an XSS payload, or a hybrid JSFireTruck stub looks like as a read-only static result.

Decode JSFuck without running it. Paste a JSFuck-encoded sample at klaroskope.com/submit and get the recovered JavaScript back as text, including hybrid JSFireTruck stubs that unpack a Base64 or string-array inner layer in the same pass.
- Why you cannot just run it to decode JSFuck code
- Decode a JSFuck alert() or XSS payload
- Hybrid JSFuck (JSFireTruck): the stub that unpacks Base64 or a string array
- Do I need a python JSFuck decoder library?
- When JSFuck decoding gets hard
Why You Cannot Just Run It to Decode It
JSFuck output is not a stub that unpacks itself later and waits for a signal. It is the program. What is JSFuck covers how six characters build up to full statements through type coercion; the practical consequence is that JSFuck can spell out any identifier character by character, including eval and the Function constructor, the exact primitives that turn decoded text into running code. The moment a JavaScript engine parses the source, the hidden logic runs. This is also what makes JSFuck effective against keyword-based content filters: a filter scanning for eval or document.cookie as literal text finds neither, because those words do not appear as strings in the file. They exist only as a coercion result, produced once something actually executes the expression.
That property is exactly why a jsfuck decode has to be read-only to count as decoding. The moment execution enters the picture, you are not decoding the sample anymore, you are running it, and running an untrusted sample to find out what it does is the outcome static analysis exists to avoid. The common ad hoc moves defenders and developers reach for to quickly decode jsfuck code cross that same line in slightly different ways.
- Pasting the blob into a browser console, which runs it with the page's cookies, DOM, and session context
- Online JS "unpacker" or beautifier sites that call eval() internally to produce readable output
- Rendering the page in a headless browser (Selenium, Puppeteer, Playwright) to see what loads, which runs the script with real network access
- Python-to-JS bridges such as PyExecJS, js2py, or quickjs bindings, built specifically to execute JavaScript from Python
- Shelling out to a real JS runtime, for example calling node -e from a Python script, which just moves the eval into a subprocess
# This "quick check" looks like static analysis but is not:
import execjs
ctx = execjs.compile(jsfuck_source) # already ran whatever the payload does
result = ctx.eval("1") # side effects have already happened# Shelling out doesn't remove the risk, it relocates it:
import subprocess
subprocess.run(["node", "-e", jsfuck_source]) # executes in a real JS engineA JSFuck payload is valid JavaScript from its first character. There is no preview mode: a browser tab, a Node process, a headless browser, and a Python-to-JS bridge each run the hidden logic in full the moment they parse it. Decode it statically first, and only consider running the recovered plaintext once you know what it says.
Decode a JSFuck alert() or XSS Payload
Searching for something like jsfuck alert(1) code usually means you already have a bracket blob and a hunch about what it decodes to: a pen tester's proof-of-concept, or a live injection someone else planted, encoded in JSFuck specifically so a WAF or input filter waves it through. What you are left with is a bracket blob sitting inside a page's HTML, and the question is simple: is this an alert(1) proof-of-concept, an alert(document.cookie) cookie-theft demonstration, or something that redirects the victim to another site altogether? You cannot tell from the encoded form. You can tell from the decoded one.
INPUT: JSFuck-encoded XSS payload (truncated, ~3,800 characters)
[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+
(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]][...]
OUTPUT: Decoded JavaScript (recovered statically, no execution)
alert('XSS successful')That output tells you what a security assessment or bug bounty submission actually looked like: a confirmation string, no cookie access, no follow-on network call. Write-ups and challenge descriptions phrase the same confirmation different ways, alert('XSS successful'), a printed "Operation successful", a changed document.title, but the mechanism is identical: the decoded call is the evidence. If the decoded source instead reads document.location='http://...' or reaches into document.cookie, the severity assessment changes, and it changes based on evidence you can cite rather than a guess. Recognising JSFuck-encoded XSS in the wild is not new: JSFuck-encoded payloads slipped past eBay's listing filters between 2015 and 2017, executing in auction pages that buyers viewed. The decode step is what turns "there is a bracket blob in this listing" into a specific answer for what it does.
Hybrid JSFuck (JSFireTruck): When the Stub Unpacks Base64 or a String Array
Pure JSFuck is expensive to write in. The word "alert" alone costs roughly 21,000 characters once each letter is derived from type coercion, so a full payload encoded purely in JSFuck would run to megabytes, impractical to inject into a compromised page without drawing attention. Attackers who want JSFuck's filter-evasion property at real scale solve this by encoding only a small stub in JSFuck, just enough to build the handful of function names a filter would otherwise flag, and passing everything else through as an ordinary Base64 string or a string-array that the stub decodes and runs.

Unit 42 documented this exact pattern at scale in its 2025 JSFireTruck research: JSFuck-encoded redirector stubs injected into compromised WordPress pages, with the campaign reaching 269,552 pages in a single month and daily infection rates topping 50,000 pages at its peak. The stub itself stayed small; the Base64 or string-array payload it decoded carried the redirect logic that sent visitors toward tech-support scams and malware downloads. This is advisory-level reporting on a documented campaign, not attribution to a specific threat actor.
This hybrid shape is where a naive "just eval it" approach is at its most dangerous and least informative at the same time. Running the stub executes both layers back to back in a single step: the redirect or download happens, but the Base64 or string-array data underneath, the part that would actually tell a defender what the stub was reaching for, stays invisible as data. It only exists as a side effect that already happened. A static decoder that treats the stub as one layer in a chain avoids that trade-off: recover the stub's plaintext first, recognise the encoding underneath it, and keep decoding through it in the same pass, so the output is a readable redirect URL or payload location rather than a page navigation you have to reverse-engineer after the fact.
Do I Need a Python JSFuck Decoder Library?
Searching for a python jsfuck decoder, jsfuck decoder python, or jsfuck decoder library usually means one of two things: you want to automate triage of samples inside a Python pipeline, or you want to avoid running untrusted JavaScript in the first place and would rather stay inside a language you already control. The second reason is sound. The first is where most available options fall short, and the honest reason is worth spelling out rather than waving away.
Writing a robust decoder from scratch is harder than the six-character rule suggests. It has to reproduce JavaScript's type-coercion behaviour with specification-level accuracy: HTML-escaping quirks in deprecated string methods like fontcolor(), the exact string form a RegExp literal produces, multi-pass resolution of characters that are themselves defined in terms of other characters, and escape-sequence handling that differs from what most people assume about octal versus hex. Why JSFuck deobfuscation breaks walks through these specific edge cases in depth. A decoder that gets one of them wrong does not throw an error; it quietly produces the wrong character at that position, and a single wrong character in a wall of brackets is easy to miss.
Many Python JSFuck decoder packages take a shortcut instead of solving that problem: they wrap execjs, PyExecJS, or a bundled Node subprocess, and hand your JSFuck string to a real JavaScript engine to evaluate, then capture whatever it produces. That is not static decoding. It is running the payload inside a subprocess instead of a browser tab, with the same execution risk in a different wrapper. If the JSFuck decodes to something that reaches the network, writes a file, or spawns a shell, the wrapper library gives it a place to do exactly that.
The practical route for a one-off sample: paste it into a hosted static decoder built for this, instead of writing or importing a jsfuck decoder library. Paste at klaroskope.com/submit; the decoded JavaScript comes back as text, hybrid stubs continue through the inner Base64 or string-array layer in the same pass, and there is no local Python environment that had to execute an unknown payload to get there.
When JSFuck Decoding Gets Hard
Some JSFuck samples do not decode cleanly on the first pass. The encoder relies on specification-level JavaScript runtime behaviour, including deprecated string methods and edge cases in how certain characters get indexed, and a decoder that gets those details even slightly wrong produces output that looks plausible but does not match what the original code actually did. Custom character sets like the ones introduced in the JSFireTruck campaign, very large hybrid files, and deeply nested multi-pass character references push on those same edge cases. Why JSFuck deobfuscation breaks covers the specific cases that defeat naive decoders in more depth, useful background if a decode result looks off or truncated. The short version: a decoder should flag when it is unsure rather than presenting a partial guess as a clean recovery.
Paste your JSFuck sample at klaroskope.com/submit and get the recovered JavaScript back as text, without running any of it along the way. Hybrid samples (a JSFuck stub wrapping Base64 or a string-array inner layer) decode through the full chain in a single pass. Try KlaroSkope Free
Frequently Asked Questions
Is it safe to decode JSFuck?
Can I decode JSFuck in Python?
What does a JSFuck alert(1) payload do?
How do I decode a JSFuck XSS payload found in a compromised page?
What is JSFireTruck?
Do I need a JSFuck decoder library, or is a hosted tool enough?
Continue Learning
Ready to decode?
See KlaroSkope transform obfuscated scripts into actionable intelligence.
Try It Free