# Deobfuscate a single file

> Readable-tier workflow for lone minified snippets or isolated chunks: sourcemap-check, wakaru-normalize, extract, smart-rename, polish --fast, format, and promote a single deliverable without import-graph orchestration.

- Repository: JimLiu/decode-codex
- GitHub: https://github.com/JimLiu/decode-codex
- Human docs: https://grok-wiki.com/public/docs/jimliu-decode-codex-1a3a0c425b33
- Complete Markdown: https://grok-wiki.com/public/docs/jimliu-decode-codex-1a3a0c425b33/llms-full.txt

## Source Files

- `.agents/skills/deobfuscate-javascript/workflows/small-minified.md`
- `.agents/skills/deobfuscate-javascript/workflows/react-vite.md`
- `.agents/skills/deobfuscate-javascript/scripts/sourcemap-check.ts`
- `.agents/skills/deobfuscate-javascript/scripts/extract.ts`
- `.agents/skills/deobfuscate-javascript/scripts/smart-rename.ts`
- `.agents/skills/deobfuscate-javascript/scripts/polish.ts`
- `.agents/skills/deobfuscate-javascript/scripts/apply.ts`

---

---
title: "Deobfuscate a single file"
description: "Readable-tier workflow for lone minified snippets or isolated chunks: sourcemap-check, wakaru-normalize, extract, smart-rename, polish --fast, format, and promote a single deliverable without import-graph orchestration."
---

The `deobfuscate-javascript` skill routes lone pasted snippets and isolated chunks through `workflows/small-minified.md`: a per-chunk workspace under `<target-dir>/.deobfuscate-javascript/<basename>/`, Stage 2 rename and polish only (Stage 1 when `detect.ts` reports obfuscation), and a single semantic deliverable in `<target-dir>/` — no `manifest.json`, `ledger.json`, or `build-import-graph.ts` orchestration.

<Info>
Whole-tree restores (`index.html` plus sibling asset chunks) default to deep mode and import-graph coordination. Use this page only when the input is one file or chunk with no app entry tree.
</Info>

## Scope and completion bar

| Axis | Single-file default |
| --- | --- |
| Scope | One input file; no recursive sibling-chunk restore |
| Depth | Readable tier — meaningful names and reading-aid polish |
| Hard bar | Naming quality throughout (no `buttonValue3`, `contextParam14`) |
| Optional | Stage 3 typed `.tsx`, npm-import resolution, acceptance review (deep mode) |

A valid readable deliverable is a semantic kebab-case public filename (for example `spinner.tsx` exporting `Spinner`, not `spinner-D37df5tU.tsx`) with the `// Restored from <path>` provenance header intact. Types, full npm-import resolution, and the Stage 3 reviewer loop are continuations, not corrections.

## Prerequisites

<Steps>
<Step title="Install skill dependencies">

From the skill directory:

```bash
cd .agents/skills/deobfuscate-javascript
bun install
```

</Step>

<Step title="Choose target and workspace paths">

```bash
INPUT=path/to/chunk.js          # the file to restore
TARGET=restored                 # deliverable root
WS="$TARGET/.deobfuscate-javascript/$(basename "$INPUT" .js)"

mkdir -p "$WS"
cp "$INPUT" "$WS/original.js"
```

Keep the hash suffix in `<basename>` when the source chunk has one (`spinner-D37df5tU` for `spinner-D37df5tU.js`). Intermediates stay in `$WS/`; the final `.tsx` lands directly in `$TARGET/`.

</Step>
</Steps>

## Pipeline overview

```mermaid
flowchart TD
  subgraph preflight [Preflight]
    SM[sourcemap-check.ts]
    DT[detect.ts]
  end
  subgraph stage1 [Stage 1 — only if obfuscated]
    S1[deobfuscate.ts]
  end
  subgraph stage2 [Stage 2 — readable tier]
    WK[wakaru-normalize.ts]
    PL[polish.ts --rename --fast --format]
    HN[Hand-name residue]
  end
  subgraph deliver [Deliver]
    PR[Promote to TARGET/]
  end
  SM -->|no usable .map| DT
  SM -->|map found| MAP[Recover via sourcemap — stop rename pipeline]
  DT -->|obfuscated| S1
  DT -->|minified only| WK
  S1 --> WK
  WK --> PL
  PL --> HN
  HN --> PR
```

Byte-rewriting steps (`wakaru-normalize`, Stage 1) invalidate symbol offsets. After normalization, run extract, rename, and polish against `$WS/normalized.js`, not `$WS/original.js`. Keep `--source` pointed at the original input path for the provenance header.

## Step 0 — Preflight checks

### Sourcemap check (always first)

```bash
bun scripts/sourcemap-check.ts "$WS/original.js"
```

<ResponseExample>

```text
✓ sourcemap detected
  sourceMappingURL = chunk.js.map
  map file         = /path/to/chunk.js.map
  3 original source(s):
    src/Button.tsx
    ...

Adjacent chunk.js.map found with 3 original source(s). Recover from sourcemap instead of renaming — preserves original variable names, comments, and file structure.
```

</ResponseExample>

When `mapFound` is true, recover originals with `source-map-explorer` or the `.map` `sourcesContent` — that path beats any rename pipeline. Skip `wakaru-normalize` when a usable map exists.

<ParamField body="--out" type="string">
Write a JSON report instead of stderr-only summary.
</ParamField>

Exit codes: `0` when a map is found; `1` when no map is detected (proceed with rename pipeline).

### Obfuscation detect

```bash
bun scripts/detect.ts "$WS/original.js"
```

If the input is packed, encoded, or Obfuscator.IO-shaped (`_0x` arrays, Packer wrappers, hex walls), run Stage 1 first via the obfuscated-input workflow, then return to this pipeline. Pure minification skips Stage 1.

## Step 1 — Wakaru normalization

```bash
bun scripts/wakaru-normalize.ts "$WS/original.js" \
  -o "$WS/normalized.js" --level standard
```

Wakaru recovers ES6 classes, async/await, optional chaining, destructuring, TS enums, and similar transpiler output that polish alone does not restore. The wrapper auto-skips (passthrough copy, exit `0`) when `@wakaru/cli` is unavailable — `normalized.js` always exists for the next step.

<ParamField body="--level" type="string" default="standard">
`minimal` for fidelity-critical code; `aggressive` only with verification.
</ParamField>

<Warning>
Wakaru is not a deobfuscator and not a semantic renamer. Run Stage 1 before wakaru on obfuscated input.
</Warning>

## Step 2 — One-shot rename and polish

The readable-tier default combines mechanical rename, reading-aid polish, and formatting in one command:

```bash
bun scripts/polish.ts "$WS/normalized.js" --rename --fast \
  --source "$INPUT" --out "$WS/draft.tsx" --format
```

<ParamField body="--rename" type="boolean">
Runs `smart-rename.ts` then `apply.ts` as a pre-step before polish passes.
</ParamField>

<ParamField body="--fast" type="boolean">
Readable-tier profile. Skips import-resolution tail: `react-shim-elim`, `resolve-npm-imports`, `npm-cjs-shim-elim`, `dead-shim-elim`. Drop `--fast` for deep-mode compilable imports.
</ParamField>

<ParamField body="--source" type="string" required>
Original input path for the `// Restored from <path>` provenance header. Polish input is normalized output; provenance still references the pristine chunk.
</ParamField>

<ParamField body="--format" type="boolean">
Runs `format.ts` on `--out` (requires `--out`; uses `.prettierignore` only so gitignored `restored/` trees still format).
</ParamField>

### What `--fast` polish runs

| Step | Purpose |
| --- | --- |
| `strip-react-compiler` | Remove React Compiler `cache[N]` memoization |
| `simplify` | Fold constants, unwrap `(0, fn)(...)`, collapse templates |
| `jsx-runtime` | Convert `jsx`/`jsxs`/`Fragment` calls to JSX syntax |
| `inline-defaults` | Inline destructure defaults |
| `normalize-exports` | Collapse single-use export aliases |

### What `smart-rename` covers mechanically

`smart-rename.ts` applies deterministic heuristics before hand-naming:

- React component `props` and DOM `event` parameters
- `.map`/`.reduce`/`.forEach` callback parameters (`item`, `index`, `accumulator`)
- Promise `.then`/`.catch` handlers (`value`, `error`)
- Hook return aliases (`useIntl()` → `intl`, `useRouter()` → `router`)
- Destructure prop aliases (`{ foo: e }` → `foo`)
- `clsx`/`classNames`/`cn` call results → `className`

Cryptic names match `/^_?[a-zA-Z]{1,2}$/` — single or double letters and short letter-digit forms.

## Step 3 — Hand-name the residue

Read `$WS/draft.tsx` and name what `smart-rename` could not reach. This is the only hard bar at readable depth:

- Program-scope domain nouns — top-level exports and the concepts they model
- Lookup-table object keys — `{0:"sm",1:"md"}` → `buttonSizeClassNames`
- JSX component aliases — lowercase `<r>` that is really `<Spinner>`

Continue into function bodies until single-letter density is low. Refuse mechanical fallback names (`buttonValue3`, `contextParam14`).

### Second rename pass

When residue remains, re-extract on the draft and apply a focused `renames.json`:

```bash
bun scripts/extract.ts "$WS/draft.tsx" \
  --out "$WS/symbols.json" --only-cryptic --min-refs 2
# Write $WS/renames.json — keys are "name@declStart" ids from extract
bun scripts/apply.ts "$WS/draft.tsx" "$WS/renames.json" --out "$WS/draft.tsx"
bun scripts/format.ts "$WS/draft.tsx"
```

<ParamField body="--only-cryptic" type="boolean">
Keep `_0x…`, single/double-letter, and letter+digit bindings only.
</ParamField>

<ParamField body="--min-refs" type="number">
Drop bindings referenced fewer than N times (scratch locals).
</ParamField>

`apply.ts` renames scope-aware: each `name@offset` id maps to one binding. Collisions get `_`-prefixed safe names; reserved words are normalized via `toIdentifier()`.

## Step 4 — Promote the deliverable

Copy or write the finished file to `<target-dir>/` with a semantic kebab filename:

```bash
cp "$WS/draft.tsx" "$TARGET/spinner.tsx"
```

<Check>
Deliverable checklist: meaningful names throughout, semantic kebab filename (no hash suffix), provenance header present, Prettier-formatted output.
</Check>

Do not leave the public file inside `.deobfuscate-javascript/`. That directory holds intermediates only (`original.js`, `symbols.json`, `renames.json`, `draft.tsx`).

:::files
restored/
├── spinner.tsx                              # final deliverable
└── .deobfuscate-javascript/
    └── spinner-D37df5tU/                    # workspace (gitignore-friendly)
        ├── original.js
        ├── normalized.js
        ├── symbols.json
        ├── renames.json
        └── draft.tsx
:::

## Sub-cases (branch only when applicable)

<AccordionGroup>
<Accordion title="React / Vite / Rollup component chunk">

No separate recipe. `--fast` polish already runs `jsx-runtime` recovery — `jsxRuntime.jsx("svg", …)` becomes JSX. See the react-vite workflow note: single components use this same path.

</Accordion>

<Accordion title="Obfuscated input (Packer, _0x arrays, encoded strings)">

Run Stage 1 (`detect` → `deobfuscate.ts`) before wakaru and polish. Stage 1 rewrites invalidate pre-deobfuscation `renames.json` ids.

</Accordion>

<Accordion title="≥ 500 KB or > 1000 cryptic symbols">

Use `plan.ts` batching — one rename pass will not fit context budgets. Filter large extracts: `--only-cryptic --min-refs 3 --top 200 --compact`.

</Accordion>

<Accordion title="Webpack id:(e,t,n)=>{} bundle">

Pre-split with `webcrack`, then restore each module through this single-file flow.

</Accordion>

<Accordion title="≥ 3 exports or registry object">

Optional deep-tier step: split into a directory via multi-export-bundle workflow. Readable tier may leave the file flat.

</Accordion>
</AccordionGroup>

## Troubleshooting

| Symptom | Likely cause | Recovery |
| --- | --- | --- |
| `extract.ts` warns about `sourceMappingURL` | Map comment present but not checked | Re-run `sourcemap-check.ts`; prefer map recovery |
| `polish[format]: prettier failed` | Prettier missing or path issue | Install prettier; ensure `--out` is set with `--format` |
| Names still cryptic in function bodies | Stopped after program-scope only | Hand-name bodies; run second extract/apply pass |
| `renamed 0 symbol(s) (N id(s)… had no matching binding)` | Stale `renames.json` after byte rewrite | Re-extract from current draft; rebuild ids |
| JSX still `jsx()` calls | Non-standard runtime binding names | Inspect polish report; check `jsx-runtime` step stats |
| Wakaru stderr `skipped` | `@wakaru/cli` unavailable | Passthrough is valid; pipeline continues on `original.js` bytes in `normalized.js` |

<Note>
Optional E1 naming self-review (meaningful identifiers, no fallback names, low single-letter density) adds confidence at readable depth. The full Stage 3 acceptance review (E1–E4) is deep mode only.
</Note>

## Readable vs deep for a single file

| Concern | Readable (`--fast`) | Deep (drop `--fast`) |
| --- | --- | --- |
| Naming | Required | Required |
| JSX recovery | Yes | Yes |
| npm import resolution | Skipped | `resolve-npm-imports` runs |
| Shim elimination | Skipped | `react-shim-elim`, `npm-cjs-shim-elim`, `dead-shim-elim` |
| TypeScript types | Optional | Stage 3 rewrite |
| Acceptance review | Optional E1 | Full E1–E4 loop |

## Related pages

<CardGroup>
<Card title="Restoration pipeline" href="/restoration-pipeline">
Stage 1, Stage 2, and Stage 3 boundaries; readable vs deep depth; single-file vs whole-tree scope.
</Card>
<Card title="Workspace and output" href="/workspace-and-output">
Per-chunk `$WS` staging, promote bar, kebab filenames, and provenance headers.
</Card>
<Card title="Handle obfuscated input" href="/obfuscated-input">
Stage 1 ordering when `detect.ts` reports packed or encoded input.
</Card>
<Card title="Stage 2 scripts reference" href="/stage-2-scripts">
CLI signatures for `sourcemap-check`, `wakaru-normalize`, `extract`, `smart-rename`, `apply`, and `polish` flags.
</Card>
<Card title="Quality bar and anti-patterns" href="/quality-bar-and-anti-patterns">
Naming anti-patterns, completion criteria, and when to refuse mechanical fallback names.
</Card>
<Card title="Full tree restoration" href="/full-tree-restoration">
Default path when input is `index.html` plus an asset tree instead of one chunk.
</Card>
</CardGroup>
