Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ bn skill install

# Smoke-check the running bridge(s)
bn doctor
bn daemon list # mode + pid + socket + target count per running daemon
bn daemon list # mode + pid + endpoint + target count per running daemon

# Start a headless daemon (needs BN headless license on PYTHONPATH)
PYTHONPATH=/path/to/binaryninja/python bn daemon start --foreground
Expand All @@ -49,10 +49,10 @@ Tests do **not** require Binary Ninja. `tests/test_bridge.py` constructs a fake
### Wire path

1. A bridge is running: GUI auto-starts when Binary Ninja loads the plugin; headless is started by `bn daemon start`.
2. `BinaryNinjaBridge.start()` binds an `AF_UNIX` socket at `paths.bridge_socket_path(mode)` and writes a registry JSON at `paths.bridge_registry_path(mode)` (i.e. `cache_home()/daemons/{mode}.json`) carrying `pid`, `socket_path`, `plugin_version`, `plugin_build_id`, and `mode`.
2. `BinaryNinjaBridge.start()` selects its transport with `BN_BRIDGE_TRANSPORT=auto|pipe|tcp|unix`. `auto` means Unix-domain socket on Unix, the Binary Ninja **GUI Transport** user setting for Windows GUI mode, and authenticated Named Pipe for Windows headless mode. An explicit environment value overrides the GUI setting. It writes a mode registry at `paths.bridge_registry_path(mode)` carrying the endpoint, authentication data when required, plugin metadata, and mode.
3. The CLI (`src/bn/transport.py`) calls `list_instances()` which scans `cache_home()/daemons/`. `choose_instance()` picks one via the sticky pointer at `cache_home()/current_daemon`, falling back to "the only one running" when no sticky is set. With both `gui` and `headless` alive and no sticky, the CLI errors and hints the user to run `bn daemon use <mode>`.
4. The CLI opens the chosen socket and sends a one-line JSON request: `{"id", "op", "params", "target"}`. It then `shutdown(SHUT_WR)` and reads the whole response until EOF.
5. `BridgeHandler.handle` parses the request and calls `BinaryNinjaBridge.dispatch`. Target-scoped operations are submitted to `JobManager`, which resolves the target to a concrete id, reserves read/write access, and executes `_execute_operation` on an ordinary Python background thread. Multiple reads may run concurrently; read/write and write/write conflicts fail immediately with the blocking job id instead of waiting.
4. The CLI opens the chosen endpoint and sends `{"id", "op", "params", "target"}`. Unix/TCP use a one-line stream request and read until EOF; Windows Pipe uses `multiprocessing.connection` message framing. TCP and Pipe requests include the registry token.
5. The transport handler authenticates and parses the request, then calls `BinaryNinjaBridge.dispatch`. Target-scoped operations are submitted to `JobManager`, which resolves the target to a concrete id, reserves read/write access, and executes `_execute_operation` on an ordinary Python background thread. Multiple reads may run concurrently; read/write and write/write conflicts fail immediately with the blocking job id instead of waiting.
6. The bridge replies with `{"ok", "result", "error"}`; the CLI raises `BridgeError` if `ok` is false.

When adding a new operation: add the dispatch branch in `_execute_operation`, add the op name to either `READ_LOCKED_OPS` or `WRITE_LOCKED_OPS` (or deliberately leave it lock-free for state that has its own mutex), and add the matching CLI subparser + handler in `src/bn/cli.py`.
Expand Down Expand Up @@ -113,7 +113,7 @@ So every mutation lane has four possible result statuses — `verified`, `previe

### `paths.py` is the single source of truth for filesystem layout

`src/bn/paths.py` and the symlinked `plugin/bn_agent_bridge/paths.py` define every path the system uses: per-mode bridge socket (`bridge_socket_path(mode)`), per-mode registry (`bridge_registry_path(mode)` under `bridge_registry_dir()`), sticky daemon mode pointer (`current_daemon_mode_path()`), cache home, spill root, plugin install dir, skill install dirs for both Codex (`$CODEX_HOME/skills/bn`) and Codex (`$CLAUDE_CONFIG_DIR/skills/bn`). The plugin's `paths.py` and `version.py` are **symlinks** into `src/bn/` — keep them as symlinks so the GUI plugin and the CLI agree on path layout without duplication.
`src/bn/paths.py` and the symlinked `plugin/bn_agent_bridge/paths.py` define the transport selection, per-mode Unix socket (`bridge_socket_path(mode)`), per-mode registry (`bridge_registry_path(mode)` under `bridge_registry_dir()`), sticky daemon mode pointer (`current_daemon_mode_path()`), cache home, spill root, plugin install dir, and skill install directories. The plugin's `paths.py` and `version.py` are **symlinks** into `src/bn/` — keep them as symlinks so the GUI plugin and the CLI agree without duplication.

### Async load tracking

Expand Down
18 changes: 12 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,17 +46,21 @@ If the plugin code changes, reload Binary Ninja Python plugins or restart Binary

- `bn` has two parts:
- a normal Python CLI that you can run from your shell or agent tool harness
- a Binary Ninja bridge that exposes the API over a local transport: a Unix-domain socket on Unix, or authenticated loopback TCP on Windows
- a Binary Ninja bridge that exposes the API over a local transport: a Unix-domain socket on Unix, or an authenticated Windows Named Pipe on Windows
- The bridge runs in one of two **modes**:
- **`gui`** — loaded as a Binary Ninja plugin inside the GUI process. Works with a personal license. Started automatically when Binary Ninja opens with the plugin installed.
- **`headless`** — long-running daemon started with `bn daemon start`. Requires a Binary Ninja commercial (headless) license. Built for containers, CI, and AI agent driver loops.
- Each mode has its own endpoint and registry file under the platform cache directory, so both can run simultaneously on the same machine. On Windows, GUI defaults to `127.0.0.1:26765` and headless to `127.0.0.1:26766`; registry entries include a random authentication token and the CLI only accepts loopback endpoints. Override the defaults with `BN_BRIDGE_GUI_PORT` or `BN_BRIDGE_HEADLESS_PORT`.
- Each mode has its own endpoint and registry file under the platform cache directory, so both can run simultaneously on the same machine. Windows uses the fixed local Named Pipes `\\.\pipe\bn-agent-bridge-gui` and `\\.\pipe\bn-agent-bridge-headless`; the registry contains the selected endpoint and a random authentication token. The CLI rejects remote or nested pipe names.
- The CLI auto-discovers all running daemons. When only one is up, it routes to that one. When both are up, it routes to the **sticky** mode chosen via `bn daemon use <mode>` (see [Daemon Mode Selection](#daemon-mode-selection)).
- **No repeated loading**: The daemon keeps loaded binaries resident in memory. Each CLI invocation just opens a socket, sends a JSON request, and reads the response — the binary is never re-imported.
- **No repeated loading**: The daemon keeps loaded binaries resident in memory. Each CLI invocation opens the registered endpoint, sends a JSON request, and reads the response — the binary is never re-imported.

### Changing the GUI bridge port
### Transport selection

On Windows, run `BN Agent Bridge\Set GUI Port...` inside Binary Ninja to save a new user-level port and restart the bridge immediately. You can also edit **GUI Listen Port** under the **BN Agent Bridge** group in Binary Ninja Settings, then run `BN Agent Bridge\Restart Bridge`. If the new port cannot be bound, the plugin restores the previous setting and listener instead of leaving the bridge offline.
On Windows, choose **Named Pipe** or **Loopback TCP** with **GUI Transport** under the **BN Agent Bridge** group in Binary Ninja Settings. **GUI Listen Port** configures the TCP port. Run `BN Agent Bridge\Restart Bridge` after changing either setting. If the new transport or port cannot start, the plugin restores the previous working configuration.

`BN_BRIDGE_TRANSPORT=auto|pipe|tcp|unix` also controls the bridge transport. `auto` is the default: the Windows GUI reads **GUI Transport**, Windows headless selects `pipe`, and Unix selects `unix`. An explicit environment value overrides the GUI setting. Unsupported platform combinations fail explicitly and never fall back silently.

For Windows automation or headless compatibility, set `BN_BRIDGE_TRANSPORT=tcp` before starting Binary Ninja or the headless daemon. TCP remains restricted to `127.0.0.1`, requires the registry authentication token, and uses `BN_BRIDGE_GUI_PORT` / `BN_BRIDGE_HEADLESS_PORT` or the GUI **Listen Port** setting. CLI and plugin should be upgraded together because older CLIs do not understand Pipe registry entries.

## Quick Start

Expand Down Expand Up @@ -255,7 +259,7 @@ The headless daemon imports `binaryninja` and requires a Binary Ninja commercial

```bash
bn daemon start --foreground # block in foreground (Docker PID 1 / systemd)
bn daemon status # pid, socket, target count
bn daemon status # pid, endpoint, target count
bn daemon stop # authenticated graceful shutdown + registry cleanup
```

Expand Down Expand Up @@ -532,6 +536,8 @@ If `bn target list` is empty:
- make sure the plugin is installed with `bn plugin install`
- reload Binary Ninja plugins or restart Binary Ninja after plugin changes

On Windows, `bn doctor` should report a `pipe://\\.\pipe\...` endpoint. If Pipe creation or access fails, check that the CLI and plugin run as the same Windows user. For explicit compatibility mode, set `BN_BRIDGE_TRANSPORT=tcp` in both environments and restart the bridge; there is no automatic TCP fallback.

On Unix, if `bn doctor` sees a bridge registry but reports `Operation not permitted` under Codex,
the Codex sandbox is blocking the Unix socket that connects to the live Binary Ninja GUI
process. Let Codex run `bn` outside the sandbox by adding this rule to
Expand Down
Loading