Skip to content
NAILS on GitHub
These docs describe NAILS 0.3, published so far as the release candidate v0.3.0-rc.3. They do not apply to v0.2.0, the latest stable release. Download and verify NAILS 0.3

Check your setup

The end-to-end tests check the leak claims of the threat model in virtual machines. Your machine, decoy, backend and desktop are different, so NAILS ships three tools to check them yourself before you rely on it:

  • nails check runs on the decoy before you activate. It reports readiness to activate and changes nothing.
  • nails verify looks for known NAILS artifacts. A clean result is evidence, not proof.
  • nails rehearse plants a canary in a hidden session and searches the raw decoy disk for it after the session ends.

verify and rehearse also list what they could not check. Your first session shows where check and verify fit in a session.

Run it on the decoy, in a shell with history off, before you activate. It runs the same pre-flight checks as activate, changes nothing and prints what activation would do.

Terminal window
sudo /mnt/data/nails check

It reports each check, the effective configuration, the decision for each decoy filesystem and the decoy state the hidden session would inherit. When a check fails, the hint under it names the fix, and check exits with code 4. If the hidden volume is not mounted, the checks that need it are skipped with that reason instead of failing too.

  • The platform row names the host NAILS detected. A later Debian or Ubuntu release that NAILS has not been tested on gets a warning. The row fails on a host that activate refuses, such as Fedora or Arch.
  • The inherited decoy state lists what needs review: autostart entries and user services that you or third-party software added, lingering, display-manager autologin and known trace writers such as sync, backup and messaging clients. The desktop’s and the distribution’s own components are counted, not listed, and nails check --json lists every entry. Both the desktop components and the known trace writers are matched by name only. An autologin stays listed while the decoy has it, even after you turn it off for the hidden session. In the default shared-home mode, a line of its own says that the hidden session starts in the decoy home.
Excerpt of real nails check output
…
Pre-flight checks:
[PASS] hidden-volume: Hidden volume mounted at /mnt/data
[PASS] backend-entries: No fstab or crypttab entry opens or mounts the volume at /mnt/data
[PASS] session-state: on the plain decoy root
[PASS] systemd-version: systemd 261 supports soft-reboot
[PASS] soft-reboot-units: soft-reboot.target and systemd-soft-reboot.service are installed
[PASS] decoy-root: the decoy root (ext4 on /dev/vda) can be mounted again for the way back
…
[PASS] upper-fs-capability: Hidden root at /mnt/data can host an overlayfs upper layer
[PASS] backend-detect: Hidden root backend: ext4 on LUKS mapping dm-0 on block device vdb; no backend-specific hazards detected
…
[PASS] sleep-policy: host will not suspend an active session (HandleLidSwitch=lock, IdleAction=ignore)
[PASS] decoy-persistence: decoy keeps no durable trace of the hidden session
…
Inherited decoy state:
[INFO] shared-home: the hidden session starts in the decoy home, so the decoy's accounts, keys, browser profiles and shell history are live in it (persona.mode = "fresh-home" starts it in an empty home)
[PASS] nothing to review
[INFO] 4 components of the desktop or distribution not listed (--json lists every entry)
[PASS] Ready to activate (24 passed, 0 warnings, 0 failed)

Captured on 2026-10-16 in the session-readme-captures NixOS VM test, which runs sudo /mnt/data/nails check --overlay-only and fails if check prints a warning. --overlay-only skips the checks for a hidden NixOS build. The capture predates the platform row. Lines shown as … are left out, and the full capture is in the NAILS repository. The test VM is an amnesic decoy as the decoy requirements describe it, with a volatile journal, no swap and systemd-coredump set to keep no core dump on the disk (Storage=none with ProcessSizeMax=0). A closed lid locks the screen. Its kernel command line does not set init_on_free=1, though. The memory-wipe row, one of the lines left out here, recommends adding it, as the decoy requirements do. Prepare the decoy shows how to set up yours.

Run it inside the hidden session. It looks for known NAILS artifacts and never claims that a system is clean. A run with no findings says “No known artifacts found” and “This is not proof of absence”, then lists the areas it did not check. A clean result is evidence against known artifacts, not proof that the decoy is untouched.

Terminal window
sudo /mnt/data/nails verify
sudo /mnt/data/nails verify --deep

--deep adds homes, shell histories, temporary and log directories, and a search of the systemd journal for the tool name, the configuration file name, cryptsetup and the hidden volume’s path. sudo logs every command run with it, so its records of your own commands match too, such as the unlock, the mount and this verify run. verify names them as sudo’s records. While the journal is in RAM they are notes, and so is every entry of the session’s own journal inside the session, because the way back or a power-off removes them. When the journal keeps files on disk, every entry is a warning.

Inside the session, what belongs to the session is not reported as residue. Its runtime files in /run, the mounted hidden volume and its active state make one note ([INFO]). A NAILS path that the hidden layer holds, such as the link nails init stages on NixOS, is a note too. The hidden layer hides whatever the decoy holds beneath such a path, so check the decoy itself offline, as described below. The header counts warnings and critical findings, not notes.

The exit code is 0 when no known artifacts were found, 10 for warnings, 11 for critical findings, 1 when the scan could not run and 3 when the configuration could not be loaded. The exit code table on the Commands page lists every code.

To inspect the decoy itself, mount its image or partition read-only and scan it offline, from the hidden side:

Terminal window
sudo /mnt/data/nails verify --offline --root /mnt/decoy

Every path is then read under --root, and the live-only checks are skipped: running processes, the kernel mount table, open block devices and platform package drift. Mount the decoy with mount -o ro. A decoy partition you mount by hand in the hidden session is outside the overlay and is written directly.

--examiner adds the setup traces an examiner looks for, grouped by what an examiner would conclude from them. --disk names the raw disk or image behind the mounted root, so it can also look for LUKS headers and random-looking space.

Terminal window
sudo /mnt/data/nails verify --offline --root /mnt/decoy --examiner --disk /dev/sdX

It lists LUKS headers on partitions and in the gaps between them, random-looking unpartitioned space, files that look like encrypted containers, crypttab and fstab entries, LVM metadata archives, udisks2 state, encryption tools and their access times, and recent-files or history entries that name a container. It only reports, reads the disk read-only and never changes the exit code. A LUKS header for the decoy’s own full-disk encryption is expected. Only you can tell which of the other traces have a plausible explanation. Remove each one before you rely on the setup, or be ready to explain it.

nails rehearse checks the leak claims on your machine, with your decoy, backend and desktop. Rehearse before you rely on NAILS.

Terminal window
# 1. Inside an active hidden session
sudo /mnt/data/nails rehearse plant
# 2. End the session
sudo /mnt/data/nails deactivate
# 3. On the decoy, unlock and mount the hidden volume again, then
sudo /mnt/data/nails rehearse scan
# 4. Remove the rehearsal when you are done
sudo /mnt/data/nails rehearse reset
  • plant puts a random token where a session most often leaks: a file named after it in the hidden root and in your home, a recently-used.xbel bookmark, a system log line, a process command line and a desktop notification. Every file write is checked first to land in the upper layer on the hidden volume, and nothing is written to the decoy. The token stays on the hidden volume and is never printed, only a six-character fingerprint.
  • scan reads every mounted decoy block device and active swap raw and read-only, without mounting anything. It searches allocated and unallocated space for the token, in ASCII and UTF-16LE. Exit code 0 means it was not found, 11 that it was, with the device and byte offset of each hit, and 9 that a device could not be scanned completely.
  • A channel the canary could not be planted in, such as a notification with no desktop session to show it, is listed with the result as not tested.
  • nails rehearse on its own shows where you are and the next step.

scan refuses to run in the session the canary was planted in, which plant records by the session’s entry time. It runs on the decoy after a plain deactivate, after a power-off or in a later session, on every systemd version. --allow-same-boot overrides the refusal, but a scan in the planting session proves little, because the session’s writes may not have reached the disk yet. To rehearse the power-off exit instead, end the session with deactivate --poweroff and boot the decoy again before the scan.

plant refuses on a hidden root that this computer’s own storage seems to hold, which NAILS accepts only with --allow-decoy-storage, because a rehearsal on that storage cannot pass.

A clean scan covers only what the host can read. It cannot see SSD cells the drive has remapped or keeps in reserve, blocks discarded with TRIM, disks that are not mounted, the encryption header area or firmware storage. The scan prints this list with every result. To scan a disk that is not mounted, name it with --device, which you can repeat. The scan then reads only the devices you name, instead of the mounted devices and active swap. To scan a disk image, use nails rehearse scan --offline --image <file>.