When you run npm install, it not only downloads the package but may also execute scripts that the package provides. If you run that code in your regular development environment, you can access files readable by the executing user and the environment variables passed to the process.

To isolate such development commands and track what they access, we have built Izanagi in Rust (source). It records file accesses, network communications, and child‑process launches so you can examine the behavior of the executed code.

In this article, we’ll first explain what Izanagi can do. Based on the implementation as of October 8 2026 (bc74c86e), we’ll describe how to use it and the scope of each feature. (source)

Running commands in a separate execution environment

The basic workflow is to create a configuration and then run the command inside a sandbox.

izanagi init
izanagi exec -- npm install

init generates the configuration interactively. Choose the backend and the shared directory, then pass the command you want to execute to exec. The sandbox environment must have the required build tools and runtime available.

The implementation of exec (source code) connects to an existing session if one is running; otherwise it starts a one‑off execution environment. It returns stdout, stderr, and the exit code to the caller, and performs cleanup after the one‑off execution finishes.

You can also keep the environment running and continue multiple tasks.

# Keep this running in this terminal
izanagi up
# Operate from another terminal
izanagi exec -- npm test
izanagi shell
izanagi down

shell is an interactive shell. It uses a pseudo‑terminal (PTY) to handle input, output, and terminal‑size changes. The environment started with up can be stopped with Ctrl+C or with down.

Configurations are stored using a hash derived from the working directory, allowing each project to have its own shared locations and monitoring criteria.

Choosing an isolation method

The sandbox backends include Apple Container, QEMU, and Linux’s Landlock. The meaning of specifying native varies by operating system. The code that selects the backend (source code) chooses Apple Container on macOS and Landlock on Linux.

BackendExecution environmentPrimary isolation method
Apple ContainermacOSRuns in a containerized virtualization environment
QEMUmacOS, LinuxBoots a guest OS and runs inside a VM
LandlockLinuxApplies file‑access restrictions to child processes on the host

With QEMU and containers, directories shared from the host are mapped to the guest’s working directory. Linux’s Landlock does not start a separate OS; instead, it sets access rules on host paths. The Landlock implementation (source code) permits read/write access to shared resources and read access to system files required for execution.

The shared location is also the working directory for the code being executed. If you pass a project with read‑write permissions, its files can be modified. Not sharing the host’s private keys and not allowing modifications to the shared source code are separate conditions.

When using Landlock and eBPF on Linux, you must build with the required features enabled.

cargo build --release --features landlock,ebpf

If the build lacks the necessary Linux features, it will produce an error indicating how to re‑build. PR #6 (PR #6) updates this guidance and fixes the execution of system commands via Landlock as well as the cleanup performed at termination.

Recording access to files and communications

Isolation and monitoring are implemented as separate components. The Sandbox manages the execution environment, the Tracer collects OS operations as events, and the Detector matches them against rules.

flowchart LR
    C[Development command] --> S[Execute in sandbox]
    S --> T[Tracer collects operations]
    T --> D[Detector matches against rules]
    D --> L[Logs and alerts]

A system call is the entry point for a process to request the OS to perform actions such as opening a file, establishing a connection, or launching another program. Izanagi monitors these operations using Linux eBPF or macOS DTrace. With QEMU, you can use vm-agent, which sends events from the guest‑side agent to the host.

izanagi up --sandbox=qemu --tracer=vm-agent

The combination of backend and Tracer must be verified. In the current select_tracer (source code), only QEMU allows explicit specification of vm-agent. Moreover, when you explicitly select QEMU or Apple Container and use auto, it becomes NullTracer, which does not perform monitoring. Simply launching an isolated environment does not guarantee that internal system calls are being captured.

Recorded events can be viewed from the CLI.

izanagi logs
izanagi logs --suspicious
izanagi logs --follow

--suspicious filters for suspicious operations, while --follow tracks newly added logs. For example, you can use it to check whether, during dependency installation, writes to the usual cache are mixed with accesses to private keys.

Detectable operations

The detection rules cover access to sensitive files, communications outside the allowed list, execution of suspicious commands, execution that deviates from a process baseline, and access to environment‑variable files.

SuspiciousPathRule (source code) matches configured path patterns against accesses. This includes directories that hold SSH keys or cloud credentials.

NetworkAllowlistRule (source code) compares destination IPs for connect and sendto against the allowlist. If a domain name is specified, the resolved IP address is used. If the allowlist is empty, all network traffic is treated as a warning.

This rule generates alerts. An alert merely indicates that a warning was raised; it does not block the connection itself. DNS and HTTPS traffic control are handled by the proxy functionality described later.

UnexpectedExecRule (source code) detects command names such as curl, wget, and nc. These commands are also used in normal development work, so their detection alone does not constitute definitive evidence of an attack. They serve as clues for investigating which process launched them, together with destination addresses and file‑access patterns.

EnvAccessRule detects file operations on /proc/self/environ and /proc/<pid>/environ. However, not all actions—such as reading environment variables that are already present in a process’s memory via JavaScript’s process.env—can be observed as system calls.

Therefore, the environment variables passed from the host are also limited. The CLI’s build_sandbox_env constructs environment variables consisting of a fixed PATH and, when present, TERM. It does not copy all of the host’s environment variables. The actual environment also depends on the selected image and backend configuration in addition to these arguments.

Handling DNS and HTTPS with a Proxy

To manage destinations and transmitted data, we have implemented a DNS proxy as well as proxies for capturing HTTP and HTTPS traffic. ProxyManager launches these as child processes according to the settings of izanagi up.

The DNS proxy forwards queries for allowed domains upstream and returns a dummy IP address for domains outside the allow list. This provides control over name resolution that passes through the DNS proxy. It is not equivalent to a complete network block, including traffic that specifies IP addresses directly.

The HTTP capture records the request content and returns a 403 response. For HTTPS, we use a TLS MITM approach that generates certificates for the target host using a custom CA. The client trusts this CA and routes traffic through the proxy, allowing us to process requests both before and after encryption.

izanagi-http-capture forwards HTTPS requests to allowed hosts upstream and rejects all others. Some communications—such as those from clients that perform strict certificate verification—cannot be handled solely by the standard CA trust configuration.

This proxy also includes a mechanism to replace secrets. Within the sandbox, a dummy token is used, and it is swapped for the real token when the request is forwarded to the permitted upstream.

In sandbox: create a request using a dummy token
  → Proxy: verify the destination and replace dummy values with real ones
  → Authorized upstream: receive request with real token

This feature allows sending requests without directly passing the real token to the processes of dependent packages. The current SecretMap implements string replacement, so a configuration that routes the corresponding HTTP traffic through the proxy is required. It does not automatically substitute credentials for arbitrary programs.

Note that configuring Apple Container to specify network = "none" is currently rejected as unsupported by the existing configuration checks. DNS control and proxy forwarding should be configured separately from a complete network block.

Execution from AI Agents

We have provided an MCP entry point so that AI agents can also use it. izanagi mcp can be launched as an MCP server that handles stdio. The following three tools are offered:

ToolOperation
sandbox_statusCheck the session’s running status
sandbox_execExecute a command with specified arguments
sandbox_shellPass a string to /bin/sh -c and execute

MCPのハンドラー first verifies the running session before proceeding. Starting the server does not automatically create a sandbox; the environment must first be prepared using izanagi up.

The MCP’s sandbox_shell is an API that executes a single shell command. It serves a different purpose from the interactive shell provided by the CLI’s izanagi shell, which continuously sends keyboard input.

By using this MCP tool, an AI agent can direct its dependency installations and tests to run within an Izanagi session. It is not an automatic mechanism that forwards all executions performed by the agent to other tools into Izanagi.

When Monitoring Stops

If you intend to run commands under the assumption that monitoring is active, you must also ensure that you do not begin work when monitoring has failed to start.

In Issue #30, there was a problem where even if the VM agent’s authentication or monitoring initialization failed, the host would still report a successful startup. In PR #34, we changed the code so that monitoring is not considered started until the agent responds with TraceStarted, and we added logic to stop the launched sandbox when initialization fails.

In engines where monitoring is enabled, if the required event stream fails while the process is running, the operation is aborted and the error is propagated back to the caller. --tracer=none is a setting that explicitly disables monitoring. This policy, together with the update criteria for the host and agent, is documented in the protocol specification.

Communication between the host and the agent currently uses a versioned postcard format together with HMAC‑SHA256. Previously we used bincode, but PR #29 migrated to postcard. The README has not yet been updated to reflect this. In addition, a response for the start of monitoring has been added, so configurations that update only the host while continuing to use an older guest image should be avoided; instead, build everything using the same revision.

Verification Status and Remaining Work

The CI run as of October 8 succeeded (CI run). In PR #36, tests passed for 458 Linux hosts, 480 macOS hosts, and 38 Linux agents. Cases in which input or terminal resizing occurs while only part of the communication has been received have also been added to the regression tests. (PR #36)

However, during the behavior verification on October 8 (source code), the existing QEMU image closed the connection before the “Hello” message, causing the smoke test to fail. Work remains to verify Exec, Event, and Shell end‑to‑end using an image that includes the latest agent and eBPF. PR #29 confirmed functionality on a real VM, but the same checks need to be performed again on images that incorporate the subsequent fixes.

We are currently developing implementations for macOS and Linux. Support for Windows AppContainer and ETW is still being considered for future work. The findings presented here are based on verification up to October 8 and have not been re‑tested with VMs or containers specifically for this article.

When using it, first determine the execution environment and the scope of sharing, then select the Tracer that corresponds to that environment. Configure the paths and endpoints you want to monitor, and if you need to control over communications, set up a proxy route as well. In Izanagi, we combine isolation, logging, detection, and communication control to configure the execution environment for development commands.