hexmux
by ChiChou
README.md
# Hexmux

Hexmux runs task-specific IDAPython scripts in a selected IDA Pro database. It has three parts:
- `hexmux`, an external command-line client;
- `hexmux_plugin.py`, a pure-Python IDA plugin that listens on one private Unix socket per IDA process.
- `hexmux.domain_worker`, a headless worker that owns an official `ida_domain.Database` context.
There is no supervisor, background daemon, native helper, MCP server, TCP port, or UDP port. Hexmux currently supports macOS and Linux. Windows is not supported.
## Install from a checkout
Install the CLI as an isolated uv tool:
```sh
uv tool install --editable .
```
Install the IDA-side plugin with HCLI:
```sh
hcli plugin lint ./plugin
hcli plugin install ./plugin
```
HCLI copies `plugin/ida-plugin.json` and `plugin/hexmux_plugin.py` into `$IDAUSR/plugins/hexmux`. It does not install the external `hexmux` command, which is why the uv and HCLI installation commands are separate. Restart IDA after installing the plugin.
For development without HCLI, copy both files in `plugin/` into a `hexmux` directory below the active IDA user plugin directory. Typical locations are:
```text
Linux: ~/.idapro/plugins/hexmux/
macOS: ~/Library/Application Support/IDA Pro/plugins/hexmux/
```
If `IDAUSR` is set, use its `plugins/` directory instead.
## Discover IDA processes
Once IDA loads the plugin, it creates a socket named with its process ID:
```text
macOS: /private/tmp/hexmux-<uid>/<pid>
Linux: ${XDG_RUNTIME_DIR:-/run/user/<uid>}/hexmux/<pid>
```
The containing directory has mode `0700` and each socket has mode `0600`. `HEXMUX_RUNTIME_DIR` overrides the containing directory for both the CLI and plugin.
List live instances by querying every digit-named socket in that directory:
```sh
hexmux ps
hexmux ps --json
```
`hexmux list` is an alias for `hexmux ps`. Stale or unrelated directory entries that do not answer a valid `info` request are ignored.
## Open a headless IDA database
For a new input, Hexmux launches a Python worker and opens IDA in official library mode through `ida-domain`:
```sh
hexmux open firmware.bin
hexmux open firmware.bin --output /tmp/firmware.i64 --wait 120 --json
```
The `idapro` runtime normally discovers IDA through the configuration written by HCLI or IDA's `py-activate-idalib.py`. Use `--ida-dir` to override the installation for one worker process.
Hexmux always supplies an explicit output database path, so the source may reside on a read-only filesystem. `--output` selects the path directly. Without it, Hexmux derives a stable `.i64` from the canonical source path under:
```text
macOS: ~/Library/Caches/hexmux/databases/
Linux: ${XDG_CACHE_HOME:-~/.cache}/hexmux/databases/
```
`HEXMUX_DATABASE_DIR` overrides the generated-output directory. Opening the same canonical source path again reuses its live Domain worker, or reopens the same cached IDB after that worker exits. Use `--new-instance` to create an independent IDB and worker intentionally:
```sh
hexmux open firmware.bin --new-instance
```
Hexmux serializes concurrent launches with a file lock. The Domain worker also holds an exclusive ownership lock for the IDB throughout its lifetime, so two workers cannot open the same writable database.
Domain workers form a bounded local session pool. By default, a worker closes and saves its database after 30 minutes without an `exec` request, and at most four workers remain live. Discovery (`ps`/`info`) does not refresh activity. Workers publish private sidecar state so a busy IDA process still counts toward the limit while it cannot answer its socket. When opening a fifth source, Hexmux gracefully closes the least-recently-used idle worker before starting the new one; it never evicts a busy worker. Configure the policy per launch:
```sh
hexmux open firmware.bin --idle-timeout 900 --max-sessions 2
```
The worker owns an `ida_domain.Database` context and runs the request loop synchronously on the same thread. The normal GUI plugin is suppressed inside this process so only the worker owns its PID socket. The socket is published after auto-analysis completes and metadata is available. The worker remains alive after the launching CLI exits; SIGINT or SIGTERM exits the database context and saves the database.
## Run IDAPython
Save a script such as `functions.py`:
```python
import idautils
import ida_name
result = [
{"ea": hex(ea), "name": ida_name.get_name(ea)}
for ea in list(idautils.Functions())[:20]
]
```
Run it using the exact PID or a unique PID prefix printed by `hexmux ps`:
```sh
hexmux run 1234 functions.py
hexmux run 1234 --timeout 120 functions.py
```
If the script argument is omitted or is `-`, Hexmux reads the script from standard input:
```sh
hexmux run 1234 --json < functions.py
hexmux run 1234 - < functions.py
```
Scripts execute in a fresh namespace on IDA's main thread: through `execute_sync` in a GUI instance and directly in the single-threaded Domain worker. A trailing expression is returned automatically, or a script can assign its return value to the global `result`. JSON-compatible values are returned directly; other Python objects are represented by their `repr` and type name.
Without `--json`, Hexmux prints captured stdout, stderr, tracebacks, and the result. With `--json`, it prints the complete response envelope, including `ok`, `success`, `result`, `stdout`, `stderr`, `traceback`, and `elapsed_ms`.
Only one script runs in an IDA process at a time. A concurrent request receives a busy error. A client timeout stops waiting but cannot preempt Python already executing on IDA's main thread.
## HCLI releases
The `plugin/` directory is already a complete HCLI pure-Python plugin package. Its manifest declares Linux x86-64 and macOS x86-64/arm64 and deliberately excludes Windows. A release archive needs only:
```text
hexmux/
├── ida-plugin.json
└── hexmux_plugin.py
```
No IDA-plugin `pythonDependencies`, native executable, service registration, or post-install action is required. The external CLI depends on `ida-domain` for headless databases and is published separately as a Python package or source checkout.
## Fetch IDAPython reference stubs
Fetch a pinned source-form documentation snapshot without retaining the SDK checkout:
```sh
python3 scripts/fetch_idapython_reference.py \
--ref v9.3.0-release \
--output references/9.3
```
This copies curated `apidoc/*.py` files as `.pyi` documentation stubs, plus the qualified-symbol inventory and examples index. These stubs are useful but are not a complete substitute for runtime-generated IDAPython documentation.
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessUnresponsive