# idac
[](https://pypi.org/project/idac/)


The IDA Pro CLI built for agents and humans, powered by
[`ida-nexus`](https://github.com/HexRaysSA/ida-nexus). Run
`idac decompile "sub_08041337"` from any shell or agent against either a live IDA
session or a headless database.
> `idac` is in early alpha and actively developed. It is already useful day to day, but the CLI surface may still change between releases.
## Contents
- [Why idac](#why-idac)
- [Demo](#demo)
- [Quick start](#quick-start)
- [Requirements](#requirements)
- [Upgrading from 0.19](#upgrading-from-019)
- [How it works](#how-it-works)
- [Agent sandbox setup](#agent-sandbox-setup)
- [Usage](#usage)
- [Highlights](#highlights)
- [Agent plugin](#agent-plugin)
- [Development](#development)
- [Credits](#credits)
## Why idac
- **Shell-native** — compose with pipes, `xargs`, `jq`, and your agent's existing
tool-use loop. The CLI connects through Nexus; no separate MCP server is required.
- **Agent-native by default** — every command can emit structured JSON (`-j`), and the
`idac` Agent Plugin teaches compatible agents to drive `idac` instead of guessing at
raw IDAPython.
- **Safe mutations** — supported mutations offer `preview`, which applies the change,
captures the before/after, and restores it with IDA undo or an operation-specific rollback. Dry-run retypes,
prototype changes, and other preview-capable edits before committing them.
- **Built for batches** — recover an entire class hierarchy, retype a hundred locals, or decompile every `Handler_*` in one invocation against a shared context.
- **Live or headless** — the same commands work against a running IDA GUI session,
a saved `.i64`, or a binary that IDA can open. Select a path with `-c`, an exact
running instance with `--instance`, or omit both when exactly one instance is ready.
## Demo
Run this from a regular shell against the fixture committed in this repo. Agents
using a per-command sandbox should first follow [Agent sandbox setup](#agent-sandbox-setup):
```bash
idac decompilemany "CreateHandler_" --out-dir decomp/ \
-c fixtures/idb/handler_hierarchy.i64
```
Every matching function is decompiled into its own `.c` file (named `<symbol>_0x<address>`) alongside a `manifest.json` index:
```
decomp/
├── CreateHandler_Text_0x100000588.c
├── CreateHandler_Stream_0x100000608.c
├── CreateHandler_Pack_0x100000688.c
└── manifest.json
```
The `.c` files hold the real Hex-Rays pseudocode:
```c
Handler *__cdecl CreateHandler_Text()
{
Handler_Text *v1; // [xsp+8h] [xbp-18h]
v1 = (Handler_Text *)operator new(0x38u);
Handler_Text::Handler_Text(v1);
return v1;
}
```
`manifest.json` records the exact address, symbol, and artifact paths for each function (abridged to one of three entries):
```json
{
"ok": true,
"pattern": "CreateHandler_",
"out_dir": "decomp",
"functions_total": 3,
"functions_succeeded": 3,
"functions_failed": 0,
"functions": [
{
"identifier": "CreateHandler_Text",
"name": "CreateHandler_Text",
"address": "0x100000588",
"ok": true,
"chars": 175,
"artifact_path": "decomp/CreateHandler_Text_0x100000588.c",
"artifact_stem": "CreateHandler_Text_0x100000588",
"artifacts": { "decompile": "decomp/CreateHandler_Text_0x100000588.c" }
}
]
}
```
The same command works against a live GUI session — drop `-c` when it is the only
READY Nexus instance.
## Quick start
Install the CLI from [PyPI](https://pypi.org/project/idac/). For live desktop work,
install the matching GUI integration:
```bash
uv tool install idac # installs the `idac` command on your PATH
idac setup gui # GUI plugin matching the installed ida-nexus client
idac doctor # report local versions and check the IDA environment
```
Restart IDA or load the installed Nexus component, then rerun `idac doctor` and
`idac targets list --json`. Headless work does not require the GUI component.
From a regular shell, select an existing database or binary with `-c` to start a
managed worker. For Linux agents with per-command sandboxes, follow
[Agent sandbox setup](#agent-sandbox-setup) before any database read or edit.
For the agent guidance, install the Agent Plugin (see [Agent plugin](#agent-plugin)).
`ida-hcli>=0.24.0` is an `idac` runtime dependency. Setup and diagnostics
run it through `idac`'s Python environment; they do not require `uvx` or a
separately installed HCLI executable. Runtime dependencies use minimum versions;
`uv.lock` records the resolved versions for reproducible development installs.
`setup gui` selects the release matching the installed Nexus client and lets the
installer resolve ida-domain within the declared requirement.
To install the latest development version straight from git instead:
```bash
uv tool install git+https://github.com/trailofbits/idac.git
```
Talk to a live GUI session:
```bash
idac targets list --json
idac decompile "sub_08041337"
idac decompile "sub_08041337" --f5 # force a fresh Hex-Rays pass
idac decompile "sub_08041337" --instance "<record-id>"
```
Work headless against an existing database from a regular shell:
```bash
idac database show -c sample.i64
idac decompile "ExampleClass::method_1" -c sample.i64
```
To run from a checkout without installing globally, use `uv run idac --help`.
## Requirements
- **Python 3.11+** and [`uv`](https://docs.astral.sh/uv/).
- **IDA Pro 9.4+** with the **Hex-Rays decompiler** for `decompile`, `ctree`,
and class recovery.
- A valid IDA license and an IDA Python environment on Python 3.11 or newer.
- `ida-nexus>=0.13.2` and `ida-domain>=0.5.1`, installed as dependencies of `idac`.
Install the matching GUI component with
`idac setup gui` when using a live GUI.
- Linux for persistent shared sessions opened with `idac database open`.
Run `idac doctor` to report CLI package versions, check the configured IDA version
through HCLI without starting IDA, and check the installed GUI
component, Nexus discovery, and the runtime inside every ready IDA instance.
When Codex or Claude is on `PATH`, it also compares installed `idac` Agent Plugin
versions with the CLI version and warns if they differ. Each optional agent check
has a two-second default timeout; `--timeout` overrides it.
Remote runtimes must satisfy the package requirements, and Nexus enforces protocol
compatibility. `idac` does not fall back to another backend.
## Upgrading from 0.19
The rewrite requires Python 3.11+ and IDA Pro 9.4+. Install the matching Nexus GUI
component with `idac setup gui` for live desktop work, and install the Agent Plugin
separately for agent guidance.
If uv's `exclude-newer` policy makes `ida-nexus>=0.13.2` unavailable, installation
fails during dependency resolution. Use a policy cutoff that admits the required
Nexus release before upgrading; the resolver cannot satisfy this dependency with
an older release.
Update existing commands and scripts as follows:
| Previous usage | Current usage |
|----------------|---------------|
| `-c db:sample.i64` | `-c sample.i64`; existing binary paths work too |
| `-c pid:1234`, `-c module:NAME`, or a bare module selector | `--instance RECORD_ID` from `idac targets list --json` |
| A legacy `.idb` database | Convert it to `.i64` in IDA first |
| `misc plugin install` | `setup gui`, followed by restarting IDA or loading the Nexus component |
| `misc skill install` | Install the [Agent Plugin](#agent-plugin) through your client's marketplace |
| `idac docs` | Subcommand `--help`, `--full-help`, and the installed skill's references |
| `targets cleanup` | Use `database close --instance RECORD_ID` for a persistent database opened by idac |
| `database save DESTINATION` | `database save` checkpoints the selected database; it accepts no destination path |
| `py exec --persist` | Combine dependent Python work in one invocation; each execution has a fresh namespace |
`targets list --json` returns Nexus records with `record_id`, `state`, `detail`,
`backend`, `pid`, `idb_path`, `exe_path`, `managed`, and `started_at`. Databases opened
with `database open` also report `session_status` and, when present, `session_error`.
Update scripts that read the old target fields. Omitted selectors require exactly one READY
instance, including managed headless workers. `-c` can attach to a matching GUI
database, so it does not guarantee headless execution.
Put target and timeout options on `batch` and `preview`, not on their child
commands. Successful headless mutations save automatically; batches keep earlier
successful changes even if a later step fails. GUI saves remain explicit. See
[How it works](#how-it-works) and [batch usage](#run-an-ordered-mutation-pass-with-batch)
for execution and journal behavior.
## How it works
Every IDA operation goes through the public ida-nexus Python API. `-c/--context PATH`
opens or attaches to an `.i64` or input binary. Nexus prefers a matching live GUI
database, reuses a matching managed worker, or starts a headless worker. Use
`--instance RECORD_ID` to attach to one exact record from `idac targets list`.
With neither selector, `idac` proceeds only when discovery finds exactly one READY
instance. Ambiguous, missing, blocked, disconnected, timed-out, or version-mismatched
targets fail explicitly. An operation is never retried on a different instance.
Headless opens wait for auto-analysis, and successful headless mutations save
automatically. Live GUI commands use the current analysis state and leave saving
explicit. Persistent Linux sessions support worker reuse across commands and agents.
See the [target and lifecycle guide](plugins/idac/skills/idac/references/targets-and-backends.md)
for lease lifetime, command coordination, saves, and failure recovery.
Use `idac targets list --json` to inspect discovery records and `idac doctor` to
diagnose the local and remote runtime stack.
## Agent sandbox setup
Linux command sandboxes can terminate detached IDA workers between tool calls.
The [idac skill](plugins/idac/skills/idac/SKILL.md#database-lifetime-in-linux-command-sandboxes)
covers safe startup and shutdown; its
[hosted-session guide](plugins/idac/skills/idac/references/targets-and-backends.md#shared-hosted-sessions-linux)
documents approvals, multi-agent coordination, and recovery.
Scaffold a project-local reversing workspace for sandboxed agents:
```bash
idac workspace init reversing-workspace
```
That creates workspace-local `.claude/` and `.codex/` config, agent guidance files,
prompt templates, and a git-backed directory layout for RE work. Nexus discovery and
execution are local to the host; the generated workspace allows the local access
needed by `idac`. Install the Agent Plugin in the client separately; workspace
initialization does not copy the skill or its references.
Claude and Codex share `AGENTS.md` directly; the workspace does not generate a
separate `CLAUDE.md`.
To customize the generated files, see the templates under [src/idac/workspace_template/default](src/idac/workspace_template/default).
## Usage
Use `idac <command> --help` for one subcommand and `idac --full-help` for the complete CLI surface. Command grammar, workflow, and IDA reference material ships with the plugin's skill and is loaded by agents that have it installed.
### Command families
| Family | Commands |
|--------|----------|
| Discovery | `doctor`, `targets list`, `database show`, `segment list`, `bookmark list/show`, `comment show` |
| Functions | `function list`, `metadata`, `frame`, `stackvars`, `callees`, `callers`, `prototype`, `locals` |
| Decompilation | `decompile`, `decompilemany`, `disasm`, `disasm --start/--end`, `ctree` |
| Search | `search bytes`, `search strings`, `xrefs`, `imports` |
| Types | `type list`, `show`, `deps`, `check`, `declare`, `type struct list/show/field`, `type enum list/show/member`, `type class vtable` |
| Classes | `type class list`, `show`, `hierarchy`, `fields`, `candidates` |
| Mutations | `misc rename`, `comment set/delete`, `bookmark add/set/delete`, `function prototype set`, `function locals update/rename/retype/apply`, `type struct field set/rename/delete`, `type enum member set/rename/delete` |
| Batch | `batch`, `batch --lint`, `preview` |
| IDAPython | `py exec` |
| Workspace | `workspace init` |
| Persistent Linux target | `database open`, `targets list --json`, `database close` |
| Maintenance | `misc reanalyze`, `database save`, `setup gui` |
### Output
Most read commands default to `--format text`. Use `--format json` (or `-j`) or `--format jsonl` when parsing, and `-o/--out <path>` for large results. With `--out`, `stdout` stays empty and a short `stderr` summary reports the artifact path and result counts. Matching is case-sensitive by default; `--regex` treats the filter as a regular expression and `-i` makes it case-insensitive. `search bytes` and `search strings` require both `--timeout` and `--segment`.
## Highlights
A few of the commands that make `idac` worth reaching for. Install the plugin for the full command and workflow reference.
### Preview a mutation before committing
`preview` is a wrapper that runs the real mutation and restores it with IDA undo or an operation-specific rollback, returning the before/after so you (or an agent) can verify the change first:
```bash
idac preview -o "/tmp/preview.json" \
function prototype set "sub_08041337" \
--decl "int __fastcall sub_08041337(void *ctx, const unsigned char *buf, unsigned int len)"
```
The wrapper owns the preview artifact. A wrapped command cannot set `--out`,
`--out-file`, or `--out-dir`; put `--out` on `preview` itself. Output paths are
also rejected when they alias the selected binary/database or any command input.
Put `-c`, `--instance`, and `--timeout` on the wrapper too; child commands cannot
override them.
Unfiltered type, struct, and enum lists can use the preview or batch wrapper's
`--out` to preserve their full results. File output checks use the selected
Nexus database and input paths, including GUI databases saved under another name.
### Recover C++ class hierarchies
Walk vtables, flattened layouts, and inheritance straight from the database:
```bash
idac type class list
idac type class hierarchy "ExampleClass"
idac type class show "ExampleDerived" # flattened object layout
idac type class vtable "ExampleDerived" --runtime
```
### Decompile a whole family in one pass
Select by name filter or by reading exact identifiers from a file; emit one combined file or one `.c` per function plus a `manifest.json`:
```bash
idac decompilemany "Handler_" --out-dir "decomp/" -c sample.i64
idac decompilemany "Handler_.*" --regex --out-dir "decomp/" --disasm --ctree
printf '%s\n' main sub_401000 0x401234 > funcs.txt
idac decompilemany --functions-file "funcs.txt" --out-dir "decomp-exact/"
# Or use --out-file for one combined text file without a manifest.
```
Pass `--f5` after type or prototype changes so each function reflects the latest state. With `--out-dir`, the manifest records each function's `name`, exact `address`, and artifact paths.
If you also write the command summary with `--out`, choose a different path from
the combined pseudocode file or any generated directory artifact.
For prototype edits that must retain the existing calling convention, use
`function prototype set --preserve-cc`. This preserves IDA's stored convention
when the declaration parser would otherwise normalize it. For `__usercall` and
`__userpurge`, it also preserves explicit argument locations by parameter position
and the return location. These custom-convention edits require the same parameter
count and parameter/return type sizes; supply explicit locations without the flag
when changing those. `type check` validates
dependent declarations together in a temporary type library, without importing
or replacing database types. Type imports preserve `#pragma` directives, including
structure packing, while removing comments, includes, and include guards. Changing
an existing local type requires `type declare --replace`; identical declarations
and references to existing types can be imported without it. A refused replacement
restores the entire import, including any new types declared in the same header.
`misc rename` is batch-safe, so related renames, prototype edits, comments, and
readbacks can share one Nexus session.
### Run an ordered mutation pass with batch
Run many subcommands against one shared context, leaving behind a stable ordered log. Batch files use one subcommand per line (drop the leading `idac`) and inherit the target (`-c` or `--instance`) and `--timeout` from the `batch` call. Child commands cannot set their own target or timeout because the wrapper owns one Nexus session for the entire run:
```bash
idac batch "recovery.idac" --out "/tmp/recovery_batch.json"
idac batch "recovery.idac" --lint --out "/tmp/recovery_batch_lint.json"
```
```text
# recovery.idac — run after support types exist locally
type check --decl-file "recovered_classes.h"
type declare --replace --decl-file "recovered_classes.h"
function prototype check "0x100000000" --decl "int __fastcall ExampleClass__parseHeader(ExampleClass *__hidden this, const unsigned __int8 *buf, unsigned int len)"
function prototype set "0x100000000" --decl "int __fastcall ExampleClass__parseHeader(ExampleClass *__hidden this, const unsigned __int8 *buf, unsigned int len)"
misc reanalyze "0x100000000"
function locals rename "0x100000000" 5 --new-name header_size
function locals rename "0x100000000" 6 --new-name record_type
```
Mutating batches require `--out` so the result log is preserved before any change runs. `batch --lint` parses child commands, resolves relative input paths, rejects unsupported batch commands, and warns on risky local selectors before execution. Setup commands are intentionally rejected from `batch`; `misc reanalyze` is batch-safe and belongs between type/prototype changes and local cleanup.
Before dispatching the first command, `batch` writes a `pending` journal and checkpoints it after every line. It closes the shared Nexus session before replacing that journal with a terminal `ok`, `failed`, or `interrupted` record; Ctrl-C returns 130 without discarding the lifecycle record. Mutating child commands cannot set their own `--out`—the wrapper artifact is the mutation log—while read-only children may still write separate artifacts.
Child output paths cannot replace existing input files referenced by any batch
line. A read-only child can generate a new file for a later child to consume when
that file did not exist before the batch started. On subsequent runs, remove that
generated file or choose a new path before regenerating it.
Use `--fail-fast` when later steps depend on earlier ones or local selectors must
stop on the first miss. A batch does not roll back earlier successful mutations;
headless checkpoints survive a later failure or interruption.
### Address locals three ways
`function locals update/rename/retype` share one selector model — local name, numeric index, or canonical `local_id`. Prefer `--index` or `--local-id` for longer passes and after reanalysis, since names drift:
```bash
idac function locals rename "sub_08041337" "v12" --new-name "value_maybe"
idac function locals rename "sub_08041337" --index 3 --new-name "value_maybe"
idac function locals retype "sub_08041337" --local-id "stack(16)@0x100000460" --type "unsigned int"
idac function locals apply "sub_08041337" --json-file "locals-plan.json"
```
Read the canonical `local_id` with `idac function locals list --json`. Use `update` when one local needs both a better name and type in one pass, and `apply` when several locals in one function should be applied from one fresh locals snapshot.
### Escape hatch: raw IDAPython
When no first-class command fits, drop to IDAPython against the same target:
```bash
idac py exec --code "result = {'entry': hex(ida_ida.inf_get_start_ea())}"
```
Each execution uses a fresh namespace; `--persist` is no longer available. Use
`--script PATH` or `--stdin` for multiline code, and keep dependent work in one
invocation. Script contents are read locally and execute inside IDA with the local
filename as `__file__`. Assign JSON-native data to `result`; structured output
includes `result`, `result_repr`, `stdout`, and `stderr`. `py exec` is treated as
mutating for saving and cannot be previewed.
## Agent plugin
The [Agent Plugins v1](https://agent-plugins.org/) package in
[plugins/idac](plugins/idac) teaches compatible agents to prefer `idac` commands over
ad hoc shell or raw IDAPython for RE work. The repository catalog at
[.agents/plugins/marketplace.json](.agents/plugins/marketplace.json) points directly to
that package; [plugins/idac/plugin.json](plugins/idac/plugin.json) is its manifest and
[plugins/idac/skills/idac](plugins/idac/skills/idac) contains its `idac` skill. The
plugin is not included in the `idac` Python package.
Install it through an Agent Plugins-compatible client. With Codex:
```bash
codex plugin marketplace add trailofbits/idac
codex plugin add idac@idac
```
Once installed, the skill loads automatically when relevant. The repository contains
one canonical plugin package, with no client-specific compatibility package or manual
skill-link fallback.
Run `idac doctor` after updating the CLI or plugin to check that their versions match.
It reads the installed plugin inventories through `codex plugin list --json` and
`claude plugin list --json` when those clients are available on `PATH`. Version
mismatches are warnings; the Agent Plugin is optional and does not affect CLI health.
For a ready-to-fill task prompt covering anything from a light analysis pass to class-family recovery, run `idac workspace init <dir>` to scaffold a workspace containing `prompts/recovery-pass.md`.
## Development
```bash
git clone https://github.com/trailofbits/idac.git
cd idac
uv sync # project venv with an editable install; run via `uv run idac ...`
make test # run tests
make check # formatting checks + lint + test + audit
```
To put an `idac` on your PATH that tracks your checkout, install it as editable:
```bash
uv tool install -e .
```
See [docs/development.md](docs/development.md) for fixture regeneration, Nexus
integration tests, and local tooling details.
## Credits
Inspired by [@banteg's `bn` Binary Ninja CLI tool](https://github.com/banteg/bn).
Backend integration is provided by
[`ida-nexus`](https://github.com/HexRaysSA/ida-nexus).
Written by [Codex](https://openai.com/codex)/gpt-5.3-codex/gpt-5.4/gpt-5.5.