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

deobscura

v1.0.4

Published

Universal JS deobfuscation — AST-level preprocessing for LLM-assisted reverse engineering

Readme

deobscura

Deobscura is an AST-based JavaScript deobfuscation framework, purpose-built as a data preprocessing tool for LLM-driven reverse engineering.

  • Universal. No obfuscator detection, no signature matching. obfuscator.io, JSVMP, webpack bundles, custom obfuscators — all treated the same.
  • LLM-oriented. Structure reports, call graphs, string alerts, compact index, function categorization — output designed for LLM consumption, not just human readability.

Quick Start

npm install
npm link
deobscura init               # create deobscura.config.js
# edit deobscura.config.js — set input path and options
deob                         # run with config
deobscura -c other.config.js      # explicit config path

Configuration

deobscura.config.js format:

module.exports = {
  input: "src/main.js",             // file, directory, or array
  // output: "out/",                // optional — auto-derived from input
  split: false,                     // per-function files
  metrics: false,                   // HTML readability report
  md: true,                         // 0-prompt.md + 1-structure.md
  index: true,                      // 2-index.txt
  tier: 3,                          // 1=alerts+hotspots, 2=+callees, 3=all
  fold: false,                      // collapse mechanical functions
  denoise: [                        // alert denoising rules (optional)
    { match: "regex-source", label: "Label", severity: "low" },
  ],
};

| Field | Type | Default | Description | |-------|------|---------|-------------| | input | string \| string[] | required | JS file(s) or directory | | output | string | auto | Output directory | | split | boolean | false | Write each function to separate file | | metrics | boolean | false | Generate HTML before/after report | | md | boolean | true | Generate 0-prompt.md + 1-structure.md | | index | boolean | true | Generate 2-index.txt | | tier | 1 \| 2 \| 3 | 3 | Output filtering level | | fold | boolean | false | Collapse mechanical functions to comments | | denoise | DenoiseRule[] | defaults | Alert denoising rules |

See docs/llm-output-tuning.md for detailed guidance on tier and fold.

Output Files

All output goes into a directory. Files are numbered to guide LLM agents through a natural reading order:

output.deob/
├── 0-prompt.md       ← LLM analysis entry (architecture, alerts, top 5, reading path)
├── 1-structure.md    ← call graph, hotspots, alert traces, naming convention
├── 2-index.txt       ← function catalog with line numbers for jump-reading
├── main.js           ← deobfuscated code — jump-read only, do not read first
└── metrics.html      ← readability report (metrics: true)

Directory input recursively processes nested subdirectories. A top-level 0-prompt.md provides the cross-file entry point. summary.md aggregates cross-file analysis.

Agent-Oriented Navigation

Output files are designed for LLM agents (Claude Code, Codex, etc.) to discover and follow naturally:

| Step | File | Purpose | |------|------|---------| | 1 | 0-prompt.md | "What to read and why" — architecture, alerts, top 5 functions, reading path | | 2 | 1-structure.md | "What this file contains" — domain, hotspots, alerts, call graph, naming | | 3 | 2-index.txt | "Where things are" — function catalog with line numbers for jump-reading |

Single file: Agent ls sees 0-, 1-, 2- in sort order → reads in sequence → jump-reads main.js by line number.

Directory: Agent ls sees top-level 0-prompt.md → picks interesting files → enters subdirectory → follows 0-1-2- → jump-reads.

Pipeline

| Step | Pass | Description | |------|------|-------------| | 0 | sanitizeReservedWords | Rename reserved-word identifiers (let, default, delete…) | | 1 | processAllFunctions | Collect all function nodes, process innermost-first | | 2 | extractTopLevelIIFEs | Extract top-level IIFEs from comma chains | | 3 | hoistDeclarations | Import→top, export→bottom; var/let/const/fn to top of scope | | 4 | extractInlineFunctions | Lift embedded function expressions to top level | | 5 | simplify | Fold constants, simplify booleans, normalize hex strings | | 6 | normalizeShortCircuit | Convert A\|\|Bif (!A) { B } | | 7 | expandSequences | Break comma chains into independent statements | | 8 | normalizeShortCircuit | Re-normalize after expansion | | 9 | eliminateDeadCode | Remove unreachable code, if(false) branches | | 10 | inlineReadOnlyProperties | Replace cfg.PROP with literal values | | 10b | inlineConstObjects | Replace cfg.timeout with 5000 when cfg is const | | 11 | removeUnusedHelpers | Delete unreferenced function declarations | | 12 | simplifyRedundantConditions | if(a) return true; return falsereturn !!a | | 13 | inlinePureWrappers | Remove functions that are just return call(args) | | 14 | sortByCallTree | Topological sort: callees before callers | | 15 | inlineSingleCallerFns | Inline functions called from exactly one place | | 16 | normalizeSyntax | ~arr.indexOfarr.includes, ~~xMath.trunc | | 17 | extractInlineFunctions | Re-extract exposed inline functions | | 18 | annotateAlerts | Inject [Label] comments for security-relevant patterns | | 19 | sanitizeReservedWords | Re-sanitize (pipeline may introduce new reserved words) | | 20 | pushDataToBottom | Move DATA-heavy functions to end with separator |

Alert System

Three detection layers:

  1. String-based (ALERT_PATTERNS): regex scan of string literals — API endpoints, tokens, crypto, eval, storage, DOM sinks, network, fingerprints, cookies
  2. AST-based: detect debugger, eval(), new Function() via AST node types
  3. Denoise: configurable rules to downgrade false-positive alerts

Severity levels: critical > high > medium > low > info

Function Categories

Functions are automatically categorized:

| Category | Detection | Example | |----------|-----------|---------| | data | Large hex string arrays | String lookup tables | | core | Original function names | Entry points | | framework | Vue/React/Regenerator patterns | Framework internals | | network | fetch/xhr/axios patterns | HTTP client code | | crypto | sign/encrypt/hash patterns | Cryptographic operations | | websocket | WebSocket patterns | WS connection code | | polyfill | core-js/ToPrimitive patterns | ES polyfills | | callback | _S_return_* names | Extracted callbacks | | branch | _S_*_if/_try/_catch | Extracted branches | | other | None of the above | Uncategorized |

Naming Convention

All extracted sub-functions follow: _S_<parent>_<seq>_<hint>

| Component | Meaning | |-----------|---------| | _S_ | Prefix for extracted sub-functions | | <parent> | Parent function name, method name, or lXXXX for anonymous | | <seq> | Two-digit extraction order | | <hint> | if, else, try, catch, fn, iife_body... | | _L<line> | (Collision only) Source line disambiguator |

When two sub-functions share the same parent + seq + hint, _L<source line> is appended:

_S_l251_01_try          ← first try block (unique, no _L needed)
_S_l251_L1364_01_try    ← collision! disambiguated by source line 1364
_S_l251_L1548_01_try    ← another collision, source line 1548

Output Examples

Comma operator expansion + inline function extraction

// Input
function a0_0x5465(_0x147aca,_0x1c469e){var _0x477d9d=a0_0x1cb6();return (a0_0x5465=function(_0x593f2d,_0x4d5e1e){var _0x2a8c=_0x477d9d[_0x593f2d];return _0x2a8c?_0x2a8c(_0x4d5e1e,_0x147aca):_0x4d5e1e}),a0_0x5465(_0x147aca,_0x1c469e)}

// Output
function a0_0x5465(_0x147aca, _0x1c469e) {
  var _0x477d9d = a0_0x1cb6();
  a0_0x5465 = _S_return_1_fn;
  return a0_0x5465(_0x147aca, _0x1c469e);
}
function _S_return_1_fn(_0x593f2d, _0x4d5e1e, _0x477d9d, _0x147aca) {
  var _0x2a8c = _0x477d9d[_0x593f2d];
  return _0x2a8c ? _0x2a8c(_0x4d5e1e, _0x147aca) : _0x4d5e1e;
}

return (a=fn, b) → two statements; inline function lifted to _S_return_1_fn with external refs as params.

Short-circuit polyfill → if block

// Input
"undefined"==typeof Element||Element.prototype.addEventListener||(u=[],Ao=function(n,t){...})

// Output
if ("undefined" != typeof Element && !Element.prototype.addEventListener) {
  u = [];
  Ao = function (n, t) { ... };
}

A||B||(C,D)if(!A&&!B){C;D;}

Hex string normalization

// Input
var _0xa1b2 = ["\x68\x74\x74\x70\x73\x3a\x2f\x2f\x61\x70\x69"];

// Output
var _0xa1b2 = ["https://api"];

\x escape sequences decoded to readable strings.

Directory Structure

deob.js               ← CLI entry point, config parsing
scripts/
  config.js           ← User-facing config (parser, t, DEFAULT_DENOISE)
  constants.js        ← Internal constants (RESERVED, GLOBALS, ALERT_PATTERNS, SKIP_KEYS, etc.)
  ast-utils.js        ← AST walkers, pattern detectors, clone
  scope.js            ← Variable scope & external reference analysis
  naming.js           ← Sub-function naming (_S_ prefix, collision detection)
  emit.js             ← Sub-function AST node creation, safeParam
  extract.js          ← Syntactic extraction (IIFE, try/catch, loop, if/else, switch)
  traverse.js         ← Innermost-first function collection
  wrapper.js          ← Top-level IIFE extraction from comma chains
  pipeline.js         ← 20-step pipeline orchestration
  passes/
    simplify.js       ← Constant folding, boolean, string, hex, short-circuit, syntax
    dead-code.js      ← Dead code elimination, unused helpers, DATA separation
    inline.js         ← Property/wrapper/single-caller inlining
    declarations.js   ← Hoisting, sanitization, alert annotation, call-tree sorting
    index.js          ← Re-exports all pass modules
  structure/
    analyze.js        ← AST analysis, domain classification, function categorization
    report.js         ← Markdown/prompt generation, reading guide
    index-gen.js      ← Compact index.txt generation
    tier.js           ← Tier filtering (1=alerts, 2=+callees, 3=all)
    cross-file.js     ← Multi-file summary and cross-readme
    index.js          ← Re-exports all structure modules
  types/              ← TypeScript declarations (.d.ts) — no runtime code
    index.d.ts        ← Entry point
    config.d.ts       ← DeobConfig, DenoiseRule
    analysis.d.ts     ← FunctionMeta, Alert, StructureReport
    ast.d.ts          ← ASTNode, ExtractResult, PassFunction
    constants.d.ts    ← AlertPattern, Thresholds, OutputFiles
    passes.d.ts       ← Pass function signatures
  metrics.js          ← Before/after readability metrics, HTML report
  index.js            ← Public API re-export

API

const { main } = require("./scripts");
main({ input: "obfuscated.js", output: "out/", split: true });

License

ISC