---
title: Install nd7 on macOS
description: What you need, the one command that installs both binaries, why they must sit next to each other, and how to remove them.
order: 10
section: Getting started
---

Installing nd7 is one `cargo install`. It builds two programs, `nd7` and `nd7-exec`, and puts them side by side in `~/.cargo/bin`. This page covers what you need first, the command, why the two binaries must stay together and be writable only by you, the developer setup, how to check the install worked, and what to delete if you want it gone.

## What you need

macOS, and a Rust toolchain. If you do not have one, install it from [rustup.rs](https://rustup.rs). Nothing else: nd7 needs no privileges, no kernel extension, no entitlement and no background service.

The sandbox is macOS only. On any other platform `nd7 run` and `nd7-exec` print `only supported on macOS` and exit 2; the rest of the tool (recording, verifying, shipping) builds and runs elsewhere, and continuous integration covers Linux as well as macOS.

## Install

```sh
cargo install --locked --git https://github.com/nd7-dev/nd7-core
```

That builds and installs both binaries into `~/.cargo/bin`. From a local checkout of the repository, the same thing:

```sh
cargo install --locked --path .
```

`--locked` means cargo uses the dependency versions recorded in the repository's `Cargo.lock` rather than resolving newer ones.

## Why there are two binaries, and why they must sit together

`nd7` is the command line: it records hook events, checks logs, ships them, registers its hooks with the agents, and starts a sandboxed session. `nd7-exec` is a small, separate program that is the only thing the sandbox lets out of itself; every Bash command the agent runs goes through it, and it applies the session's current rules to itself before running anything. [How the macOS sandbox works](/docs/sandbox) explains that mechanism.

Two consequences for the install:

- **They must stay next to each other.** `nd7 run` locates `nd7-exec` by resolving its own path and looking beside it, never by searching `PATH` or reading an environment variable. In the source that is `exit_path()` in [src/bin/nd7.rs](https://github.com/nd7-dev/nd7-core/blob/main/src/bin/nd7.rs), which takes the current executable, canonicalises it, and replaces the file name with `nd7-exec`.
- **They must not be writable by anyone but you.** Before starting a session, `nd7 run` checks that `nd7-exec` exists, that it is a regular file, and that neither it nor the directory holding it is writable by group or others. If either is, it refuses to start with a message naming the path and its mode, because a binary someone else can replace is a binary that can be made to run outside the sandbox.

A normal `cargo install` into `~/.cargo/bin` satisfies both. If you copy the binaries somewhere else, copy both, into a directory only you can write.

## Developer setup

While working on nd7 itself, symlink the installed names to the debug build instead of reinstalling after every change:

```sh
ln -sf "$PWD/target/debug/nd7"      ~/.cargo/bin/nd7
ln -sf "$PWD/target/debug/nd7-exec" ~/.cargo/bin/nd7-exec
```

The agent spawns hooks fresh for every event, so the next hook picks up each `cargo build` without restarting Claude Code. One caveat from the benchmarks: a debug build is roughly 1 ms slower per hook run than a release build, and the published numbers are all for release builds.

## Check the install

```sh
nd7 --help
```

prints the list of commands: `record`, `verify`, `enroll`, `ship`, `run`, `init`, `hook-prefix`, `allow` and `deny`. Running `nd7` with no arguments prints the same usage and exits 2, and an unknown command exits 2 as well.

```sh
nd7-exec
```

prints its own usage and exits 2. That is the expected result outside a session: `nd7-exec` runs a command only when it can find the session it was started in and that session's rules, and refuses with exit 126 otherwise.

To check the sandbox end to end, run any program under it:

```sh
cd your-project
nd7 run zsh
```

`nd7 run` prints one line naming the session before the program starts. Inside that shell, a write into the project succeeds and a write into your home directory outside the project does not. [A session, start to finish](/docs/session-walkthrough) walks through the same thing with `claude`.

## What nd7 writes to disk

Two directories under your home, and one file beside them:

- **`$XDG_STATE_HOME/nd7/` (default `~/.local/state/nd7/`)**: holds `sessions/<session-id>/` with the event log, its chain head and its lock; `vault/` with the machine key and pinned admin keys. When: recording, and enrolment with a vault.
- **`~/.nd7/sessions/<pid>/`**: holds `record.json` and `policy.sb`, the rules of one `nd7 run`. When: while a sandboxed session is running.
- **`~/.nd7/aliases.sh`**: `alias claude='nd7 run claude'` and `alias codex='nd7 run codex'`, plus one line in `~/.zshrc`, and in `~/.bashrc` if you have one, that loads it. When: `nd7 init` without `--no-alias`. The aliases live under `~/.nd7` because that is the one directory no session may write to, so a session cannot take away the aliases that put the next session under the sandbox.

`nd7 init` also writes hooks into the agents' own configuration, `~/.claude/settings.json` and `~/.codex/config.toml`; see [Set up nd7's hooks](/docs/claude-code-hooks).

The session directory is short-lived: `nd7 run` removes it on exit, and the next `nd7 run` sweeps up any left behind by a run that was killed outright. It is also the one place nd7's own rules can never make writable, from inside or out.

## Uninstall

The repository does not document an uninstall command, and there is none. To remove nd7 by hand:

```sh
cargo uninstall nd7-core          # removes nd7 and nd7-exec from ~/.cargo/bin
rm -rf ~/.local/state/nd7         # session logs, and the vault keys if enrolled
rm -rf ~/.nd7                     # sandbox session records, and the aliases
```

Adjust the second path if you set `XDG_STATE_HOME`. If you ran `nd7 init`, also remove what it wrote into the agents' own configuration: the `hooks` entries in `~/.claude/settings.json` or a project's `.claude/settings.json`, the `[[hooks.…]]` tables and the `[hooks.state]` entries marked `# nd7:` in `~/.codex/config.toml`, and the line loading `~/.nd7/aliases.sh` from `~/.zshrc` and `~/.bashrc`; see [Set up nd7's hooks](/docs/claude-code-hooks). Deleting `~/.local/state/nd7` deletes your recorded sessions, including any not yet shipped to a vault.
