# Imunify for AI agents docs > Documentation for the Imunify for AI agents syscall interception and policy enforcement platform. --- # Imunify for AI agents > Documentation site for Imunify for AI agents — a kernel-level syscall interception and policy enforcement system for Linux hosts running autonomous AI agents. Welcome to the **Imunify for AI agents** documentation site. This is where operators, integrators, and resellers come to read how the product is set up, how the daily workflow looks, and what every knob does. In one sentence: Imunify for AI agents is a kernel-level gate that sits between an autonomous AI agent and the shell access it has on your Linux host. The Sensor watches every syscall the agent tries to make, evaluates it against your policy, and either lets it through, blocks it, or pauses it for a human approval. ## Where to start - **New here?** Begin with [Getting Started]({{< ref "getting-started" >}}) — install the Sensor, see your first event, and tour the Panel. - **Living in the Panel day to day?** The [Panel section]({{< ref "panel" >}}) covers reading the [Events]({{< ref "panel/events-page.md" >}}) log and resolving [Approvals]({{< ref "panel/approvals-page.md" >}}). - **A hoster or reseller?** See the [Deployment Guide for Hosters]({{< ref "resellers" >}}). --- # Getting Started > How to set up Imunify for AI agents from scratch — install the Sensor, sign into the Panel, and see your first event. Imunify for AI agents protects a Linux host running an autonomous AI agent by intercepting every syscall the agent makes and evaluating it against a policy. To get up and running you install the Sensor on your host with a one-line install command and sign into the Panel. The whole process takes about ten minutes. Work through the three pages in this section in order: 1. **[Install the sensor]({{< ref "getting-started/install-sensor.md" >}})** — the prerequisites, the one-line install command, the kernel boot setting some Linux distributions need for LSM BPF, the optional Telegram link, and how to confirm the sensor is running and connected. 2. **[Your first event]({{< ref "getting-started/first-event.md" >}})** — what the sensor watches, what to expect the first time it catches something, and how the Panel unlocks your encrypted event content. 3. **[Tour the Panel]({{< ref "getting-started/panel-tour.md" >}})** — signing in, switching between sensors, the language selector, and what the Dashboard shows you. If you do not have an install command yet, your Imunify operator issues one from the Panel. --- # Install the sensor > Prerequisites, the one-line install command, the optional Telegram link, and how to confirm your sensor is running and connected. This page walks you through installing the Imunify sensor on one of your Linux servers. The whole flow takes about ten minutes the first time and about thirty seconds on every server after that. ## Prerequisites Your server needs: - Linux with kernel 5.4 or newer and BTF (BPF Type Format) support - **LSM BPF enabled** in the running kernel - Root (sudo) access - Network access from your server to `panel.imunify.ai` over HTTPS To check your kernel version: ```bash uname -r ``` To check that BTF is available: ```bash ls /sys/kernel/btf/vmlinux ``` If that file exists, BTF is on. Most distributions with kernel 5.8 and later ship with BTF on by default. To check that LSM BPF is on: ```bash cat /sys/kernel/security/lsm ``` The output must include `bpf`, for example `capability,bpf,landlock,yama,apparmor`. On Ubuntu 22.04 / 24.04 and Debian 12 the kernel supports LSM BPF but does **not** include `bpf` in the default boot configuration — you have to add it once. If `bpf` is missing from the line above, see [Enable LSM BPF](#enable-lsm-bpf) below before you run the installer. The installer refuses to proceed without LSM BPF. ## Step 1: Get your install command You generate the install command yourself from the Panel — no need to wait on anyone. 1. Sign in to the Panel at **[panel.imunify.ai](https://panel.imunify.ai)** with the username and password your administrator gave you. 2. Open the **Add Sensor** dialog. If you have no sensors yet, the **Add Sensor** button is right there in the header sensor selector; otherwise open it from the **Sensors** page. 3. Optionally type a **label** for this host (for example `prod-web-01`) so you can tell your sensors apart later. You can leave it blank. 4. Click **Generate**. The Panel mints a fresh install token and shows you a one-line command with a copy button. Click the copy icon. ![The Add Sensor dialog in the Panel, with an optional label field and a Generate button](/images/screenshots/add-sensor.png) The command looks like this: ```bash curl -fsSL https://panel.imunify.ai/api/v1/install/inst_.sh | bash ``` The token embedded in it is **single-use** and **expires after 24 hours**. If it expires or has already been used, just open **Add Sensor** again and generate a new one. (If you have hit your sensor limit, free a slot by deleting an unused sensor on the Sensors page, or ask your administrator to raise the cap.) ## Step 2: Run the installer SSH into the server you want to protect, as root (or a sudo user), and paste the command you copied: ```bash ssh root@your-server curl -fsSL https://panel.imunify.ai/api/v1/install/inst_.sh | bash ``` The installer will: 1. Run preflight checks (root access, Linux, kernel version, BTF support, systemd) 2. Download the sensor binary that matches your CPU architecture 3. Write the sensor configuration and your sensor's API credentials 4. Download your active policy rules 5. Install and start the sensor as a system service 6. Install the sensor's self-upgrade machinery so future updates land automatically The whole process takes about thirty seconds. You will see one status line per step as it completes. ## Step 3: Link Telegram (optional) By default approval requests show up in the Panel itself. If you would rather get them on your phone, link **Telegram** or **Discord** from **Settings → Notifications** in the Panel: pick a channel, scan the QR code (or open the deep link), and approve right from the chat with Allow/Deny buttons. This is self-service — you do not need an administrator to set it up. See [Notifications]({{< ref "panel/notifications.md" >}}) for the details. You can skip this and do it later. ## Step 4: Confirm the sensor is running The easiest check is the Panel itself: open the **Sensors** page and your newly installed host appears with a green **RUNNING** status, and its capability pills (LSM BPF, fanotify, seccomp-notify) turn green within a heartbeat or two. That means the sensor reached the server, registered, and is enforcing policy. ![The Sensors page listing a registered sensor with a green RUNNING status and capability pills](/images/screenshots/sensors.png) If you would rather confirm from the host's shell, two commands do it: ```bash systemctl status imunifyai-sensor # expect: active (running) journalctl -u imunifyai-sensor --since "2 min ago" | grep Registered # a "Registered" line = connected ``` If something looks wrong, head to [Troubleshooting]({{< ref "sensor/troubleshooting.md" >}}) — the most common first-install issue is LSM BPF, covered next. ## Enable LSM BPF If the installer (or the sensor itself after a later upgrade) fails with `LSM BPF is not active in the running kernel`, or your Panel shows a red **LSM BPF** pill on your sensor's row, your kernel supports LSM BPF but it is not switched on in the boot parameters. This is a one-time fix and needs a reboot. ```bash # 1. Confirm the kernel supports LSM BPF: grep CONFIG_BPF_LSM /boot/config-$(uname -r) # Expected: CONFIG_BPF_LSM=y # 2. Add 'bpf' to the lsm= list in /etc/default/grub. # Edit the GRUB_CMDLINE_LINUX line so it reads, for example: # GRUB_CMDLINE_LINUX="... lsm=lockdown,yama,apparmor,bpf" sudo vi /etc/default/grub # 3. Regenerate the GRUB config: sudo update-grub # Debian / Ubuntu # or: sudo grub2-mkconfig -o /boot/grub2/grub.cfg # RHEL / Rocky / Alma # 4. Reboot: sudo reboot # 5. After reboot, confirm 'bpf' is now in the LSM list: cat /sys/kernel/security/lsm # must contain 'bpf' # 6. If the sensor was already installed, restart it: sudo systemctl restart imunifyai-sensor ``` The Panel's **LSM BPF** pill turns green on the next heartbeat from your sensor. If the installer instead reports `Kernel does not support LSM BPF (CONFIG_BPF_LSM is not set)`, your kernel is too old or was not built with LSM BPF. Install a newer kernel and reboot into it: - Ubuntu: `sudo apt-get install -y linux-generic-hwe-$(lsb_release -rs)` - Debian: `sudo apt-get install -y linux-image-amd64` (bookworm or newer) - RHEL / Rocky / Alma: `sudo dnf install -y kernel-modules-core kernel` ## What happens next Once the sensor is running, it starts intercepting system calls on your server and evaluating them against your policy rules. See [Your first event]({{< ref "getting-started/first-event.md" >}}) for what to expect the first time the sensor catches something, and [Tour the Panel]({{< ref "getting-started/panel-tour.md" >}}) for a quick orientation to the Panel UI. --- # Your first event > What the sensor watches, what to expect when it catches its first event, and how the Panel unlocks your encrypted event content. Once your sensor is running, it uses kernel-level mechanisms (eBPF, fanotify, and seccomp) to watch which system calls the AI agent makes and which files it accesses — and, when the optional HTTP(S) proxy is enabled, the network requests it sends. The first time something risky happens, you see an event in the Panel — and possibly an approval prompt asking you to allow or deny it. ## What the sensor watches Through those kernel mechanisms the sensor sees the operations that matter most for security: - **File reads (`openat`)** — reading sensitive files like SSH keys, credentials, and configuration. - **Process execution (`execve`)** — running commands like `sudo`, package managers, or a new shell. - **Network connections (`connect`)** — outbound connections to restricted destinations such as cloud metadata endpoints or internal networks. - **File deletion (`unlinkat`)** — removing critical system files. Every intercepted call is checked against your policy rules. Depending on the matching rule, the call is **allowed** (let through), **blocked** (denied at the kernel), or **held for approval** (paused while you decide). ## What you should see in the Panel Open the Panel in your browser, sign in, and unlock your private content key when prompted. Then: 1. Go to the **Sensors** page. Your newly installed sensor appears here. The status pill should be green and the **LSM BPF**, **fanotify**, and **seccomp-notify** capability pills should all be green within a heartbeat or two. 2. Go to **Events**. As soon as a monitored process makes a watched system call, a row appears here with the syscall name, the path or destination, and the matching rule. 3. Go to **Approvals**. If a rule is set to "hold for approval," a card appears here with **Allow Once**, **Allow Always**, **Deny Once**, and **Deny Always** buttons. Approval requests also stream to Telegram or Discord if you linked one. ![The Approvals page showing past decisions with allow/block outcomes and approver attribution](/images/screenshots/approvals.png) If nothing shows up after a few minutes, your sensor is connected but the watched processes have not done anything interesting yet. That is the expected steady state — the sensor only logs the events your rules tell it to log. ## Reading your encrypted event content Your event and approval content — file paths, command arguments, executable paths, request and response bodies — is encrypted on the way in under a key only you control. The Panel decrypts it for you transparently, but two things look different from a plain dashboard: 1. **You unlock once per sign-in.** Right after you sign in, the Panel asks for your password a second time so it can unlock your private key. The unlock lasts about fifteen minutes and then the Panel asks again. This is intentional: someone with a stolen browser tab but no password cannot read your event content. 2. **Lost password = lost data.** Your password is the only thing that can unwrap your key. If you forget it and have to reset it, the previous key is destroyed and a fresh one is created. Older events stay in the database but show up as **redacted** with the reason `key_reset`, and they cannot be recovered. Pick a password you can remember, or store it in your password manager. Container and Docker names stay visible to administrators as operational metadata so they can tell replicas apart, but the content fields are unreadable to anyone but you. ### A note on API tokens The unlock behavior is tied to your Panel sign-in session. Long-lived API tokens — the ones that look like `sk-user-EXAMPLE` — cannot unlock encrypted content. If your automation calls an event or approval content endpoint with one of those tokens, the API returns `403 requires_jwt`. Use the Panel for encrypted content; use API tokens for redacted and metadata workflows. The full token reference is in your Panel's authenticated **API documentation** (Settings → API documentation). The **Settings** page also has a destructive **Reset encryption key** action. It requires typing your username and password before it runs and should only be used if you accept losing access to your older encrypted events. ## Managing your sensor day to day You will rarely need to touch the sensor by hand once it is running, but a few `systemctl` and `journalctl` commands cover almost every situation: **Check the sensor's status:** ```bash systemctl status imunifyai-sensor ``` **View the most recent logs:** ```bash journalctl -u imunifyai-sensor --since "10 min ago" --no-pager ``` **Restart the sensor:** ```bash sudo systemctl restart imunifyai-sensor ``` **Stop the sensor (turns off all monitoring on this host):** ```bash sudo systemctl stop imunifyai-sensor ``` Stopping the sensor disables every layer of system-call monitoring. The processes the sensor was watching keep running, but nothing on this host is enforcing policy until you start it again. For deeper diagnostics and recovery, see the [Sensor section]({{< ref "sensor" >}}). --- # Tour the Panel > A short orientation to the Imunify Panel: how to switch between your sensors, how language and sign-in work, and what you see the first time you log in. The Imunify Panel is your day-to-day control plane. This page is a quick orientation so you know where to look the first time you sign in. The deeper Panel reference lives in the [Panel section]({{< ref "panel" >}}). ## Signing in There is no self-service registration — your administrator creates your account up front and shares your username and a starting password with you. Open the Panel URL, sign in with those credentials, and your browser will offer to save the password as usual. The Panel uses short-lived sign-in sessions. After about fifteen minutes of inactivity, it asks you to sign in again. This is intentional: it keeps a stolen browser tab from being useful to someone who is not you. If you forget your password, an administrator can reset it. **Note:** resetting your password also resets your private content key, which means older encrypted events become unreadable. See [Your first event]({{< ref "getting-started/first-event.md" >}}) for the details. ## The sensor selector If you have more than one sensor — for example, one per server — the header has a sensor selector dropdown. Pick the sensor you want to look at and the Dashboard, Events, and Approvals pages all narrow their view to that sensor. The Panel remembers your last choice in the browser, so the next time you sign in on the same computer you land on the same sensor. If you do not have any sensors registered yet, the selector shows an **Add Sensor** button instead. Clicking it opens a small dialog that asks for a label (optional), then gives you a one-line install command to run on your next server. The command works the same way as the install token in [Install the sensor]({{< ref "getting-started/install-sensor.md" >}}). ## Language The Panel ships in English by default and also supports German, French, Spanish, and Portuguese. Change the language from **Settings → Language**. Your choice is saved in the browser, so each computer you sign in from can have its own language. ## Your first time on the Dashboard The Dashboard is the first page you land on after signing in. The four summary tiles across the top show **Sensors**, **Events**, **Pending approvals**, and active **Overrides** for the sensor you have selected. Below those, two charts show the recent event volume so you can spot bursts of activity. ![The Panel Dashboard with summary tiles and event-volume charts](/images/screenshots/dashboard.png) The first time you sign in, those numbers are probably zero or close to it. As your sensor starts intercepting system calls, the Events tile climbs and the chart fills in. The page keeps itself fresh in the background, so you do not need to refresh — if the live connection drops, an amber banner appears so you know the numbers are stale. From here, the sidebar takes you to: - **Sensors** — every server you have installed the sensor on, with its current status and capability pills. - **Events** — the full filterable event log. - **Approvals** — pending approvals and the history of past decisions. - **Overrides** — active "always allow / always deny" rules you have set. - **Settings** — your account, password, language, and notification channels. The full reference for each of these pages lives in the [Panel section]({{< ref "panel" >}}). --- # Sensor > Operate, upgrade, and troubleshoot the Imunify sensor on your Linux hosts — systemd commands, upgrade behavior, and recovery. The Imunify sensor is the small system service that runs on each of your Linux hosts. Once installed, it sits in the kernel and watches the system calls that matter most for security — file reads, process execution, outbound network connections, and file deletion — and runs each one through the policy you defined in the Panel. Depending on the matching rule, the call is allowed, blocked, or held for your approval. See [Your first event]({{< ref "getting-started/first-event.md" >}}) for the full list of intercepted system calls and what the Panel shows you when the sensor catches one. The sensor keeps itself up to date through a maintenance-window upgrade flow. When a new release is published and an administrator pushes it to your host, the sensor stops itself, replaces its binary, restarts, and reports back. There is a short enforcement gap during the swap and no action is required from you. See [Upgrades]({{< ref "sensor/upgrade.md" >}}) for what an upgrade looks like from your side and how to verify the new version is healthy afterwards. ## Pages in this section - **[Install the sensor]({{< ref "getting-started/install-sensor.md" >}})** — the one-line install command, prerequisites, and a checklist that confirms the sensor is connected. (Installation lives in Getting Started so you can finish onboarding without leaving that section.) - **[Operation]({{< ref "sensor/operation.md" >}})** — the day-to-day `systemctl` and `journalctl` commands for checking the sensor's status, reading its logs, and restarting it. - **[Upgrades]({{< ref "sensor/upgrade.md" >}})** — what happens during a sensor upgrade, how to watch one happen live, and the post-upgrade checks you can run to confirm the new version is healthy. - **[AI agents in Docker and Docker Compose]({{< ref "sensor/docker-agents.md" >}})** — create a new managed OpenClaw or Hermes container with one command, or bring an existing Docker / Docker Compose deployment under protection with `protect-container`, including the install-time offer and what happens on hosts that need a reboot. - **[Troubleshooting]({{< ref "sensor/troubleshooting.md" >}})** — what to do when the sensor fails to start, can't reach the server, or refuses to attach because LSM BPF isn't enabled in the running kernel. The LSM BPF GRUB fix is here. --- # Operation > Day-to-day systemctl and journalctl commands for checking your sensor's status, reading its logs, restarting it, and stopping it cleanly. The Imunify sensor runs as a systemd service named `imunifyai-sensor` on every host where you installed it. The four commands below cover everything you will do day to day: checking that it is alive, reading what it has been doing lately, restarting it after a config or rule change, and stopping it when you need to take the host out of enforcement. These commands need `sudo` on most hosts. Run them as root or with `sudo` in front. ## Check status ```bash systemctl status imunifyai-sensor ``` A healthy sensor shows `Active: active (running)` with a recent start time. If the status is `inactive`, `failed`, or `activating` and not moving on, jump straight to [Troubleshooting]({{< ref "sensor/troubleshooting.md" >}}). ## View recent logs ```bash journalctl -u imunifyai-sensor --since "10 min ago" --no-pager ``` Adjust `--since` to whatever window you need. The sensor logs registration with the server, rule reloads, policy decisions it surfaced, and any errors it ran into. The most useful filter when something is wrong is `journalctl -u imunifyai-sensor -p err --since "1 hour ago"` — that limits output to the error-level lines. ## Restart the sensor ```bash sudo systemctl restart imunifyai-sensor ``` Restart the sensor after editing its on-disk config under `/etc/imunifyai/sensor/` (the on-disk YAML is the cold-boot fallback — once the sensor reconnects, the Panel's ruleset is the source of truth). A restart causes a short enforcement gap of a few seconds while the sensor reattaches. ## Stop the sensor ```bash sudo systemctl stop imunifyai-sensor ``` Stopping the sensor removes all syscall monitoring on that host. Processes the sensor was watching keep running, but without any policy enforcement. Use this only when you know you want enforcement off — for example, during a planned maintenance window. Start it again with `sudo systemctl start imunifyai-sensor` (or just `sudo systemctl restart imunifyai-sensor`). --- # Upgrades > What happens when your sensor upgrades, how to watch one live, and how to confirm the new version is healthy afterwards. When your sensor needs to upgrade, it stops itself, replaces its binary, restarts, and reports back. There is a short enforcement gap during the swap, and no action is required from you — your administrator pushes the new release from the Panel and the sensor takes it from there. This page covers what an upgrade looks like from your side: what changes on the host, how to watch one happen, and the post-upgrade checks you can run to confirm the new version is healthy. ## Why upgrades pause enforcement The sensor sits in the kernel and intercepts system calls. Replacing the running binary while it is actively attached to the kernel — with fanotify watches, eBPF programs, and (optionally) HTTP redirect rules in place — would be risky. So the sensor takes itself out of the way first: 1. Stop the sensor's interception components. 2. Replace the binary while the sensor is down. 3. Restart and verify. This creates a short window — usually a handful of seconds — during which no syscall monitoring is happening on that host. The trade-off is intentional: a clean, predictable swap is safer than trying to mutate a running interception stack in place. ## Watch the upgrade live If you want to see an upgrade as it runs (during the brief swap window), tail the upgrade-related journal entries on the host: ```bash journalctl -u imunifyai-sensor-upgrade.service -f ``` You will see the upgrade machinery progress through fetching the new binary, verifying its checksum, swapping it in, and restarting the sensor. The whole thing typically takes well under a minute. You can also inspect the durable on-host state file while an upgrade is in flight: ```bash cat /var/lib/imunifyai/sensor/upgrade-state.json ``` This file records which phase the upgrade is currently in and what version it is moving from and to. It is kept on disk for about an hour after the upgrade finishes so you can read it after the fact, then garbage-collected. ## Confirm the upgrade succeeded After an upgrade completes, you can verify the host ended up healthy and on the new version with this short checklist: 1. **The sensor service is active.** ```bash systemctl is-active imunifyai-sensor.service ``` Expected: `active`. 2. **The running binary matches the target version.** ```bash /usr/local/bin/imunifyai-sensor --print-version ``` Expected: the new version your administrator pushed. 3. **The upgrade state file shows `completed`.** ```bash cat /var/lib/imunifyai/sensor/upgrade-state.json ``` Expected: a `phase` of `completed` and matching `from_version` / `to_version` fields. 4. **If HTTP interception was enabled on this host, the embedded TPROXY gateway is active.** ```bash ss -ltnp | grep ':19080' ip rule show | grep 1049 iptables -t mangle -S IMUNIFY_TPROXY || true ``` Expected: the Sensor owns the TPROXY listener and capture rules again. A separate mitmproxy service is not expected. 5. **The Panel agrees.** Open the Panel's Sensors page; the row for this host should show the new sensor version and a recent heartbeat. If the panel still shows the old version, give it 30 seconds and refresh — sensor heartbeats are not instant. If any of those checks fails, see [Troubleshooting]({{< ref "sensor/troubleshooting.md" >}}). The most common post-upgrade issue is the sensor refusing to start because LSM BPF is not active in the running kernel; the troubleshooting page has the one-time GRUB fix. ## Resume a stalled upgrade In the rare case that an upgrade does not converge on its own — for example, the host lost power partway through — the upgrade machinery is designed to reconcile itself on the next boot or within five minutes via a recover timer. If you want to nudge it without waiting, you can restart the upgrade service explicitly: ```bash sudo systemctl start imunifyai-sensor-upgrade.service ``` If the upgrade is unrecoverable and you want to roll the sensor back to the previous version manually, run the rollback command: ```bash sudo /etc/imunifyai/sensor/upgrade-controller.sh rollback ``` This restores the previous binary from `/usr/local/bin/imunifyai-sensor.prev` and restarts the sensor. The Panel will show the older version on the next heartbeat. If you reached this command after a failed upgrade, also let your administrator know — they may want to investigate the new release before re-pushing it. --- # AI agents in Docker and Docker Compose > Run OpenClaw or Hermes as a protected Docker container — create a new managed container, or bring an existing Docker / Docker Compose deployment under sensor protection. If your AI agent — OpenClaw or Hermes — runs inside a Docker container, the sensor's kernel-level protection (file access, process execution, network decisions) covers it automatically. Two protection layers, however, need settings **inside** the container: HTTPS traffic analysis (the container must trust the sensor's certificate authority) and agent-action events (the agent plugin must reach the sensor's AppHook socket). This page shows how to get those layers wired — whether you are starting fresh or already have an agent running. There are two paths: - **Starting fresh?** Let the sensor create a fully managed agent container for you — one command, protected from the first second. - **Already running an agent in Docker or Docker Compose?** Protect the existing container in place with `protect-container` — a one-time recreate that keeps your data and configuration. Everything on this page runs on the host where the sensor is installed, as root (or with `sudo`). Install the sensor first: [Install the sensor]({{< ref "getting-started/install-sensor.md" >}}). ## What protection adds to a container A protected container differs from an unprotected one in three ways: 1. **Imunify labels** (`imunify.ai.managed=1`) so the sensor recognizes and tracks the container across restarts. 2. **Certificate trust** — the sensor's CA certificate is mounted read-only into the container and wired into the environment (`SSL_CERT_FILE`, `NODE_EXTRA_CA_CERTS`, and friends), so HTTPS analysis works without certificate errors. 3. **AppHook socket** — the sensor's socket is mounted into the container so the agent plugin can report prompts, tool calls, and responses as events. Docker containers are immutable: these settings cannot be added to a running container in place. That is why both paths below involve creating (or recreating) the container once. ## Path 1: Create a new managed agent container If you do not have an agent running yet, this is the fastest way — the sensor creates the container with all protection wired in from the start: ```bash # OpenClaw sudo imunify-agent-support create-openclaw-container # Hermes sudo imunify-agent-support create-hermes-container ``` The command pulls the agent image, creates a managed container with the labels, certificate trust, and socket mounts already in place, and starts it. Useful options (both runtimes): | Option | What it does | |---|---| | `--name ` | Container name (instead of the default) | | `--image ` | A specific agent image instead of the default | | `--state-dir ` | Host directory for the agent's persistent state | | `--env-file ` | Environment file with your API keys and settings | | `--port ` | Host port for the agent's gateway | | `--skip-onboarding` | Skip the agent's interactive first-run onboarding | When it finishes, the container shows up in the Panel as a protected agent, and every prompt and tool call it makes lands on the [Events page]({{< ref "panel/events-page.md" >}}). ## Path 2: Protect an existing container Already running an agent in Docker? One command brings it under protection: ```bash sudo imunify-agent-support protect-container ``` The command works for both standalone `docker run` containers and Docker Compose services — it detects which one you have and picks the right strategy. Before changing anything it prints a plan (what will change, expected downtime, how rollback works) and asks for confirmation. Add `--dry-run` to see the plan without doing anything, or `--yes` to skip the confirmation prompt in scripts. ### Standalone Docker containers Supported for **OpenClaw and Hermes**. For a container started with `docker run`, protect-container: 1. Backs up the original container configuration. 2. Stops the container and recreates it with the Imunify labels, certificate/AppHook environment, and read-only mounts added. Your volumes, network settings, and environment are preserved. 3. Starts the new container and verifies it is healthy. **Downtime:** the container is stopped and recreated, so in-memory work is interrupted — in-flight agent conversations are cut. Persistent state on volumes is untouched. **Rollback:** if anything fails, the original container and runtime configuration are restored automatically. ### Docker Compose services For a Compose-managed container the in-place approach would not survive: the next `docker compose up` recreates the service from your project files and would silently drop every Imunify setting. So protect-container takes a durable route instead: 1. It writes a `docker-compose.override.yml` next to your compose file. The override carries all the Imunify settings and is loaded automatically by Compose — your own compose file is **never modified**. 2. It recreates the service once via `docker compose up -d --no-deps`. From then on the protection **survives every future `docker compose up`**, image update, and service recreate, because Compose keeps applying the override. **Downtime:** the service is recreated by Compose; in-memory work is interrupted. **Rollback:** on failure the override file is removed (or the previous one restored) and Compose re-applies your original definition. > **Note:** Docker Compose protection currently supports **OpenClaw only** and is considered experimental. A Hermes container managed by Compose is still covered by kernel-level protection; for the in-container layers, run it standalone for now. If something does not work, contact support@imunify.ai. ## The installer offers this for you When you install the sensor on a host that already runs a Compose-managed OpenClaw container, the installer detects it and asks right there: ```text Docker Compose openclaw agent detected: openclaw-openclaw-gateway-1 (1d8e46e81605). To protect it, Imunify writes a compose override file next to your compose file and recreates the service once. ... Protect openclaw-openclaw-gateway-1 now (write override + recreate the service)? [y/N] ``` - Answer **y** and the installer runs the protect flow immediately — nothing else to do. - Answer **n** (or run non-interactively) and the installer prints the exact `protect-container` command to run later, and records a **pending action** so the reminder shows up in the install summary and in the Panel. Nothing is ever done to your container without consent, and the install never fails because of this step. **Hosts that need a reboot:** on some distributions the installer has to enable LSM BPF in the boot configuration and asks you to reboot before the sensor can start. On those hosts the question is still asked during install — answering **y** queues your consent, and a one-shot service applies the protection **automatically right after the reboot**, once the sensor is up. You can check how it went with: ```bash journalctl -u imunifyai-postboot-protect -b ``` If the automatic run could not protect the container (for example, the container was not running yet), the pending action remains and the manual `protect-container` command always works. ## Check the result After protecting a container, verify the wiring: ```bash # The container carries the Imunify label docker inspect --format '{{ index .Config.Labels "imunify.ai.managed" }}' # prints: 1 # Certificate trust is wired into the environment docker exec env | grep -c imunify-sensor # prints a non-zero count ``` Then open the Panel: the container appears as a protected agent, and its HTTPS activity and agent actions start showing on the [Events page]({{< ref "panel/events-page.md" >}}). If something looks off, the [Troubleshooting]({{< ref "sensor/troubleshooting.md" >}}) page covers sensor-side diagnostics. To see any outstanding container-related reminders: ```bash sudo imunify-agent-support list-pending ``` ## Undo it Protection is fully reversible: ```bash # Remove Imunify settings from one container (recreates it back) sudo imunify-agent-support unprotect-container # Remove Imunify settings from all managed containers on this host sudo imunify-agent-support unprotect-all-containers ``` For a Compose service this removes the Imunify override file and recreates the service from your original definition. --- # Troubleshooting > What to do when the sensor fails to start, can't reach the server, or refuses to attach because LSM BPF isn't enabled — including the one-time GRUB fix. This page collects the failures customers hit most often after installing or upgrading the sensor, and what to do about each one. Start with [Sensor fails to start](#sensor-fails-to-start) — almost every problem either is that or surfaces through it. ## Sensor fails to start Read the logs for the specific error: ```bash journalctl -u imunifyai-sensor --since "5 min ago" --no-pager ``` Common causes: - **`No policy rules loaded`** — the sensor could not find rules in `/etc/imunifyai/sensor/rules/` and could not fetch them from the server. Contact your administrator. - **`failed to connect`** — the sensor cannot reach the event server. Verify network connectivity to the host and port shown in `/etc/imunifyai/sensor/config.yaml`. See [Cannot connect to server](#cannot-connect-to-server) below. - **`LSM BPF is not active in the running kernel`** — the kernel supports LSM BPF but it is not enabled in the boot parameters. See [LSM BPF not active](#lsm-bpf-not-active) below for the one-time GRUB fix. ## Binary not found If `imunifyai-sensor` is not found after installation, check that it was installed to `/usr/local/bin/`: ```bash ls -la /usr/local/bin/imunifyai-sensor ``` If missing, the installer may have failed during the download step. Re-run the install command with a fresh install token from your administrator. ## LSM BPF not active If the installer — **or** the sensor service itself after upgrade — fails with `LSM BPF is not active in the running kernel`, or the Panel shows a red `LSM BPF` pill on your sensor's row, your kernel supports LSM BPF but it is not enabled in the boot parameters. Starting with v0.0.19, the sensor exits with status 1 at startup when LSM BPF is missing, instead of falling back to a weaker monitor-only mode. Fix it once (this requires a reboot): ```bash # 1. Confirm the kernel supports LSM BPF: grep CONFIG_BPF_LSM /boot/config-$(uname -r) # Expected: CONFIG_BPF_LSM=y # 2. Add 'bpf' to the lsm= list in /etc/default/grub. # Edit the GRUB_CMDLINE_LINUX line so it reads e.g.: # GRUB_CMDLINE_LINUX="... lsm=lockdown,yama,apparmor,bpf" sudo vi /etc/default/grub # 3. Regenerate GRUB config: sudo update-grub # Debian/Ubuntu # or: sudo grub2-mkconfig -o /boot/grub2/grub.cfg # RHEL/Rocky/Alma # 4. Reboot: sudo reboot # 5. Verify after reboot: cat /sys/kernel/security/lsm # must contain 'bpf' # 6. Restart the sensor (if it was already installed): sudo systemctl restart imunifyai-sensor ``` The Panel's `LSM BPF` pill should turn green on the next heartbeat. If instead the installer reports `Kernel does not support LSM BPF (CONFIG_BPF_LSM is not set)`, your kernel is too old or was not built with LSM BPF. Install a newer kernel package and reboot into it: - Ubuntu: `sudo apt-get install -y linux-generic-hwe-$(lsb_release -rs)` - Debian: `sudo apt-get install -y linux-image-amd64` (bookworm or newer) - RHEL / Rocky / Alma: `sudo dnf install -y kernel-modules-core kernel` ## BTF not available If the installer reports that BTF is not available: ```bash # Check if your kernel config has BTF enabled: grep CONFIG_DEBUG_INFO_BTF /boot/config-$(uname -r) ``` If BTF is not compiled into your kernel, you may need to upgrade to a newer kernel (5.8+) or install a BTF-enabled kernel package for your distribution. Contact your system administrator for assistance. ## Cannot connect to server Verify the server address in the sensor configuration: ```bash cat /etc/imunifyai/sensor/config.yaml | grep server ``` Test connectivity: ```bash curl -s https://panel.imunify.ai/health ``` If the health endpoint is unreachable, check firewall rules and the configured `server.url`. Current sensors use one URL for both REST and gRPC; configs containing `server.host`, `server.port`, `server.rest_port`, or `server.tls` are from the old schema. Current sensors migrate those fields to `server.url` on first boot; if migration fails, reinstall with a fresh install one-liner from your administrator. ## Install token rejected If you see HTTP 410 when running the install command, the token has already been used. Each install token is single-use. Request a new token from your administrator. If you see HTTP 401 or 403, the token may have expired (install tokens are valid for 24 hours). Generate a new one from **Add Sensor** in the Panel. --- # Panel > Reference for the Imunify Panel: real-time approvals, notification channels, account settings, and the cross-page sensor selector. The Imunify Panel is the web interface where you see what your sensors caught, approve or deny activity in real time, and adjust your account. If you have not signed in before, start with the [Panel tour]({{< ref "getting-started/panel-tour.md" >}}) — it covers signing in, the sensor selector, and the Dashboard. This section is the deeper reference for the three Panel pages that have their own behavior worth documenting. ## Pages in this section - **[Events page]({{< ref "panel/events-page.md" >}})** — the audit log of everything your sensors flagged: how to read a row, filter the log, expand an AI-agent turn, and promote an event into a permanent custom rule. - **[Approvals page]({{< ref "panel/approvals-page.md" >}})** — the real-time human-in-the-loop interface. Pending approvals stream in live, the four action buttons (Allow Once, Allow Always, Deny Once, Deny Always) resolve them, and the History tab shows every past decision with pagination. - **[Notifications]({{< ref "panel/notifications.md" >}})** — how to add Telegram or Discord as an extra channel for approval alerts alongside the in-browser notifications that are always on. - **[Settings]({{< ref "panel/settings.md" >}})** — your account information, your sensor limit, and the language selector. For a quick walkthrough of signing in and what the Dashboard looks like the first time, see the [Panel tour]({{< ref "getting-started/panel-tour.md" >}}) in Getting Started. --- # Events page > How to read the Event Log: what a row means, the filters, the detail sheet, turn grouping, and turning an event into a permanent rule. The **Events** page is the audit log of everything your sensors flagged: which system call or tool call happened, on which host, what the policy decided, and which rule decided it. Where the [Approvals page]({{< ref "panel/approvals-page.md" >}}) is about decisions you need to make *now*, Events is the record of what already happened — both the things that were blocked or held and the routine activity worth keeping. ![The Events log showing rows of AI-agent activity with decision badges](/images/screenshots/events.png) ## What one row tells you Each row is one thing a monitored process did. The columns are: - **Time** — when the sensor observed it. - **Type** — the kind of activity: a system call (`OPENAT`, `EXECVE`, `CONNECT`, …), a network or HTTP event, or an AI-agent tool call. - **Target** — what it acted on: a file path, an `ip:port`, a URL, or the tool/command. Encrypted content (see below) shows here once you unlock. - **Rule** — the human-readable name of the rule that fired (hover for the raw rule ID). - **Status** — the decision badge: **Allowed**, **Warn**, **Blocked**, **Held** (for approval), **Killed**, or **Timeout**. Click any row to open the **detail sheet** with the full context the sensor captured: the process (PID, executable), the matched rule, the container name if the activity came from inside one, and the decrypted content fields. ### The Status badge does more than label On actionable rows — anything blocked, killed, or held — the decision badge carries a small caret (**▾**). It opens a menu that lets you turn this one event into a **persistent custom rule** without hand-writing anything: - **Whitelist** — always allow activity like this from now on. - **Require approval** — stop auto-blocking and ask you instead (block rows only). Choosing either opens a short dialog where you can record *why* (optional, up to 2000 characters) before confirming. The new rule then appears on the **Custom Rules** page in the sidebar, where you can change its action (allow / approve / deny) or remove it later. Routine `allow`/`warn` rows and `group_*` analysis rows have no caret. A small **pencil icon** inside a badge means the event matched a rule you created or overrode — "modified by rule." Hover it for the tooltip. ## Filtering the log The toolbar narrows the log down. The filters that matter most day to day: - **Decision** — `BLOCK`, `WARN`, `HOLD`, `KILL`, `TIMEOUT` (combinable, e.g. show only blocks and kills). - **Category** — `SYSCALL`, `NETWORK`, `FILE`, `PROCESS`, `HTTP`. - **Risk level** — `LOW`, `MEDIUM`, `HIGH`, `CRITICAL`. - **Rule** — every event that fired a specific rule. - **Time range** — a start/end window. - **Sensor** — set globally by the header [sensor selector]({{< ref "panel/settings.md" >}}); every page, including Events, is scoped to it. Two toggles control noise rather than narrowing by attribute: - **Exclude noise** — hides classes of expected, low-signal activity (font/favicon/CDN fetches, reads the agent makes on itself, and similar) so the genuinely interesting rows stand out. - **Collapse duplicates** (on by default) — when the same action is seen by more than one interception mechanism at once, it shows as a single row that lists the mechanisms that contributed, instead of two near-identical rows. Turn it off for a raw, forensic view that preserves every underlying record. ## Turn grouping for AI agents When the activity comes from an AI agent, related events are linked by a **turn** — one step of the agent's reasoning loop (read a file, run a command, make a request). The log groups them so you can expand a turn and see everything the agent did in that step together, instead of reading interleaved rows. This is what makes a multi-step attack — read a credential, then connect out, then exfiltrate — visible as one story rather than three unrelated lines. ## Encrypted content and the unlock prompt Event content — paths, command arguments, executable paths, request and response bodies — is stored **encrypted** under a key only you control. The first time you open Events (or Approvals) after signing in, the Panel asks for your password to **unlock** that key for the session; the unlock lasts about fifteen minutes. While the key is locked, the Event Log shows an **Unlock data** prompt instead of a blank or spinning panel — click it (or the lock indicator in the header) and enter your password to reveal the content. Two row states are expected: - **Redacted** — what an administrator sees when looking at your events. Operational fields (sensor, time, decision, container) stay visible; your workload content does not. - **Unreadable after key reset** — the row was encrypted under a key generation that a password reset destroyed. It stays in the audit trail but its content cannot be recovered. See [Encryption — User view]({{< ref "encryption/user-view.md" >}}) for the full picture, and the [Approvals page]({{< ref "panel/approvals-page.md" >}}) for resolving the events that are held for a decision. --- # Approvals page > The real-time approvals page: how pending requests arrive, how to resolve them, what the History tab shows, and how the sidebar count and unlock prompt behave. The Approvals page is where you decide what your sensor should do about activity it caught. When a rule says "ask the user before this runs," the system call pauses on the host and a card appears here, live, while a person on the host is waiting for your answer. This page is the human-in-the-loop part of the platform. ![The Approvals page History tab showing past allow and block decisions with approver attribution](/images/screenshots/approvals.png) ## Pending and History tabs The page has two tabs at the top. - **Pending** is the default view. It shows every approval that is currently waiting for a decision. Cards stream in live as new approvals arrive and disappear as you (or someone else on your team) resolve them. - **History** shows every past approval — resolved or timed out — with pagination (40 rows per page). Columns are time, system call, path or IP, the rule that matched (its human-readable name with the rule ID on hover), the decision, and who approved it. When you have no history yet, you see "No approval history yet." ## Live updates and the disconnect banner The Pending tab updates in real time. New cards appear as approvals are raised on your hosts and disappear as soon as anyone resolves them — from this tab, from Telegram, or from Discord — without you having to refresh. If the live connection between your browser and the Panel drops (a network blip, a server restart, a closed laptop lid), the page shows an amber banner that says **Live updates disconnected — showing cached data**. The numbers and cards you see are still real, but they are the last snapshot we had. The Panel reconnects on its own and the banner clears as soon as it does. If the disconnection turns out to be your sign-in expiring, the Panel sends you back to the sign-in page instead of looping on a dead connection. ## What an approval card looks like When a request arrives, you see a card with the details the sensor captured: what was attempted (the system call), where (the path, the IP and port for network calls, the URL for HTTP egress), which process attempted it, and which rule asked for your decision. If the activity came from inside a container, the container name is shown on the card so you know which workload to ask about. The card also shows the human-readable rule name (for example, "Block writes to /etc") rather than the raw rule ID. Hover over the name to see the ID itself, which is what scripts and the override list use. Four buttons let you decide: - **Allow Once** — let this specific request through. The next equivalent request still raises a new approval. - **Allow Always** — let this through and create a permanent **override** so similar requests are auto-allowed for this sensor without bothering you again. You can review and revoke saved overrides later from the Panel. - **Deny Once** — block this specific request. Subsequent equivalent requests will still ask you. - **Deny Always** — block this and create a permanent override that blocks similar requests for this sensor going forward. A decision propagates to the host within a few hundred milliseconds, which means the process that was waiting on its system call resumes (or gets killed, depending on the rule) almost immediately. There is no "save" button — clicking one of the four buttons is the action. ### What "Deny" actually means If you pick **Deny Once** or **Deny Always**, the underlying system call returns an error to the process. For execve and similar process-creation calls, the rule may also instruct the sensor to kill the offending process tree. The Panel does not give you a separate kill button — that choice lives in the rule definition itself. ## When the Panel asks you to unlock The first time you open Approvals (or Events) after signing in, the Panel may prompt you for your password to **unlock** your private content key. This is normal — event and approval details are stored encrypted in the database, and the Panel needs your unwrapped key in memory to decrypt the path, reason, and arguments on the card. Unlock is per sign-in session: once you have unlocked, you stay unlocked until your refresh session expires or you sign out. A few details that come up often: - **"Unreadable after key reset"** — the row was encrypted with a private key that no longer exists, because a password reset destroyed the old key generation. The row stays in the audit trail but you cannot read its content. See [Encryption — User view]({{< ref "encryption/user-view.md" >}}). - **"Redacted"** — an administrator looking at your data sees event and approval rows in this form. Operational fields (sensor, timestamp, decision, container name) are still visible to the admin, but your workload content (paths, arguments, reasons) is not. - **`requires_unlock`** in the API — your sign-in session has not unlocked yet. The Panel handles this for you by opening the unlock dialog automatically. - **`requires_jwt`** in the API — you tried to read encrypted content with a long-lived API token (`sk-user-...`). Long-lived API tokens cannot unlock encrypted content. Sign in with your password instead. ## The sidebar counter The **Approvals** entry in the left sidebar shows a small counter next to it — for example, **Approvals (3)** — which is the number of pending approvals across all your sensors. It updates live the same way the Pending tab does, decrementing the moment any one of your team resolves a card from Telegram, Discord, or the Panel itself. ## No-one home What if no one is watching when an approval arrives? Nothing happens silently — the approval stays in the Pending state until either: - someone opens the Panel (or Telegram, or Discord) and resolves it, or - the rule's timeout expires (120 seconds by default). When a timeout fires, the approval moves to History with the decision the rule specified for timeouts (most rules default to block). The original request on the host has already been waiting that whole time, so a long timeout means a long pause for whoever is using that host — keep that in mind when authoring rules. ## Browser push notifications If you keep the Approvals tab in the background, or you want to be alerted on your phone, you can enable **Web Push notifications**. Click the bell icon at the top of the Approvals page and your browser will ask for permission. Once you grant it, your computer (or phone home-screen, on iOS) gets a system notification each time a new approval arrives. Clicking the notification takes you straight to Approvals. For approvals delivered to Telegram or Discord instead, see [Notifications]({{< ref "panel/notifications.md" >}}). --- # Notifications > Add Telegram or Discord as an extra channel for approval alerts, link your account with a one-time token, and act on approvals from chat. You can add **Telegram** or **Discord** as an extra channel for approval requests. The Panel's own in-browser notifications (the live cards on the Approvals page, plus optional Web Push) are always active — Telegram and Discord come on top. The benefit of an extra channel is mobility. Your phone usually has the Telegram or Discord app and you can resolve an approval from there in a few seconds, including the four Allow/Deny buttons, without opening the Panel. ## Pick one channel Open **Settings → Notifications**. The page shows your current channel, which is one of: - **Web only** (default, no extra channel), - **Telegram**, or - **Discord**. You can have at most one extra channel linked at a time. Switching from one to the other unlinks the previous one. ## Link Telegram 1. Open **Settings → Notifications** and pick **Telegram**. The page shows a QR code and a deep link of the form `t.me/?start=lnk_`. 2. Open the link in Telegram (scan the QR with your phone or click the link if you are already on a desktop with Telegram). The bot replies as soon as it matches the token. 3. The Panel detects the link within a few seconds, switches your notification channel to Telegram automatically, and shows your linked status. After linking, the Imunify bot supports three commands inside your DM: - `/start lnk_` — link a different account (only used during initial linking). - `/approvals` — list your recent approval decisions. - `/overrides` — list your active overrides, with a button to revoke each one. When a new approval is raised on one of your sensors, the bot sends you a DM with the event details and inline Allow/Deny buttons. Tap one of them to resolve the approval in real time — the Panel updates immediately too. If anyone messages the bot without linking first, it replies with instructions pointing back to **Settings → Notifications**. ## Link Discord 1. Open **Settings → Notifications** and pick **Discord**. The page shows a one-time `/link ` command (with a copy button) plus a QR code that links to the Discord bot invite. 2. Add the bot to your Discord server (or open a DM with it). The QR or invite link does this for you. 3. In a DM with the bot, send the `/link ` slash command — or paste it as a plain message if your client cannot show slash commands. 4. The bot replies as soon as the token matches, and the Panel detects the link within a few seconds and switches your notification channel to Discord automatically. The Discord bot supports the same three actions: - `/link ` — link your Discord account. - `/unlink` — remove the link. - `/status` — show whether you are linked and to which account. All three also work as plain text messages, not just slash commands — useful when the slash-command UI is unavailable. Like Telegram, approval DMs from the Discord bot include Allow Once / Allow Always / Deny Once / Deny Always buttons and resolve approvals live. ## Unlinking Pick **Web only** in **Settings → Notifications**. This unlinks Telegram or Discord, whichever you had, and stops sending approval DMs to that channel. You can re-link at any time by generating a new token from the same page. ## Web Push (browser notifications) Web Push is the third notification channel and it is independent of Telegram and Discord. Enable it from the bell icon on the Approvals page — see [Approvals page]({{< ref "panel/approvals-page.md" >}}). Web Push works on desktop browsers out of the box; iOS additionally requires adding the site to your Home Screen so it runs as a PWA. ## Multi-user setups The notification channel is **per user**, not per Panel. Each user account on the Panel has its own link to Telegram or Discord (or none), and approval DMs only go to the users who have a channel linked. Web notifications still go to every signed-in browser, and any user can resolve any approval — the first decision wins. --- # Settings > Your account information, the sensor limit on your account, and how to change the Panel's language for one browser at a time. The **Settings** page is split into a few tabs. This page covers the ones that do not have their own reference elsewhere: Account information and Language. For Notifications, see [Notifications]({{< ref "panel/notifications.md" >}}). For password changes and the destructive **Reset encryption key** action, see [Encryption — User view]({{< ref "encryption/user-view.md" >}}). ## Account information The **Account** tab shows the basics about your sign-in: - **Username** — your sign-in name. You cannot change this from the Panel; an administrator does it for you. - **Display name** — the friendly name shown next to your avatar. Shown as `—` if none is set. - **Role** — either a regular user or an administrator. Administrators see data across the whole Panel; regular users only see their own. - **Max Sensors** — the cap on how many sensors your account can have registered at one time. For a regular user this is a number like `5` or `20`. For administrators, who are not subject to a per-account cap, the field reads **Unlimited**. If you hit the **Max Sensors** limit, you can either delete an unused sensor from the Sensors page in the Panel sidebar, or ask your administrator to raise the cap. Deleting a sensor frees its slot immediately. ## Language The Panel ships in five languages: **English** (default), **German**, **French**, **Spanish**, and **Portuguese**. Pick one from the **Language** selector on this tab. The selection is saved in your browser, which means each computer you sign in from can have its own language. Sign-in screens and the page you are reading right now use the same selector. ## The header sensor selector The dropdown in the header (next to your account name) is the **sensor selector**. It scopes the Dashboard, Events, and Approvals pages to the sensor you pick — for example, switching from "prod-web-01" to "prod-db-02" updates all three pages to show only that sensor's data. Your last selection is remembered in the browser. If you have no sensors registered yet, the selector shows an **Add Sensor** button instead. Clicking it opens a small dialog that generates a one-line install command for a new host — the same install command described in [Install the sensor]({{< ref "getting-started/install-sensor.md" >}}). --- # Encryption > How Imunify keeps your event and approval content private: per-user encryption, the unlock prompt, and what happens to old data if you reset your password. Imunify stores the sensitive parts of your events and approvals encrypted in the database. Only your sign-in unlocks them, which means a stolen database dump or an administrator looking at your account does not reveal what your sensor actually saw — the paths, arguments, reasons, and other workload content stay protected. The single page in this section explains what that means in practice: why the Panel asks you to **unlock**, what happens if you forget your password, and what an administrator can and cannot see. ## Pages in this section - **[User view]({{< ref "encryption/user-view.md" >}})** — the customer-facing reference. Answers "why does the Panel ask me to unlock?", "what if I forget my password?", and "does an administrator see my data?". --- # User view > Why the Panel asks you to unlock, what happens to your encrypted history if you forget your password, and what an administrator can see. Event and approval details are stored encrypted in the database. Only your sign-in can decrypt them. This page explains what that means for you day to day. ## Why does the Panel ask me to unlock? Signing in proves who you are. After your password reaches the server it is checked against the stored hash and then forgotten. Imunify does not keep your password in memory or on disk after sign-in. But your event and approval content — the paths your sensor saw, the system call arguments, the reasons rules matched — is stored encrypted with a key only your password can derive. So the first time you open **Events** or **Approvals** after signing in, the Panel prompts you to **unlock** by entering your password again. The server uses that password to decrypt your private key and keeps the unwrapped key briefly in memory, tied to your current sign-in session. While you stay unlocked, the Panel can show you the decrypted paths, arguments, and reasons on each card. When your sign-in session expires (or you sign out), the unwrapped key is dropped from memory and you unlock again on the next sign-in. There is no setting to disable this — the unlock prompt is the only way the server can see your event content. ## What if I forget my password? If you completely forget your password, an administrator can reset it for you. **The reset is destructive for your encrypted history.** Here is what happens: - A password reset destroys your old key generation. The wrapped private key for that generation is wiped from the database. - All events and approvals encrypted with that old key are now permanently unreadable. The rows themselves stay in the audit trail — your operations team can still see *that* an event happened, when, on which sensor, and what decision was made — but the content fields (path, arguments, reason) read back as **"Unreadable after key reset"** in the Panel. - After the reset, a new key generation is created and all new events and approvals are encrypted under it. From that point on, unlocking with your new password lets you read everything going forward. If you still know your current password and just want to change it, use the **Change password** action in **Settings** instead. That path unwraps your private key with the old password, re-wraps it with the new one, and saves the same key — your encrypted history stays readable. There is a related action on the same Settings page called **Reset encryption key**. It is the same destructive reset described above and is guarded by a confirmation where you must type your username to proceed. Use it only when you have already lost access to your historical data and want to start clean. ## Does an administrator see my data? No. An administrator looking at your events and approvals sees only the operational metadata — sensor, timestamp, decision, container name, rule that matched — and the content fields appear as **"Redacted"**. Your workload content (paths, arguments, reasons) is not readable by an administrator without your password. What an administrator *can* still do: - See *that* an event happened, on which sensor, at which time, in which container, under which rule. - Resolve approvals on your behalf if the rule allows it (the operational metadata is enough to make a decision on simple rules). - Reset your password, which destroys your encrypted history per the section above. The reset is destructive by design — it is the *only* way to recover from a forgotten password, but it cannot be used as a back door because the act of resetting also irreversibly drops the data the admin might have wanted to read. What an administrator cannot do, even with full database access: - Read paths, arguments, or reasons from your events and approvals. Those fields are stored encrypted with your key. ## What a database leak would expose If someone walked off with a dump of the database — and nothing else — they would have your encrypted event and approval rows and your encrypted private key. They would not have your password, and your password is what unlocks the chain. The plaintext content is not in the dump and the wrapped private key cannot be unwrapped without the password. The protection is against database dumps, backup leaks, and casual operational inspection. It is not a defense against a live attacker who controls the running Panel server, can modify its code, attach a debugger to the running process, or capture a future unlock as you type your password. That class of attacker can see plaintext content the moment any user unlocks — keep your Panel server access tight. --- # Reference > Quick lookups for Imunify terminology and common questions about installing and operating the sensor. This section is the appendix to the rest of the docs. Use it when you know what you are looking for and just want a quick lookup. ## Pages in this section - **[Glossary]({{< ref "reference/glossary.md" >}})** — one-line definitions for every Imunify-specific term used across the site: Sensor, Event, Approval, Override, Rule, and the various token types. - **[FAQ]({{< ref "reference/faq.md" >}})** — short answers to the questions that come up most often: what to do when the sensor will not start, what `LSM BPF not active` means, what happens if an install token is rejected, and a few questions about this documentation site itself. --- # Glossary > One-line definitions for Imunify-specific terms used across these docs: Sensor, Event, Approval, Override, Rule, Token types, OpenClaw, and Unlock. Short definitions for the terms used across this documentation. If you want depth, each entry links to the page that covers the concept in full. **Sensor** : The small Linux system service Imunify installs on each of your hosts. It intercepts system calls in the kernel, runs each one through your active rules, and reports the result to the Panel. See the [Sensor section]({{< ref "sensor" >}}). **Event** : A single record of something the sensor caught and made a decision on (allowed, blocked, or held for approval). Events stream from each host to the Panel and show up on the Events page. See [Your first event]({{< ref "getting-started/first-event.md" >}}). **Approval** : An event whose matching rule says "ask the user before deciding." The system call pauses on the host until you allow or deny it from the Panel, Telegram, or Discord, or until the rule's timeout expires. See [Approvals page]({{< ref "panel/approvals-page.md" >}}). **Rule** : One policy entry that describes what the sensor should do when it sees a given kind of activity — for example, "block any process that writes to `/etc`." Rules live in the Panel and are pushed to your sensors over the gRPC stream. **Override** : A permanent exception you create by clicking **Allow Always** or **Deny Always** on an approval card (or directly from the Overrides page). By default an override applies to **all** the sensors on your account; you can narrow it to one named sensor. Overrides survive sensor restarts. **Install token** : A short-lived, single-use token that lets a fresh host register itself as a sensor on your Panel. You generate one from **Add Sensor** in the header dropdown; the host uses it once during install and it is consumed. See [Install the sensor]({{< ref "getting-started/install-sensor.md" >}}). **Sensor token** : The long-lived credential each registered sensor uses to authenticate to the Panel after install. The format is `sk-sensor-EXAMPLE`. You should never need to handle it by hand — the installer writes it for you and the sensor presents it on every connection. See your Panel's authenticated **API documentation** (Settings → API documentation). **User token** : Your own long-lived API token for scripting the Panel. The format is `sk-user-EXAMPLE`. You hold one per account and use it as `Authorization: Bearer ...` against the REST API. User tokens cannot unlock encrypted content — for that you need a password sign-in. See the **Authentication** section of your Panel's API documentation. **Protected agent** : A process whose activity the sensor is actively scoping rules around. Typically your application or AI agent — the thing whose system calls you want to gate. The sensor names protected agents by their process command (`comm`) or executable path so rules can be scoped to them. **OpenClaw** : The container runtime Imunify primarily targets in the current release. When you see "OpenClaw container" or "OpenClaw replica" in the docs, that is the workload your sensor is watching from the host side. The Panel exposes an OpenClaw-specific plugin for richer in-process visibility. **Enforcement tier** : How the sensor enforces a decision on the host. Tier 1 — the only supported tier today — uses LSM BPF and synchronous seccomp notification, which lets the sensor block or kill the offending syscall in real time before it executes. The older fallback tracepoint tier no longer ships. **Unlock** : The act of decrypting your private content key so the Panel can show you event and approval content. After sign-in, the Panel prompts you for your password again to unlock; the unwrapped key lives only in server memory for the current sign-in session. See [Encryption — User view]({{< ref "encryption/user-view.md" >}}). --- # FAQ > Short answers to the questions that come up most often about installing, running, and using Imunify — plus a few notes about this documentation site itself. If your question is not here, check the [Sensor troubleshooting]({{< ref "sensor/troubleshooting.md" >}}) page (for installation and host-side issues) or the [Approvals page]({{< ref "panel/approvals-page.md" >}}) (for Panel behavior). ## Installation ### My install command finishes with HTTP 410. What happened? The install token has already been used. Install tokens are single-use by design — once a host consumes one, it cannot be reused. Generate a fresh token from **Add Sensor** in the Panel header dropdown and run the new one-line command. ### I see HTTP 401 or 403 when running the install command. What now? The token has likely expired. Install tokens are valid for 24 hours from the time you generate them. Open the Panel, generate a new token from **Add Sensor**, and run the freshly-issued install command. If it still fails, double-check that the host's clock is roughly correct — large clock skew can also cause `401` from token validation. ### The sensor fails to start with `LSM BPF is not active in the running kernel`. What does that mean? Your kernel was built with LSM BPF (`CONFIG_BPF_LSM=y`) but `bpf` is not in the boot-time `lsm=` list, so the sensor cannot attach. The fix is a one-time GRUB change and a reboot. The full procedure — including the exact lines to edit and the post-reboot check — is in [Sensor troubleshooting → LSM BPF not active]({{< ref "sensor/troubleshooting.md" >}}). If instead the installer reports `Kernel does not support LSM BPF (CONFIG_BPF_LSM is not set)`, your kernel is too old or was built without LSM BPF. You need a newer kernel package — see [Sensor troubleshooting]({{< ref "sensor/troubleshooting.md" >}}) for the per-distribution commands. ### `imunifyai-sensor: command not found` after a successful-looking install The installer's download step probably failed silently. Check `/usr/local/bin/imunifyai-sensor` — if it is missing, ask an administrator for a new install token and re-run the install command. Re-running with the *same* token will fail (see HTTP 410 above). ### Where do I find the sensor's logs? ```bash journalctl -u imunifyai-sensor --since "10 min ago" --no-pager ``` Drop the `--since` window to whatever range you need. For just the errors: ```bash journalctl -u imunifyai-sensor -p err --since "1 hour ago" ``` See [Sensor operation]({{< ref "sensor/operation.md" >}}) for the full set of day-to-day commands. ## Panel and approvals ### The Panel keeps asking me to unlock. Why? Event and approval content is stored encrypted with a key derived from your password. After sign-in the Panel needs that key in memory to show you the decrypted content, so it asks for your password once per sign-in session. See [Encryption — User view]({{< ref "encryption/user-view.md" >}}) for the long version. ### I forgot my password. Can I still read my old events? No. A password reset is destructive by design: it destroys the key generation that decrypts your old content, and those rows become permanently unreadable. The audit trail (timestamps, decisions, sensors) survives, but the workload content (paths, arguments, reasons) does not. New events created after the reset are encrypted under your new password and remain readable. See [Encryption — User view]({{< ref "encryption/user-view.md" >}}). ### Can an administrator see my events? Not the content. An administrator sees the operational metadata — which sensor, which timestamp, which decision, which container — but the content fields (path, arguments, reason) appear as **Redacted** in their view. See [Encryption — User view]({{< ref "encryption/user-view.md" >}}) for what an administrator can and cannot do. ### The Approvals page shows an amber banner. Is something broken? That banner means the live connection between your browser and the Panel dropped — usually a network blip or a server restart. The Panel reconnects on its own and the banner clears as soon as it does. The cards and counts you see during the outage are the last snapshot we had. See [Approvals page]({{< ref "panel/approvals-page.md" >}}). ### How do I get approval alerts on my phone? Two options, and you can use either: - **Telegram or Discord** — link your chat account from **Settings → Notifications**. The bot DMs you a new approval card with Allow/Deny buttons you can tap. See [Notifications]({{< ref "panel/notifications.md" >}}). - **Web Push** — click the bell icon on the Approvals page and grant notification permission. Works on desktop browsers natively; iOS additionally needs the site to be added to your Home Screen as a PWA. ## This documentation site ### How is this site deployed? We publish this site as a static build. Each release pushes the rebuilt HTML/CSS to `docs.imunify.ai` behind our standard web frontend. Publishing the exact deploy pipeline is tracked as future work. ### Is there an OpenAPI spec for the REST API? We publish the REST API surface in your Panel's authenticated **API documentation** — every endpoint, its parameters, and its response shape. A machine-readable OpenAPI document and protobuf definitions for the sensor-to-server stream are tracked as future work; for now, use the human-readable reference and the example `curl` calls on each endpoint page. --- # Deployment Guide for Hosters > VM deployment guide for hosters and platform resellers — how the reseller-to-deployment-to-Sensor token chain works, and how to integrate Imunify Sensor into your VM provisioning flow. This guide explains how Hosters and Platform Resellers integrate Imunify Sensor deployment into their VM provisioning flow. For installing a single Sensor by hand (rather than through provisioning automation), see [Getting Started]({{< ref "getting-started" >}}) instead. Imunify Sensor is installed inside customer VMs to protect AI agent runtimes. The integration is designed so VM images and templates never contain long-lived Imunify for AI agents credentials. A VM receives only a short-lived, one-time deployment token during provisioning. ## Who This Guide Is For There are two common real-world models. ### Model A: Hoster Directly Provisions Customer VMs This is the most common model. The Hoster sells VMs or managed AI agent environments directly to customers. The Hoster owns the provisioning system, VM templates, first-boot scripts, and customer lifecycle. In Imunify for AI agents, the Hoster belongs to a reseller tenant and uses that tenant to create deployment sessions for its own customer VMs. In this model: - the Hoster receives or manages the reseller tenant credentials - the Hoster creates an internal provisioning token for its own automation - the Hoster provisioning system requests one deployment token per VM - the VM receives only the one-time deployment token - the VM bootstrap installs Imunify Sensor during first boot or provisioning ### Model B: Platform Reseller Delegates VM Provisioning to Partners This model is used when the Imunify for AI agents customer operates a hosting platform, virtualization platform, marketplace, or provisioning layer used by downstream Partners. The Platform Reseller usually does not provision each end-user VM directly. Instead, Partners sell or operate the VMs for their own customers. The Platform Reseller owns the Imunify for AI agents reseller tenant and issues scoped provisioning tokens to Partners. In this model: - the Platform Reseller receives or manages the reseller tenant credentials - the Platform Reseller creates one scoped provisioning token per Partner - the Partner stores its provisioning token in its own backend - the Partner requests one deployment token per VM - the Partner injects the deployment token into its VM template or first-boot provisioning flow - the VM receives only the one-time deployment token ## Shared Deployment Principle Both models use the same token flow: ```text reseller token -> provisioning token -> deployment token -> Sensor token ``` Only the deployment token reaches the VM. Reseller and provisioning tokens stay in backend systems. ## Token Types | Token | Example | Owner | Used by | Purpose | Lifetime | Put in VM image? | |---|---|---|---|---|---|---| | Reseller backend token | `sk-reseller-*` | Hoster or Platform Reseller tenant | Tenant backend | Manage tenant namespace, create users, create provisioning tokens, create deployments | Until revoked/rotated | No | | Provisioning token | `sk-deployer-*` | Hoster automation or Partner automation | Provisioning backend | Create/read/cancel deployment sessions within scoped limits | Until revoked/rotated | No | | Deployment token | `depbt_*` | One VM deployment attempt | VM bootstrap script | One-time token exchanged for Sensor token and installer config | Default 1 hour | Inject at provisioning time only | | Sensor token | `sk-sensor-*` | Installed Sensor | Sensor service | Authenticates Sensor to Imunify for AI agents | Until revoked/rotated | No; bootstrap receives it during claim | | Report token | `deprt_*` | VM bootstrap | Bootstrap report step | Reports install progress/failure after claim | Default 30 minutes | No; bootstrap receives it during claim | `sk-reseller-*` and `sk-deployer-*` are backend credentials. Treat them as secrets. `depbt_*` is short-lived and single-use. It is the only token that should be passed to a VM. Default lifetimes are deployment settings: - `depbt_*` deployment token: `token_lifetime_seconds`, default `3600` seconds. - `deprt_*` report token: `report_token_lifetime_seconds`, default `1800` seconds. - claimed-but-never-registered cleanup: `stale_claimed_after_seconds`, default `1800` seconds. ## Component Relationship ```mermaid flowchart LR subgraph Imunify["panel.imunify.ai"] %% Panel declared first so Mermaid renders it at the top of the %% subgraph — the "events / heartbeats" arrow from Sensor lands on %% it directly without routing through the Install Bootstrap box %% (which made it look like events flow into the install API). Panel["Panel / Stats / Events"] API["Reseller API"] InstallAPI["Install Bootstrap + Claim API"] end subgraph Tenant["Hoster or Platform Reseller Tenant"] Backend["Tenant backend"] TenantProvisioning["Tenant provisioning system"] end subgraph Partner["Partner Infrastructure (Model B only)"] PartnerBackend["Partner provisioning backend"] end subgraph VM["Customer VM"] FirstBoot["VM first-boot script"] Sensor["Imunify Sensor"] Runtime["AI agent runtime"] end Backend -- "sk-reseller-*" --> API Backend -- "creates sk-deployer-*" --> API TenantProvisioning -- "sk-deployer-* (Model A)" --> API PartnerBackend -- "sk-deployer-* (Model B)" --> API TenantProvisioning -- "depbt_*" --> FirstBoot PartnerBackend -- "depbt_*" --> FirstBoot FirstBoot -- "claim depbt_*" --> InstallAPI FirstBoot -- "verified installer" --> Sensor Sensor -- "events / heartbeats" --> Panel Sensor -- "protects" --> Runtime ``` ## Token Sequence ```mermaid sequenceDiagram participant I as Imunify for AI agents API participant T as Hoster or Platform Reseller backend participant P as Hoster/Partner provisioning backend participant V as Customer VM participant S as Imunify Sensor I->>T: Issue sk-reseller-* out of band T->>I: POST /api/v1/reseller/provisioning-tokens I-->>T: Return sk-deployer-* once P->>I: POST /api/v1/reseller/sensor-deployments using sk-deployer-* I-->>P: Return depbt_* and bootstrap command P->>V: Inject depbt_* into VM provisioning data V->>I: POST /api/v1/install/deployments/claim with depbt_* I-->>V: Return sk-sensor-*, installer URL, checksum URL, config V->>I: Download installer and checksum V->>V: Verify SHA256 V->>S: Install and start Sensor S->>I: Register and send heartbeats/events ``` ## Model A: Hoster Directly Provisions Customer VMs Use this model when your company sells VMs or managed AI agent environments directly to customers and controls the VM provisioning workflow. ### Responsibilities The Hoster: - stores `sk-reseller-*` only in its backend secrets store - creates an internal `sk-deployer-*` for VM provisioning automation - creates one deployment session per VM - injects the returned `depbt_*` into the VM at provisioning time - installs Imunify Sensor during first boot or image customization - monitors deployments, Sensor status, protected runtime count, and events ### 1. Create an Internal Provisioning Token This call is made from the Hoster backend. ```bash curl -sS -X POST "https://panel.imunify.ai/api/v1/reseller/provisioning-tokens" \ -H "Authorization: Bearer sk-reseller-..." \ -H "Content-Type: application/json" \ -d '{ "label": "main VM provisioning", "scopes": [ "sensor_deployments:create", "sensor_deployments:read", "sensor_deployments:cancel" ], "allowed_external_user_prefixes": ["customer-"], "allowed_host_external_id_prefixes": ["vm-"], "rate_limit_per_minute": 120, "rate_limit_per_hour": 5000 }' ``` The response includes the raw `sk-deployer-*` once. Store it in the Hoster provisioning backend secret store. ### 2. Request a Deployment Token During VM Creation This call is made by the Hoster provisioning system while creating a VM. ```bash curl -sS -X POST "https://panel.imunify.ai/api/v1/reseller/sensor-deployments" \ -H "Authorization: Bearer sk-deployer-..." \ -H "Content-Type: application/json" \ -H "Idempotency-Key: vm-create-vm-12345" \ -d '{ "external_user_id": "customer-987", "external_user_display_name": "Customer 987", "host_external_id": "vm-12345", "label": "customer-987-vm-12345", "target_processes": ["ai-agent-runtime"], "metadata": { "template": "linux-ai-agent-template", "plan": "ai-agent-protected" } }' ``` The response includes a one-time deployment token and a ready-to-run bootstrap command: ```json { "deployment_id": "dep_...", "deployment_token": "depbt_...", "expires_at": "2026-05-25T11:00:00+00:00", "bootstrap_command": "curl -fsSL https://panel.imunify.ai/api/v1/install/bootstrap.sh | sudo bash -s -- --deployment-token depbt_...", "status_url": "/api/v1/reseller/sensor-deployments/dep_..." } ``` Use `deployment_id` for later status checks, rotation, and cancellation. See How do I check the status of one VM deployment? for the full status example. Use `expires_at` to make sure the VM claims the deployment token before it expires. ### 3. Inject the Deployment Token Into the VM Example cloud-init: ```yaml #cloud-config runcmd: - | set -euo pipefail DEPLOYMENT_TOKEN="${IMUNIFY_DEPLOYMENT_TOKEN}" curl -fsSL https://panel.imunify.ai/api/v1/install/bootstrap.sh \ | sudo bash -s -- --deployment-token "${DEPLOYMENT_TOKEN}" ``` The provisioning system replaces `${IMUNIFY_DEPLOYMENT_TOKEN}` with the fresh `depbt_*` for this VM. ## Model B: Platform Reseller With Partners Use this model when your company operates a platform and downstream Partners provision or sell VMs to their own customers. ### Responsibilities The Platform Reseller: - stores `sk-reseller-*` only in its backend secrets store - creates one scoped `sk-deployer-*` per Partner - restricts each Partner token with `partner_id`, ID prefixes, scopes, and rate limits - monitors Partner deployment usage, protected runtime count, and events - revokes or rotates Partner provisioning tokens when needed The Partner: - stores its `sk-deployer-*` only in its own provisioning backend - creates one deployment session per VM - injects the returned `depbt_*` into the VM at provisioning time - installs Imunify Sensor during first boot or image customization - never receives `sk-reseller-*` ### 1. Platform Reseller Creates a Partner Provisioning Token This call is made from the Platform Reseller backend. ```bash curl -sS -X POST "https://panel.imunify.ai/api/v1/reseller/provisioning-tokens" \ -H "Authorization: Bearer sk-reseller-..." \ -H "Content-Type: application/json" \ -d '{ "partner_id": "partner-a", "label": "Partner provisioning", "scopes": [ "sensor_deployments:create", "sensor_deployments:read", "sensor_deployments:cancel" ], "allowed_external_user_prefixes": ["partner-a-customer-"], "allowed_host_external_id_prefixes": ["partner-a-vm-"], "rate_limit_per_minute": 60, "rate_limit_per_hour": 1000, "metadata": { "owner": "partner-a" } }' ``` The response includes the raw `sk-deployer-*` once. Give it to the Partner through a secure onboarding process. The Partner stores it in its provisioning backend secret store. If a Partner cannot see or manage a deployment later, check the scope rules in What if the Partner cannot access a deployment?. ### 2. Partner Requests a Deployment Token Per VM This call is made by the Partner provisioning backend while creating a VM. ```bash curl -sS -X POST "https://panel.imunify.ai/api/v1/reseller/sensor-deployments" \ -H "Authorization: Bearer sk-deployer-..." \ -H "Content-Type: application/json" \ -H "Idempotency-Key: vm-create-partner-a-vm-12345" \ -d '{ "external_user_id": "partner-a-customer-987", "external_user_display_name": "Customer 987", "host_external_id": "partner-a-vm-12345", "label": "customer-987-vm-12345", "partner_id": "partner-a", "target_processes": ["ai-agent-runtime"], "metadata": { "template": "linux-ai-agent-template", "plan": "ai-agent-protected" } }' ``` The response includes `depbt_*` and `bootstrap_command`. The Partner injects `depbt_*` into the VM provisioning data. The VM does not need to know whether the deployment came from Model A or Model B. Use the returned `deployment_id` for Partner-side status checks, rotation, and cancellation. See How do I check the status of one VM deployment?. ## VM Bootstrap Options The VM bootstrap step can use either: - Imunify for AI agents's ready-made bootstrap script: `https://panel.imunify.ai/api/v1/install/bootstrap.sh` - a custom bootstrap script maintained by the Hoster or Partner The ready-made script is recommended unless the provisioning system needs custom logging, proxy setup, OS preparation, or tighter integration with an existing template framework. ### Minimal VM Bootstrap Script This script belongs in a VM template. The token value must be supplied dynamically by the provisioning layer. ```bash #!/usr/bin/env bash set -euo pipefail : "${IMUNIFY_DEPLOYMENT_TOKEN:?missing IMUNIFY_DEPLOYMENT_TOKEN}" case "$IMUNIFY_DEPLOYMENT_TOKEN" in depbt_*) ;; *) echo "Expected depbt_* deployment token" >&2 exit 2 ;; esac curl -fsSL https://panel.imunify.ai/api/v1/install/bootstrap.sh \ | sudo bash -s -- --deployment-token "$IMUNIFY_DEPLOYMENT_TOKEN" ``` ## Bootstrap Script Responsibilities Whether you use the Imunify for AI agents bootstrap script or a custom script, the bootstrap must perform the same security-sensitive flow: 1. Validate that the provided token is a `depbt_*` deployment token, not a reseller, provisioning, user, admin, or Sensor API token. 2. Claim `depbt_*` via `/api/v1/install/deployments/claim`. 3. Receive `server_url`, `sk-sensor-*`, installer URL, checksum URL, label, report token, and target processes. 4. Download the Sensor installer. 5. Download the installer `.sha256`. 6. Verify the installer SHA256 before execution. 7. Run the verified installer with `IMUNIFYAI_*` environment variables. 8. Optionally report install progress/failure with the returned `deprt_*` report token. The bootstrap script is responsible for safe download and handoff. The installer is responsible for changing the VM. ### What the Sensor Installer Does The Sensor installer is the host-side setup script that performs the actual installation work: 1. Validates host prerequisites such as Linux support, required tools, BTF, and active eBPF LSM support. 2. Installs the Imunify Sensor binary and configuration. 3. Writes the Sensor token and server URL received from the deployment claim. 4. Installs and registers the AI runtime integration plugin when that integration is enabled for the deployment profile. 5. Installs systemd units for the Sensor and upgrade/rollback helpers. 6. Starts or restarts the required services. 7. Leaves the Sensor running so it can register with Imunify for AI agents and begin sending heartbeats/events. The bootstrap script does not embed reseller credentials. It only uses the one-time `depbt_*` token and exchanges it for a VM-specific `sk-sensor-*` token. For a full custom bootstrap example, see Appendix A: Custom Bootstrap Script. For installation failure handling, see What if VM installation fails because prerequisites are missing?. ## Identifier Mapping Use stable identifiers from the Hoster or Partner system. | Field | Meaning | Model A example | Model B example | |---|---|---|---| | `external_user_id` | Customer/account ID in the provisioning system | `customer-987` | `partner-a-customer-987` | | `host_external_id` | VM ID in the provisioning system | `vm-12345` | `partner-a-vm-12345` | | `partner_id` | Partner namespace for Model B provisioning | omitted | `partner-a` | | `label` | Human-readable Sensor/deployment label | `customer-987-vm-12345` | `customer-987-vm-12345` | Use prefixes in Model B. They make scoping and audit trails clear. ## Idempotency-Key Use `Idempotency-Key` on `POST /api/v1/reseller/sensor-deployments`. In simple words, the idempotency key tells Imunify for AI agents: "if this exact VM creation request is retried, treat it as the same request, not a new VM." The key is created by the system that creates the VM: - in Model A, the Hoster provisioning backend creates it - in Model B, the Partner provisioning backend creates it Good values are stable IDs from the VM provisioning workflow: ```text Idempotency-Key: vm-create-vm-12345 Idempotency-Key: vm-create-partner-a-vm-12345 Idempotency-Key: provision-order-7788-vm-12345 ``` This makes retries safe and avoids duplicate deployment sessions when a worker restarts, a network call times out, or the caller loses the first response. Important: the raw `depbt_*` token is returned only when the deployment session is first created. If the first API response is lost and a retry returns the same deployment by idempotency key, the response will not include the raw deployment token again. If the VM still needs a token and the deployment is still `pending`, rotate the deployment token with `POST /api/v1/reseller/sensor-deployments/{deployment_id}/rotate-token`. For practical recovery cases, see Should I rotate a token or create a new deployment? and What if I get 409 Idempotency-Key payload mismatch?. ## Status and Stats The deployment-create response contains `deployment_id`, for example `dep_...`. This is the stable ID of the deployment session. Use it later to check status, rotate a lost deployment token, or cancel an unfinished deployment. The response also contains: - `status_url`, which is the exact status path for that deployment - `expires_at`, which is the UTC timestamp when the `depbt_*` deployment token expires Check deployment status with: ```bash curl -sS \ -H "Authorization: Bearer sk-deployer-..." \ "https://panel.imunify.ai/api/v1/reseller/sensor-deployments/dep_..." ``` Here `dep_...` means the `deployment_id` returned by `POST /api/v1/reseller/sensor-deployments`. For a full explanation, see How do I check the status of one VM deployment?. Use reseller stats endpoints to monitor protected AI runtimes: - current active count: `GET /api/v1/reseller/stats/protected-agents/current` - month-level distinct count: `GET /api/v1/reseller/stats/protected-agents/monthly?month=YYYY-MM` - daily rows for a calendar month: `GET /api/v1/reseller/protected-agents?month=YYYY-MM` Detailed examples and failure-handling scenarios are in Appendix B: Operational Q&A and Troubleshooting. Monthly usage examples are in How do I get protected runtime usage for a calendar month?. Common deployment statuses: | Status | Meaning | |---|---| | `pending` | Deployment token was created but has not been claimed by a VM. | | `claimed` | VM exchanged `depbt_*` for `sk-sensor-*` and installer config. | | `registered` | Sensor installed and registered with Imunify for AI agents. | | `active` | Sensor is active and reporting. | | `failed` | Claim or installation failed. Check `status_reason` and `last_claim_error`. | | `expired` | `depbt_*` expired before claim. | | `cancelled` | Deployment was cancelled before completion. | Response fields such as `deployment_token`, `status_reason`, `cancelled_at`, or `last_claim_error` can be `null`. That is normal: they are populated only when that value is relevant for the current deployment state. See How do I check the status of one VM deployment? for examples of those fields in context. The default `depbt_*` lifetime is 1 hour; see What if the deployment token expired?. ## Operational Guidance Scope provisioning tokens tightly: - use `partner_id` for Partner integrations - use `allowed_external_user_prefixes` - use `allowed_host_external_id_prefixes` - configure rate limits - revoke unused provisioning tokens Do not store long-lived credentials in: - VM templates - cloud-init user data - customer-visible metadata - support scripts copied to the VM Only the one-time `depbt_*` token should be injected into the VM, and only at VM creation time. If the token is lost before the VM claims it, use How do I rotate a lost deployment token?. If the deployment should no longer be used, use How do I cancel an unfinished deployment?. ## Security Checklist - `sk-reseller-*` is stored only in the Hoster or Platform Reseller backend. - `sk-deployer-*` is stored only in the Hoster or Partner provisioning backend. - VM image contains only static bootstrap logic. - VM receives only `depbt_*`, and only for its own deployment. - Installer checksum is verified before execution. - Provisioning tokens are scoped with prefixes and rate limits. - Deployment creation uses `Idempotency-Key`. - `external_user_id` and `host_external_id` are stable and unique. ## Appendix A: Custom Bootstrap Script This example shows what a Hoster or Partner-maintained bootstrap script must do if it does not call Imunify for AI agents's ready-made `/api/v1/install/bootstrap.sh` script. Use this approach only when the integration needs custom behavior around the install flow. For most integrations, the simpler and safer approach is to keep the VM template small and call the Imunify for AI agents bootstrap script. Requirements for a custom bootstrap: - accept only `depbt_*` deployment tokens - call `/api/v1/install/deployments/claim` - never print or log the returned `sk-sensor-*` - download both installer and checksum from the claim response - verify SHA256 before executing the installer - pass the returned values to the installer through `IMUNIFYAI_*` environment variables - optionally report success/failure with `/api/v1/install/deployments/{id}/report` and the returned `deprt_*` report token Example: ```bash #!/usr/bin/env bash set -euo pipefail : "${IMUNIFY_DEPLOYMENT_TOKEN:?missing IMUNIFY_DEPLOYMENT_TOKEN}" IMUNIFY_BASE_URL="${IMUNIFY_BASE_URL:-https://panel.imunify.ai}" case "$IMUNIFY_DEPLOYMENT_TOKEN" in depbt_*) ;; sk-admin-*|sk-reseller-*|sk-deployer-*|sk-user-*|sk-sensor-*) echo "Refusing API token where depbt_* deployment token is required" >&2 exit 2 ;; *) echo "Expected depbt_* deployment token" >&2 exit 2 ;; esac command -v curl >/dev/null 2>&1 || { echo "curl is required" >&2 exit 1 } command -v jq >/dev/null 2>&1 || { echo "jq is required" >&2 exit 1 } command -v sha256sum >/dev/null 2>&1 || { echo "sha256sum is required" >&2 exit 1 } work_dir="$(mktemp -d /tmp/imunify-bootstrap.XXXXXX)" cleanup() { rm -rf "$work_dir"; } trap cleanup EXIT claim_json="$work_dir/claim.json" installer="$work_dir/sensor-installer.sh" installer_sha="$work_dir/sensor-installer.sh.sha256" hostname_value="$(hostname 2>/dev/null || printf unknown)" arch_value="$(uname -m 2>/dev/null || printf unknown)" curl -fsS -X POST "${IMUNIFY_BASE_URL%/}/api/v1/install/deployments/claim" \ -H "Content-Type: application/json" \ -d "$(jq -nc \ --arg token "$IMUNIFY_DEPLOYMENT_TOKEN" \ --arg hostname "$hostname_value" \ --arg arch "$arch_value" \ '{ deployment_token: $token, hostname: $hostname, arch: $arch, os: "linux", bootstrap_version: "custom-v1" }')" \ -o "$claim_json" deployment_id="$(jq -r '.deployment_id' "$claim_json")" server_url="$(jq -r '.server_url' "$claim_json")" sensor_token="$(jq -r '.sensor_token' "$claim_json")" installer_url="$(jq -r '.installer_url' "$claim_json")" installer_sha256_url="$(jq -r '.installer_sha256_url' "$claim_json")" report_token="$(jq -r '.report_token // empty' "$claim_json")" target_processes="$(jq -r '(.target_processes // []) | join(",")' "$claim_json")" sensor_label="$(jq -r '.label // ""' "$claim_json")" for value_name in deployment_id server_url sensor_token installer_url installer_sha256_url; do value="${!value_name:-}" if [ -z "$value" ] || [ "$value" = "null" ]; then echo "Claim response missing ${value_name}" >&2 exit 1 fi done report_status() { status="$1" phase="$2" reason="${3:-}" if [ -z "$report_token" ]; then return 0 fi curl -fsS -X POST \ "${IMUNIFY_BASE_URL%/}/api/v1/install/deployments/${deployment_id}/report" \ -H "Content-Type: application/json" \ -d "$(jq -nc \ --arg token "$report_token" \ --arg status "$status" \ --arg phase "$phase" \ --arg reason "$reason" \ --arg hostname "$hostname_value" \ '{ report_token: $token, status: $status, phase: $phase, reason: (if $reason == "" then null else $reason end), hostname: $hostname, bootstrap_version: "custom-v1" }')" >/dev/null 2>&1 || true } report_status in_progress download curl -fsSL "$installer_url" -o "$installer" curl -fsSL "$installer_sha256_url" -o "$installer_sha" expected_hash="$(cut -d' ' -f1 "$installer_sha")" actual_hash="$(sha256sum "$installer" | cut -d' ' -f1)" if [ -z "$expected_hash" ] || [ "$expected_hash" != "$actual_hash" ]; then report_status failed verify "installer sha256 mismatch" echo "FATAL: installer sha256 mismatch" >&2 exit 1 fi export IMUNIFYAI_SERVER_URL="$server_url" export IMUNIFYAI_SENSOR_TOKEN="$sensor_token" export IMUNIFYAI_TARGET_PROCESSES="$target_processes" export IMUNIFYAI_SENSOR_LABEL="$sensor_label" export IMUNIFYAI_INSTALLER_URL="$installer_url" export IMUNIFYAI_INSTALLER_SHA256_URL="$installer_sha256_url" report_status in_progress install if bash "$installer"; then report_status succeeded install else rc="$?" report_status failed install "installer exited with ${rc}" exit "$rc" fi ``` The custom script above is intentionally more verbose than the minimal VM bootstrap. It shows all moving parts so an integrator can adapt the flow while preserving the security properties of the Imunify for AI agents-provided bootstrap script. ## Appendix B: Operational Q&A and Troubleshooting This appendix is organized by common operating tasks. Use it when the VM provisioning flow needs to check status, report usage, recover from a failed installation, retry a deployment, or troubleshoot Partner access. ### How do I check the status of one VM deployment? Use the `deployment_id` returned by deployment creation. Deployment IDs start with `dep_`. In examples, `dep_...` means "replace this with the real `deployment_id`." Deployment creation returns both `deployment_id` and `status_url`: ```json { "deployment_id": "dep_...", "deployment_token": "depbt_...", "expires_at": "2026-05-25T11:00:00+00:00", "status_url": "/api/v1/reseller/sensor-deployments/dep_..." } ``` Use that `deployment_id` in status, rotate, cancel, and troubleshooting calls. Use `expires_at` to confirm whether the VM can still claim the `depbt_*` token. Status request: ```bash curl -sS \ -H "Authorization: Bearer sk-deployer-..." \ "https://panel.imunify.ai/api/v1/reseller/sensor-deployments/dep_..." ``` Example successful response: ```json { "deployment_id": "dep_...", "deployment_token": null, "deployment_token_prefix": "depbt_...", "reseller_id": "res_...", "partner_id": "partner-a", "user_id": "usr_...", "external_user_id": "partner-a-customer-987", "host_external_id": "partner-a-vm-12345", "sensor_id": "sen_...", "label": "customer-987-vm-12345", "target_processes": ["ai-agent-runtime"], "status": "registered", "status_reason": null, "created_at": "2026-05-25T10:00:00+00:00", "expires_at": "2026-05-25T11:00:00+00:00", "claimed_at": "2026-05-25T10:01:00+00:00", "registered_at": "2026-05-25T10:02:00+00:00", "cancelled_at": null, "metadata": {}, "claim_attempt_count": 1, "last_claim_error": null } ``` Why is `deployment_token` null here? This is expected. The raw `depbt_*` deployment token is shown only once, when the deployment is created or when a pending deployment token is rotated. Status responses intentionally do not return the raw token. They show only `deployment_token_prefix` so operators can identify which token was issued without exposing the secret again. Why is `status_reason` null? `status_reason` is populated only when there is something to explain, such as a failure or cancellation reason. For normal states like `pending`, `claimed`, `registered`, or `active`, `status_reason: null` means there is no error reason. The same applies to `cancelled_at`, `last_claim_error`, and similar fields: `null` means that condition has not happened. If the provisioning system lost the raw `depbt_*` before the VM claimed it, rotate the token while the deployment is still `pending`; see How do I rotate a lost deployment token?. What does `expires_at` mean? For a `pending` deployment, `expires_at` is the time when the one-time `depbt_*` deployment token stops being claimable. The default lifetime is 1 hour from deployment creation or token rotation. Create the deployment close to VM boot, not hours before the VM will be provisioned. ### How do I get current protected runtime count? Use: ```bash curl -sS \ -H "Authorization: Bearer sk-reseller-..." \ "https://panel.imunify.ai/api/v1/reseller/stats/protected-agents/current" ``` Example response: ```json { "reseller_id": "res_...", "usage_date": "2026-05-25", "current_active_protected_agents": 1 } ``` ### How do I get protected runtime usage for a calendar month? There are two useful monthly views. For the month-level distinct count, use: ```bash curl -sS \ -H "Authorization: Bearer sk-reseller-..." \ "https://panel.imunify.ai/api/v1/reseller/stats/protected-agents/monthly?month=2026-05" ``` Example response: ```json { "month": "2026-05", "reseller_id": "res_...", "distinct_protected_agents": 17, "users": [ { "user_id": "usr_...", "external_user_id": "customer-987", "distinct_protected_agents": 3, "max_protected_agents": 10, "entitlement_status": "ok" } ], "quality": { "ok": 17, "conflict": 0, "missing_owner": 0 } } ``` For daily rows within the calendar month, use: ```bash curl -sS \ -H "Authorization: Bearer sk-reseller-..." \ "https://panel.imunify.ai/api/v1/reseller/protected-agents?month=2026-05" ``` This returns one row per observed protected runtime per UTC usage day, with pagination fields: ```json { "protected_agents": [ { "usage_date": "2026-05-25", "user_id": "usr_...", "external_user_id": "customer-987", "sensor_id": "sen_...", "protected_agent_id": "ai-agent-runtime", "runtime_kind": "ai-agent", "attribution_quality": "ok", "last_runtime_state": "active", "last_seen_at": "2026-05-25T10:05:00+00:00", "host_native": true, "plugin_version": "v0.2.19" } ], "count": 1, "total": 1, "limit": 500, "offset": 0 } ``` Use the monthly stats endpoint for a summary count. Use the `protected-agents?month=YYYY-MM` endpoint when you need daily detail or an audit trail. ### How do I get currently active protected runtimes? ```bash curl -sS \ -H "Authorization: Bearer sk-reseller-..." \ "https://panel.imunify.ai/api/v1/reseller/protected-agents?current=true" ``` Example response: ```json { "protected_agents": [ { "usage_date": "2026-05-25", "user_id": "usr_...", "external_user_id": "partner-a-customer-987", "sensor_id": "sen_...", "sensor_installation_id": "sensor-installation-id", "protected_agent_id": "ai-agent-runtime", "runtime_kind": "ai-agent", "attribution_quality": "ok", "attribution_source": "runtime-integration", "last_runtime_state": "active", "last_seen_at": "2026-05-25T10:05:00+00:00", "last_runtime_scope_id": "host-native", "container_name": null, "host_native": true, "plugin_version": "v0.2.19" } ], "count": 1, "total": 1, "limit": 500, "offset": 0 } ``` ### What if VM installation fails because prerequisites are missing? Example: the Sensor installer fails because the VM kernel configuration does not have required prerequisites such as active eBPF LSM support. Recommended flow: 1. Check deployment status: ```bash curl -sS \ -H "Authorization: Bearer sk-deployer-..." \ "https://panel.imunify.ai/api/v1/reseller/sensor-deployments/dep_..." ``` 2. Inspect `status`, `status_reason`, and `last_claim_error`. 3. Fix the VM template, image, kernel boot flags, package set, or provisioning step that caused the prerequisite failure. 4. If the deployment is still `pending`, rotate the deployment token and retry the same deployment. 5. If the deployment is `claimed` but the Sensor never registered, cancel the deployment and create a fresh deployment for the same VM after fixing the prerequisite issue. 6. If the deployment is `registered` or `active`, do not cancel it. The Sensor has already registered. ### Should I rotate a token or create a new deployment? Use this rule: | Situation | Action | |---|---| | Deployment is `pending` and the raw `depbt_*` was lost | Rotate token. | | Deployment is `pending` and VM never started provisioning | Rotate token or cancel and recreate. | | Deployment is `claimed` and install failed before registration | Cancel and create a fresh deployment after fixing the VM issue. | | Deployment is `expired` | Create a fresh deployment. | | Deployment is `cancelled` or `failed` | Create a fresh deployment. | | Deployment is `registered` or `active` | Do not rotate or cancel; manage the installed Sensor. | ### How do I rotate a lost deployment token? Rotation works only while the deployment is `pending`. ```bash curl -sS -X POST \ -H "Authorization: Bearer sk-deployer-..." \ "https://panel.imunify.ai/api/v1/reseller/sensor-deployments/dep_.../rotate-token" ``` The response includes a new raw `deployment_token` and a new `bootstrap_command`. The old `depbt_*` stops working. ### How do I cancel an unfinished deployment? Cancel when the deployment should no longer be used. Cancellation is allowed for unfinished deployments and rejects registered/active deployments. ```bash curl -sS -X POST \ -H "Authorization: Bearer sk-deployer-..." \ -H "Content-Type: application/json" \ "https://panel.imunify.ai/api/v1/reseller/sensor-deployments/dep_.../cancel" \ -d '{"reason":"VM provisioning failed before Sensor registration"}' ``` Example response: ```json { "status": "cancelled", "deployment_id": "dep_..." } ``` ### What if the VM was recreated with the same VM ID? Use the same `host_external_id` if it represents the same VM identity in your system. If an old deployment is still `pending` or `claimed`, cancel it before creating the replacement deployment. If an old Sensor already registered for the previous VM, treat that as an installed Sensor lifecycle action rather than a deployment-token retry. ### What if I get `409 Active deployment already exists for host_external_id`? There is already an unfinished or active deployment for that VM ID. Recommended actions: 1. Query deployments for that VM: ```bash curl -sS \ -H "Authorization: Bearer sk-deployer-..." \ "https://panel.imunify.ai/api/v1/reseller/sensor-deployments?host_external_id=vm-12345" ``` 2. If the existing deployment is still valid and `pending`, rotate its token. 3. If it is stale and unfinished, cancel it and create a new deployment. 4. If it is `registered` or `active`, the Sensor is already installed for that VM identity. ### What if I get `409 Idempotency-Key payload mismatch`? The same `Idempotency-Key` was reused with different VM/customer identifiers. This usually means the provisioning system generated the key too broadly. Fix the provisioning code so the idempotency key uniquely identifies one VM creation operation, for example: ```text Idempotency-Key: vm-create- ``` Do not reuse the same idempotency key for different VMs. ### What if the Partner cannot access a deployment? Partner provisioning tokens are scoped. A Partner can access only deployments inside its own `partner_id` and allowed ID prefixes. A deployment outside that scope may return `404` instead of exposing that it exists. Check: - the Partner used the correct `sk-deployer-*` - `partner_id` matches the provisioning token scope - `external_user_id` starts with an allowed prefix - `host_external_id` starts with an allowed prefix - the token has the needed scope, such as `sensor_deployments:read`, `sensor_deployments:create`, or `sensor_deployments:cancel` ### What if the deployment is stuck in `claimed`? `claimed` means the VM exchanged `depbt_*` for `sk-sensor-*`, but the Sensor did not register yet. Common causes: - installer failed after claim - VM lost network access during installation - prerequisites were missing - VM was destroyed before Sensor registration - custom bootstrap did not run the installer or did not pass `IMUNIFYAI_*` values correctly Recommended action: 1. Inspect the VM provisioning logs. 2. Check deployment status for `status_reason` and `last_claim_error`. 3. Fix the VM image/template/provisioning issue. 4. Cancel the stuck deployment if it will not complete. 5. Create a fresh deployment for the VM. ### What if the deployment token expired? The one-time `depbt_*` deployment token expires at the response's `expires_at` timestamp. The default lifetime is 1 hour, controlled by `token_lifetime_seconds`. If the deployment is still `pending` but `expires_at` is in the past, create a fresh deployment session. Expired deployment tokens cannot be claimed. If the deployment is still `pending` and not expired but the raw token was lost, rotate it instead. The `deprt_*` report token is separate. It is returned after deployment claim and expires after 30 minutes by default, controlled by `report_token_lifetime_seconds`. ### What if only install progress reporting is broken? If the VM installed successfully but the bootstrap report token was leaked or should no longer be accepted, revoke only the report token: ```bash curl -sS -X POST \ -H "Authorization: Bearer sk-deployer-..." \ "https://panel.imunify.ai/api/v1/reseller/sensor-deployments/dep_.../revoke-report-token" ``` This affects only future install reports for that deployment. It does not stop an installed Sensor.