# synchrony: undoing javascript-obfuscator output

> synchrony is a TypeScript tool that takes JavaScript obfuscated by javascript-obfuscator and produces readable source, with a string decoder that runs automatically and no user configuration to tune. Its README spends as much space on version drift and on how to report a failure as it does on the feature list, which tells you what kind of tool this is.

**relative/synchrony** — javascript-obfuscator cleaner & deobfuscator

- Repository: https://github.com/relative/synchrony
- Website: https://deobfuscate.relative.im/
- Stars: 1,260 · Forks: 149
- Language: TypeScript
- License: GPL-3.0
- Published: 2026-10-19 · Updated: 2026-10-19 · Language: en
- Canonical page: https://hysenlabs.com/projects/relative-synchrony

## A cleaner aimed at one obfuscator in particular

synchrony is described as a javascript cleaner and deobfuscator, primarily for javascript-obfuscator and the site behind it, obfuscator.io. That qualifier is the most important thing in the description, because it tells you the tool is not a general purpose JavaScript beautifier with a deobfuscation mode bolted on. It is built to reverse one specific set of transformations: the string array, the control flow flattening, the identifier renaming and the rest of the javascript-obfuscator toolbox. The project publishes an API reference, generated with typedoc and hosted at relative.github.io/synchrony, and there is a web version at deobfuscate.relative.im where you can paste a script and get the cleaned result without installing anything. The audience is a developer who has a minified, mangled bundle in front of them and needs to know what it does, typically because a bug report points at a line of code they cannot read, or because they are assessing a third party script before running it.

## The pipeline: parse, resolve scope, regenerate

The dependency list is the pipeline. The parser is acorn at version 8.7.0, with acorn-loose for input that does not parse cleanly and acorn-walk for traversing the tree. Scope analysis is eslint-scope, which is what makes identifier demangling possible, since you can only rename a binding back to something meaningful if you know which references resolve to which declaration. Code generation is @javascript-obfuscator/escodegen, a fork of escodegen, which is notable: the tool regenerates the source with the same printer the obfuscator used, so the output shape matches the input conventions rather than being reformatted into a different style. The string handling is where a deobfuscator is usually judged, and the project uses a mersenne-twister library, which is what the obfuscator's own string array encoding relies on, so the decoder can reproduce the shuffle that encrypted the strings. The command line is yargs, and the whole thing is built with esbuild and TypeScript. The description in the package manifest calls it a multipurpose javascript transformer, which is the honest name for a set of AST visitors applied in sequence.

## The string decoder runs on its own, with nothing to configure

There is a usage note that says the string decoder works automatically and that there is no user configuration as of yet. That single sentence describes both the appeal and the limitation. The appeal is that the common case is a single command with no tuning, no plugin file and no obfuscator config to reconstruct. The limitation follows from the same design: because there is no configuration, there is also no lever to pull when the decode does not match the input. The tool is a fixed sequence of transformations, and a fixed sequence either fits the artifact in front of you or it does not. The documentation is clear that the primary target is javascript-obfuscator output, and the term cleaner in the description is worth noting, because much of the value is in restoring readable structure and demangled names rather than in defeating a protection. For a script that has been through a minifier alone, a formatter is the right tool and synchrony is more machinery than you need.

## Installing and running it end to end

The usage section gives a four step path, and it is the fastest way to see whether the tool fits your artifact. The package is published on npm under the name deobfuscator, even though the project is called synchrony, and it can be installed globally with npm, yarn or pnpm:

```shell
# 1. Install deobfuscator globally using yarn/npm
npm install --global deobfuscator

# 2. Get an obfuscated file
curl https://gist.github.com/relative/79e392bced4b9bed8fd076f834e06dee/raw/obfuscated.js -o ./obfuscated.js

# 3. Run deobfuscator
synchrony deobfuscate ./obfuscated.js
```

The result is written next to the input with a .cleaned.js suffix, so the fourth step is to read that file. The same section also gives the route from git, installing the master branch directly with `npm install --global relative/synchrony#master`, which is useful when you want to try an unreleased state. The command itself is a single subcommand, deobfuscate, taking a path, and the binary is registered as synchrony, so the executable name and the npm package name differ, which is a small trap when you are reading an install error.

## When it fails, the README tells you what not to do

Two failure modes are documented and both are worth reading before you spend an hour on the wrong path. The first is version drift, and the wording is emphatic: artifacts produced by old versions of javascript-obfuscator will likely not deobfuscate correctly, and you are told not to open an issue, but to try previous versions of synchrony or another deobfuscator. The second is the transformer error, which appears in the terminal like this:

```text
Caught an error while attempting to run AST visitor!
node = Node {...}
err = ...
```

For that case the instructions are to copy the entire terminal output, or redirect it to a file, and then open a new issue with the terminal output and the obfuscated file and any config you used. Two prohibitions follow, and the second is unusual enough to be worth repeating: please do not include screenshots or partial output from the deobfuscator. A screenshot loses the exact node that failed, which is precisely the information the maintainer needs. The final request is the most useful one, since it asks for a reproduction with a smaller input file and a javascript-obfuscator config, which turns an unusable bug into a test case. The repository has a scripts/ directory and a tests/ setup implied by the dev dependencies, so that kind of reduction is the shape of contribution the project wants.

## Licence, package name and the release gap

Three practical details sit outside the feature list and belong in any evaluation. The licence is GPL-3.0-only, declared in the package manifest and with a LICENSE.md file in the repository root, so if you intend to use synchrony inside a proprietary build, that is a conversation to have early. The npm package is named deobfuscator while the project and the command are called synchrony, which is the kind of inconsistency that produces a confusing search result, and it also means the package name is generic enough that you should check the publisher before installing it. The release history shows a gap: v2.4.6 was published on 2026-07-07, after v2.4.5 from 2023-11-09 and v2.4.4 from 2023-10-17. So the current release is two and a half years newer than the one before it, and the last push to the repository was on the same day as that release. The project is under the relative organisation with a single listed author, and the build is a plain esbuild script with typedoc for the API reference, so nothing about the toolchain is exotic.

## Where synchrony is the wrong tool

Four limits are clear from the documentation itself. The first is that the coverage is narrow, and the maintainer says so: this is for javascript-obfuscator output, and for other obfuscation, or for a script that is merely minified, you want a formatter and a scope-aware renamer rather than a fixed sequence of AST visitors. The second is that there is no configuration, so there is no way to tell the tool which transformations to skip, and no way to stop it from applying a transform that makes your particular file worse. The third is the version coupling, which the project is unusually upfront about: because the decoder reproduces the obfuscator's own encoding, an artifact produced by a much older javascript-obfuscator will not decode, and the tool cannot be coaxed into trying. The fourth is the legal and ethical dimension, which the tool itself does not address. Deobfuscating a script to read it is legitimate reverse engineering, and synchrony is a reasonable instrument for that, but the same capability applies to code you have no right to inspect, and the licence being GPL does not settle that question. For the narrow, legitimate case, the tool does one job and the documentation tells you honestly when it cannot.

## Conclusion

Adopt synchrony if you are analysing a JavaScript bundle that was obfuscated with javascript-obfuscator, especially when you need the string table decoded and the code laid out again in a readable form. Do not adopt it as a general purpose JavaScript beautifier or as a way to defeat obfuscation you do not have the right to defeat. Before you file a bug, read the usage note, because the project states plainly that artifacts from old versions of javascript-obfuscator will likely not deobfuscate correctly and asks you to try an older synchrony or another deobfuscator instead of opening an issue, and note the release picture, with v2.4.6 published on 2026-07-07 after a two year gap.

## FAQ

### What is synchrony?

It is a TypeScript JavaScript cleaner and deobfuscator, primarily aimed at output from javascript-obfuscator and obfuscator.io. It ships as a command line tool called synchrony, is published on npm as the deobfuscator package, and is licensed GPL-3.0-only.

### How do I install and run synchrony?

Install it globally with `npm install --global deobfuscator`, or from git with `npm install --global relative/synchrony#master`. Then run `synchrony deobfuscate ./obfuscated.js`, and the cleaned output is written next to the input with a .cleaned.js suffix.

### Does synchrony need configuration?

No. The usage note states there is no user configuration as of yet and that the string decoder works automatically, so the common case is a single command with no plugin file and no reconstructed obfuscator config.

### What should I do if synchrony fails on a file?

The project asks you to copy the entire terminal output, including the Caught an error while attempting to run AST visitor! message and the node it printed, and to open an issue with that output plus the obfuscated file and any config you used. It asks you not to include screenshots or partial output, and to try to reproduce the failure with a smaller input file and a javascript-obfuscator config.

### What licence is synchrony released under?

GPL-3.0-only, declared in the package manifest with a LICENSE.md file in the repository root. The current release is v2.4.6 from 2026-07-07, following v2.4.5 from 2023-11-09.

## Sources

- [License: GPL-3.0](https://github.com/relative/synchrony/blob/master/LICENSE)
- [Project website](https://deobfuscate.relative.im/)
- [README](https://github.com/relative/synchrony/blob/master/README.md)
- [relative/synchrony on GitHub](https://github.com/relative/synchrony)
- [Releases](https://github.com/relative/synchrony/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/relative-synchrony
