npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

restringer

v2.2.0

Published

Deobfuscate Javascript with emphasis on reconstructing strings

Readme

REstringer

Node.js CI Downloads npm version

A JavaScript deobfuscation tool that reconstructs strings and simplifies complex logic.

REstringer automatically detects obfuscation patterns and applies targeted deobfuscation techniques to restore readable JavaScript code. It handles various obfuscation methods while respecting scope limitations and maintaining code functionality.

Contact: For questions and suggestions, open an issue or find me on LinkedIn - Ben Baryo


Table of Contents


Features

  • Obfuscation detection via Obfuscation Detector
  • 40+ modules split into safe (no execution) and unsafe (sandboxed eval) passes
  • Swappable sandbox backends: isolated-vm or subprocess evaluation under Node / Deno / Bun
  • Processors for specific stacks (obfuscator.io, Caesar Plus, etc.)
  • String recovery, dead-node cleanup, control-flow cleanup where patterns match
  • But the major feature is the modularity that allows one to create bespoke deobfuscators

Installation

Requirements

You can run REstringer with only Node.js (v22+). The default sandbox will be Node itself running in a spawned process. You can manually install [isolated-vm](https://www.npmjs.com/package/isolated-vm) with npm i isolated-ve which will then be used as the default sandbox. Deno and Bun can also be used (see Sandbox backends).

Global Installation (CLI)

npm install -g restringer

Local Installation (Module)

npm install restringer

Optional isolated-vm Backend

isolated-vm is no longer installed by default. Install it only if you plan to run:

npm install isolated-vm

Development Installation

git clone https://github.com/ctrl-escp/restringer.git
cd restringer
npm install

Usage

Command-Line Usage

Usage: restringer input_filename [-h] [-c] [-q | -v] [-m M] [-o [output_filename]]
                  [--sandbox <name>] [--sb-exec <path>] [--sb-timeout <ms>] [--sb-memory-limit <mb>]

positional arguments:
  input_filename                  The obfuscated JavaScript file

optional arguments:
  -h, --help                      Show this help message and exit
  -V, --version                   Show version number and exit
  -c, --clean                     Remove dead nodes after deobfuscation (unsafe)
  -q, --quiet                     Suppress output to stdout
  -v, --verbose                   Show debug messages during deobfuscation
  -m, --max-iterations M          Maximum deobfuscation iterations (must be > 0)
  -o, --output [filename]         Write output to file (default: <input>-deob.js)
  --sandbox <name>                Sandbox to use: isolated-vm, node, deno, or bun (default: current runtime)
  --sb-exec <path>                Path to the sandbox runtime executable
  --sb-timeout <ms>               Sandbox execution timeout in milliseconds (default: 1000)
  --sb-memory-limit <mb>          isolated-vm memory limit in MB (default: 128)

Examples

Print to stdout (default sandbox follows whatever started the CLI: same Node/Deno/Bun binary when REstringer can detect it):

restringer obfuscated.js

Write to a file, limit iterations, tweak verbosity:

restringer obfuscated.js -o clean-code.js
restringer obfuscated.js -v -m 10 -o output.js
restringer obfuscated.js -q -o output.js

--clean strips dead nodes afterward (can change behavior; treat as unsafe):

restringer obfuscated.js -c -o output.js

Sandbox flags:

restringer obfuscated.js --sandbox=node
restringer obfuscated.js --sandbox=deno
restringer obfuscated.js --sandbox=node --sb-exec=/opt/node-v22/bin/node
restringer obfuscated.js --sandbox=deno --sb-timeout=400
restringer obfuscated.js --sandbox=isolated-vm --sb-memory-limit=64

Sandbox backends

Unsafe passes evaluate snippets to fold literals and builtins. There is a process provider (spawn Node, Deno, or Bun) and isolated-vm, which uses a V8 isolate via the npm package.

If you skip --sandbox, REstringer aligns process with the host runtime (Node / Deno / Bun) and reuses the current executable path when it can. --sandbox node or bun uses that binary from PATH unless --sb-exec says otherwise. --sandbox deno picks Deno and sets strict: true; only Deno can honor that, so enabling strict evaluation on Node or Bun fails fast. The CLI never accepts --sandbox process; use node, deno, bun, or isolated-vm.

For process with Node, the evaluated binary must be at least 22.20.0 (the subprocess uses hardened Node flags). Deno and Bun only need to be installed discoverably. isolated-vm remains optional: npm install isolated-vm when you need it.

--sb-timeout maps to sandbox.options.timeout (default 1000 ms per eval), --sb-exec to executablePath, --sb-memory-limit to memoryLimit (128 MB default; isolate sizing only applies to isolated-vm). Strict Deno from code:

await preloadSandboxProvider({
  provider: 'process',
  options: {runtime: 'deno', strict: true},
});

Importing the package loads the sandbox glue and seeds the default process setup, so simple scripts usually need nothing else. If you switch to isolated-vm or another registered provider, call await preloadSandboxProvider({...}) before deobfuscate(). registerSandboxProvider is there for custom implementations.

Neither path looks like a full browser or unrestricted Node: snippets run after dropping Math.random and Date, and subprocess engines scrub globals such as fetch, WebAssembly, and navigator (isolated-vm has its own small blocklist too). Values coming back must serialize (no Promises, no cycles). Node workers start with --permission and related flags; strict Deno wraps the engine with --deny-read, --deny-write, --deny-net, --deny-run, --deny-env. That cuts down accidental damage during deobfuscation; it is not a guarantee against malicious code.

Module Usage

Basic Example

import {REstringer} from 'restringer';

const obfuscatedCode = `
const _0x4c2a = ['hello', 'world'];
const _0x3f1b = _0x4c2a[0] + ' ' + _0x4c2a[1];
console.log(_0x3f1b);
`;

const restringer = new REstringer(obfuscatedCode, {
  clean: false,
  detectObfuscationType: true,
  maxIterations: 500,
  normalize: true,
});

if (restringer.deobfuscate()) {
  console.log(restringer.script);
} else {
  console.log('No changes applied.');
}

Process-backed Node runtime:

import {REstringer, preloadSandboxProvider} from 'restringer';

await preloadSandboxProvider({provider: 'process'});

const restringer = new REstringer(obfuscatedCode, {
  sandbox: {
    provider: 'process',
    options: {
      runtime: 'node',
    },
  },
});

restringer.deobfuscate();

With isolated-vm installed:

import {REstringer, preloadSandboxProvider} from 'restringer';

await preloadSandboxProvider({provider: 'isolated-vm'});

const restringer = new REstringer(obfuscatedCode, {
  sandbox: {
    provider: 'isolated-vm',
  },
});

restringer.deobfuscate();

Advanced Usage

Custom Deobfuscators

import {applyIteratively} from 'flast';
import {safe, unsafe} from 'restringer';

// Import specific modules
const normalizeComputed = safe.normalizeComputed.default;
const removeRedundantBlockStatements = safe.removeRedundantBlockStatements.default;
const resolveDefiniteBinaryExpressions = unsafe.resolveDefiniteBinaryExpressions.default;
const resolveLocalCalls = unsafe.resolveLocalCalls.default;

let script = 'your obfuscated code here';

// Define custom deobfuscation pipeline
const customModules = [
  resolveDefiniteBinaryExpressions,  // Resolve literal math operations
  resolveLocalCalls,                 // Inline function calls
  normalizeComputed,                 // Convert obj['prop'] to obj.prop
  removeRedundantBlockStatements,    // Clean up unnecessary blocks
];

// Apply modules iteratively
script = applyIteratively(script, customModules);
console.log(script);

Targeted Processing

Use candidate filters to target specific nodes:

import {unsafe} from 'restringer';
import {applyIteratively} from 'flast';

const {resolveLocalCalls} = unsafe;

function resolveGlobalScopeCalls(arb) {
  // Only process calls in global scope
  return resolveLocalCalls(arb, n => n.parentNode?.type === 'Program');
}

function resolveSpecificFunctions(arb) {
  // Only process calls to functions with specific names
  return resolveLocalCalls(arb, n => {
    const callee = n.callee;
    return callee.type === 'Identifier' && 
           ['decode', 'decrypt', 'transform'].includes(callee.name);
  });
}

const script = applyIteratively(code, [
  resolveGlobalScopeCalls,
  resolveSpecificFunctions
]);

Custom Method Integration

Replace or customize built-in methods:

import fs from 'node:fs';
import {REstringer} from 'restringer';

const code = fs.readFileSync('obfuscated.js', 'utf-8');
const restringer = new REstringer(code, {
  detectObfuscationType: false,
});

// Find and replace a specific method
const targetMethod = restringer.unsafeMethods.find(m => 
  m.name === 'resolveLocalCalls'
);

if (targetMethod) {
  let processedCount = 0;
  const maxProcessing = 5;
  
  // Custom implementation with limits
  const customMethod = function limitedResolveLocalCalls(arb) {
    return targetMethod(arb, () => processedCount++ < maxProcessing);
  };
  
  // Replace the method
  const index = restringer.unsafeMethods.indexOf(targetMethod);
  restringer.unsafeMethods[index] = customMethod;
}

restringer.deobfuscate();

Architecture

Module Categories

Safe Modules (src/modules/safe/):

  • Perform transformations without code evaluation
  • No risk of executing malicious code
  • Examples: String normalization, syntax simplification, dead code removal

Unsafe Modules (src/modules/unsafe/):

  • Use provider-backed sandbox execution for dynamic analysis (see Sandbox backends)
  • Can resolve complex expressions and function calls
  • Support isolated-vm and local process runtimes today
  • Reserve Docker and iframe providers as extension points for later adapters

Processing Pipeline

Detect obfuscation flavor; run preprocessors; iterate safe then unsafe modules; postprocessors; optional normalization / --clean.

Processor Architecture

Processors are separate match/transform steps tuned for particular tools; see src/processors/README.md.


Development

Project Structure

  • src/modules/safe/ - safe transforms (no evaluation)
  • src/modules/unsafe/ - eval-backed transforms
  • src/modules/utils/ - shared helpers
  • src/processors/ - obfuscation-specific processors
  • src/restringer.js - main class
  • tests/ - test suites
  • docs/ - contributor docs

Running Tests

# Quick test suite (without testing against samples)
npm run test:quick

# Watch mode for development (quick tests)
npm run test:quick:watch

# Full test suite with samples
npm test

Contributing

See docs/CONTRIBUTING.md for setup, style notes, and PR expectations.


Resources

Documentation

Related Projects

Research & Blog Posts

The REstringer Tri(b)logy:

Additional Resources:

Community


License

This project is licensed under the MIT License.