# RzWeb puts Rizin behind a browser tab and a Web Worker

> RzWeb is a browser interface for Rizin compiled to WebAssembly, where analysis runs entirely on the device, each open binary is a context backed by IndexedDB, and the decompiler view falls back to pseudo code when the bundled build has no decompiler. It is a good fit for quick triage of a binary you are allowed to open, and a poor fit for anything that needs ptrace or a persistent server side core.

**IndAlok/rzweb** — A complete browser-based reverse engineering platform built on Rizin, running entirely client-side via WebAssembly.

- Repository: https://github.com/IndAlok/rzweb
- Website: https://rizin.pages.dev
- Stars: 762 · Forks: 72
- Language: TypeScript
- License: LGPL-3.0
- Published: 2026-10-21 · Updated: 2026-10-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/indalok-rzweb

## The whole analysis runs in the tab, and nothing is uploaded

RzWeb is a browser UI for Rizin compiled to WebAssembly. The pitch is one sentence: you drop a binary in, and the analysis stays on the machine.

The privacy claim is stated in terms of what is used: files stay on the device, and the only storage involved is WebAssembly memory and IndexedDB. Nothing is uploaded. For a reverse engineering tool, that matters more than it would for most applications, because the binaries people analyse are frequently the thing they are not allowed to send anywhere. A local web application that never opens a socket to a server is a genuinely different proposition from a desktop tool on a laptop that happens to be offline.

The performance model is a Web Worker. Rizin runs in a worker so the page does not freeze while the WASM loads, and the documentation is specific about the consequence: `aaa` still owns that worker until the command returns. That is the mechanism behind the first listed limit, that the worker cannot take other commands until the analysis returns.

There is also an MCP server in the `mcp/` directory. It uses the same wasm, with contexts on disk, and any MCP client can be pointed at it. So the same engine that runs in a browser tab can also be driven programmatically, which is the part that turns this from a demo into something you can script.

## Every open binary is a context, and one tab is one core

The unit of work in RzWeb is a context. Each open binary has one, and the context holds the artifact bytes, a current `.rzdb` database and a short revision history, all in IndexedDB. From Home you can resume a context by hash or start a new one on the same file.

The lifecycle rule is strict and simple: one tab is one worker, and closing the tab kills that core. That is worth understanding before you plan a workflow around it, because it means the analysis state lives in the tab while the tab is alive and in IndexedDB only to the extent that you snapshot or export it. A context you have not snapshotted is a context you lose when the tab closes.

There are two ways to keep a copy. The snapshot feature and the `RZWEBPRJ` export, which embeds the binary itself. History applies an older `.rzdb` onto the open file, so a session can be rolled back to an earlier state of the analysis rather than re-run from scratch.

The restore path has a version dependency, and it is documented as a limit rather than hidden. Restore needs `rzweb_apply_project` in the wasm you load; older distributions fall back to `Po` and show a notice. So a context exported today may restore differently on a different build, and the fallback is a command that applies the project without the newer helper, with a visible notice that tells you the degraded path was taken.

## The decompiler view degrades to pseudo code unless you build a special wasm

This is the sharpest edge in the project and the first thing to check before you plan around the decompiler.

The wasm build comes from rzwasi, a separate repository. The stock GitHub Pages build has neither jsdec nor rz-ghidra. The decompiler view therefore uses `pdg`, `pdd` or `pdc` when the build ships one, and otherwise falls back to `pdf` with `asm.pseudo`.

The practical meaning is that on the default build you get disassembly rendered as pseudo code, which reads better than raw assembly but is not decompilation. It is not the same artefact, and anyone who has used a decompiler will notice the difference in structure, in recovered types and in how much has to be read by hand.

If you want a real decompiler, jsdec is an opt-in rzwasi build, and you point `VITE_WASM_BASE_URL` at that distribution. The documentation includes a warning not to make it the default Pages build, which tells you something about why: the special build is for testing a local distribution, not for what the public site serves. The environment variable is left unset by default, and the default wasm is the GitHub Pages rzwasi build.

The formats you can open are whatever the bundled Rizin build opens, which the documentation lists as ELF, PE, Mach-O and raw dumps.

## The view set is wide, but it is all one Rizin core underneath

The feature list is longer than a browser toy usually is, and it is worth reading as a list of Rizin views rather than as new functionality. Tabs each with their own worker, a terminal with catalog autocomplete, disassembly, decompiler, xrefs, control flow graph, hex dump, strings, imports, exports, sections and binary info.

The interactive parts are built on well known libraries. The terminal is xterm with the clipboard, fit, search, web links and webgl addons. The control flow graph is cytoscape with the dagre layout plugin, which is the usual pairing for a layered graph in a web UI. The palette is built on cmdk and opens on `Ctrl` or `Cmd` plus `K`, with view shortcuts on `Alt` plus `1` through `9`. UI primitives come from Radix, and data fetching and lists go through TanStack Query and TanStack Virtual.

Two capabilities are less common in a browser tool. The scripts view accepts rizin commands or JavaScript with a synchronous `rz` API, which is how you script analysis against the live core. And there are hex and write-mode patches, after which you can save the file back out. That last one is a real capability and also a real risk: a browser application that can write to a binary you loaded is a place where a mistaken command does lasting damage, and the write path is worth understanding before you rely on it.

Themes are supported, and the terminal and the graph follow the active one, which is a small detail that tells you the state is shared across those views rather than duplicated.

## Running it locally needs Node 22, and Ubuntu's package will not do

The local setup is documented precisely because a very specific failure follows from the wrong Node version.

The requirement is Node 20.19+ or 22.12+. Ubuntu's apt `nodejs` is 18, and on that version Vite dies with `crypto.hash is not a function`. The repository pins 22 in `.nvmrc`, and there is a `.node-version` file alongside it.

If `node -v` still prints v18, the documented fix installs a local Node 22 build under your home directory and puts it first on the path:

```bash
mkdir -p "$HOME/.local"
curl -fsSL https://nodejs.org/dist/v22.23.2/node-v22.23.2-linux-x64.tar.xz -o /tmp/node22.tar.xz
tar -xJf /tmp/node22.tar.xz -C "$HOME/.local"
ln -sfn "$HOME/.local/node-v22.23.2-linux-x64" "$HOME/.local/node"
export PATH="$HOME/.local/node/bin:$PATH"
```

The documentation asks you to put that `export` in `~/.bashrc` so new terminals keep it. Then the project itself is started with the usual three commands:

```bash
cd rzweb
node -v
npm install
npm run dev
```

`node -v` must print v22 before you continue, and the app is then served at http://localhost:3000. Leave `VITE_WASM_BASE_URL` unset unless you are testing a local distribution, because the default wasm is the GitHub Pages rzwasi build.

The `postinstall` script runs `node scripts/postinstall.cjs`, so an install step happens after `npm install` fetches the dependencies. The other scripts are the standard Vite and TypeScript set: `dev` is `vite`, `build` is `tsc -b && vite build`, `preview` is `vite preview`, `lint` is `eslint src --max-warnings 0`, `typecheck` is `tsc --noEmit`, and `test` is `vitest run`.

## Vitest cannot load the wasm, so the test story has a manual gap

The testing section is unusually candid, and it is worth reading before you judge the project's quality.

Vitest cannot load `rizin.wasm`. That is stated as a fact, not as a limitation to be worked around later. The consequence is that after a wasm publish, the checks have to be done by hand, and the documentation lists them: two tabs where you rename in A, switch to B and back, and the rename is still in A; a cold `RZWEBPRJ` from Home; restoring an older snapshot from history; a depth 2 job banner with a cancel between `aa` and `aaa`; and the missing `rzweb_apply_project` case showing the apply-project notice.

That list is a specification of the behaviours that are easy to break, and it maps directly onto the limits section. The cross-tab rename check, for instance, tests exactly the kind of state-sharing bug that a per-tab worker architecture can introduce.

The limits themselves are the second half of the honest picture. There is no `ptrace` in the browser, so nothing that requires attaching to a running process is possible. There is one `RzCore` per live context, and that worker cannot take other commands until `aaa` returns. Cancellation works between stages, meaning between `aa` and `aaa` or `aaaa`, but not mid-command. And `aaa` on a large binary is the long pole, which means the tab is occupied for the duration.

For a project that has been public for less than a year and publishes no GitHub releases, having the limits written down plainly is worth something. It tells a user exactly where the edges are instead of letting them find out.

## Against a native Rizin install, the difference is where the core lives

The alternative is obvious and needs no advocacy: install Rizin itself and use the command line or the native UI. Comparing the two is a comparison of deployment models rather than of analysis capability, because both run the same engine.

A native Rizin has the full toolchain available to it, including whatever plugins and decompilers you have built, and it can attach to a running process, which the browser cannot. Its context survives closing the window, because the core is a process rather than a tab. Debugging a session is a matter of re-running the command, and the project has a scriptable interface that is not limited to what a web UI exposes.

RzWeb wins in a different set of conditions. There is nothing to install, so the barrier to opening a binary is a URL and a drag and drop, and a reviewer or a colleague can look at a sample without provisioning a machine. The file never leaves the device, which is the one property a native tool cannot offer when it is running on someone else's infrastructure or in a sandbox that has no network. And because the same wasm serves the MCP server, the tool can be driven by an agent or a script without a native build.

The realistic split is by task. Quick triage of a file you received, looking at strings, imports and the control flow graph, with a snapshot to keep, is what RzWeb is good at. Deep work on one binary, where you will want ptrace, a real decompiler, a long session and precise scripting, belongs in a native install.

One more thing to check is licensing. RzWeb is LGPL-3.0, and the package manifest records it as LGPL-3.0-only, with a `COPYING.LESSER` file alongside `COPYING`. If you intend to modify and redistribute it rather than use it as is, that distinction between the two versions of the LGPL is the detail to read.

## Conclusion

RzWeb suits analysts who want to open a binary, look at its strings, xrefs, imports and control flow, and take a copy of the session, without installing a toolchain or uploading a file anywhere. It is the wrong choice for dynamic analysis, since there is no ptrace in the browser, and for long sessions, since closing the tab kills the core. Before you rely on it, check three things: that the bundled rzwasi build actually ships a decompiler, because the stock Pages build has neither jsdec nor rz-ghidra and the view silently drops to `pdf` with `asm.pseudo`, that `rzweb_apply_project` exists in the wasm you load so restoring a snapshot works, and that your Node version is 22, since the project pins it and Vite dies on 18 with `crypto.hash is not a function`. The code is LGPL-3.0, the last push was on 2026-10-19, and the repository publishes no GitHub releases.

## FAQ

### What is RzWeb and how does it relate to Rizin?

RzWeb is a browser interface for Rizin compiled to WebAssembly. Rizin runs in a Web Worker so the page does not freeze while the WASM loads, and `aaa` owns that worker until the command returns.

### Does RzWeb upload the binaries I open?

No. Files stay on the device, with WebAssembly memory and IndexedDB as the only storage, and nothing is uploaded. Each open binary is a context holding the artifact bytes, a current `.rzdb` and a short revision history in IndexedDB.

### Why does the RzWeb decompiler view show pseudo code instead of decompiled output?

The stock GitHub Pages rzwasi build ships neither jsdec nor rz-ghidra. The view uses `pdg`, `pdd` or `pdc` when the build has one, and otherwise falls back to `pdf` with `asm.pseudo`. jsdec is an opt-in rzwasi build you point `VITE_WASM_BASE_URL` at.

### How do I run RzWeb locally?

You need Node 20.19+ or 22.12+, since Ubuntu's apt nodejs is 18 and Vite fails on it with `crypto.hash is not a function`. Then run `cd rzweb`, `node -v`, `npm install` and `npm run dev`, and open http://localhost:3000. Leave `VITE_WASM_BASE_URL` unset unless you are testing a local wasm build.

### What are the limits of RzWeb as a reverse engineering tool?

There is no `ptrace` in the browser, so dynamic analysis is out. There is one `RzCore` per live context and the worker cannot take other commands until `aaa` returns, cancellation works between analysis stages but not mid-command, and closing the tab kills that core. Restoring a project also needs `rzweb_apply_project` in the wasm you load.

## Sources

- [IndAlok/rzweb on GitHub](https://github.com/IndAlok/rzweb)
- [Issues](https://github.com/IndAlok/rzweb/issues)
- [License: LGPL-3.0](https://github.com/IndAlok/rzweb/blob/main/LICENSE)
- [Project website](https://rizin.pages.dev)
- [README](https://github.com/IndAlok/rzweb/blob/main/README.md)

---

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