How it works
NAILS runs the hidden session in one OverlayFS overlay over the whole decoy root, and enters and leaves it with a systemd soft-reboot. This page is the operator’s view of that design. The architecture overview explains why the session takes the whole root and what that costs.
The overlay
Section titled “The overlay”An overlay merges two layers into one directory tree:
- The lower layer is the decoy root. It stays read-only.
- The upper layer is on the hidden volume, in its
root/upperdirectory. With the hidden volume at/mnt/data, as in these docs’ examples, that is/mnt/data/root/upper. Changes made in the hidden session land there, from files and logs to shell history and package builds.
The hidden session sees both layers merged. A decoy file it changes is first copied up in full, so the decoy’s own copy stays as it was. The upper persists across sessions, on the hidden volume.
Entering with a soft-reboot
Section titled “Entering with a soft-reboot”nails activate builds the overlay at /run/nextroot and enters it with
systemctl soft-reboot, which needs systemd 254 or newer. systemd stops every
decoy program, switches root into the overlay and starts a fresh userspace
there. The hidden desktop, its applications and its services all start inside
the overlay. After the switch, no program in the session has a path to the
decoy’s own filesystem. The few writes that go around the mount layer, to a disk
you mount yourself during the session or to EFI variables, are the exception.
The kernel, its modules, the device-mapper mappings and /run survive the
switch. That is why the hidden volume has to be one the kernel manages, such as
a dm-crypt mapping or a loop device. A FUSE mount would die with the programs at
the soft-reboot.
An earlier NAILS design overlaid the running decoy directory by directory. It had to find every process that could still write to the decoy, and a writer it missed went on writing there without any sign. Overlays set up at boot need no such search, but they need a full reboot and leave an enabler on the decoy, such as a package, a boot entry or a kernel command-line flag. With a soft-reboot, every process of the hidden session starts inside the overlay, and nothing is installed on the decoy.
Both transitions log you out. Every program closes, and the hidden session and later the decoy start at their own login screens. NAILS logs nobody in, but a display manager set to log an account in automatically logs it in again after each switch, as at boot. The operator guide shows how to turn autologin off for the hidden session alone.
Other decoy filesystems
Section titled “Other decoy filesystems”The root overlay covers every directory on the root filesystem. Every other filesystem the decoy’s fstab names gets a fixed decision before the switch:
| Decoy filesystem | In the hidden session |
|---|---|
The ESP and /boot | Bound read-only |
| A filesystem the decoy mounts read-only | Bound read-only |
Any other mounted local filesystem, such as a separate /home | An overlay of its own, with its upper on the hidden volume |
| Swap, a local filesystem mounted at boot but not now, a network filesystem | Masked, so the hidden session never uses them |
The units that write to the ESP and /boot at every start,
systemd-boot-random-seed and on Debian and Ubuntu grub-common and
grub-initrd-fallback, are masked in the hidden session as well. They run as
usual on the decoy. The masks live in /run, so they are gone with the session.
nails check prints the decision for every entry, and the
configuration reference
has the full table. A disk you mount by hand during the session is outside this
policy and is written directly. See
Your first session.
On NixOS
Section titled “On NixOS”On NixOS, the hidden session can run its own declarative NixOS configuration, built into the overlay and entered in the same soft-reboot. The hidden system’s store paths end up on the hidden volume, like everything else the session writes through the decoy’s mounted filesystems. That narrows what an offline examination of the decoy can find. The hidden volume, its encryption and any plausible deniability come from the storage backend you choose, such as a VeraCrypt hidden volume, and NAILS adds nothing to the backend’s claim.
The way back mounts the decoy root again by its device, so the root has to be
ext4, xfs, btrfs or f2fs on a disk. A tmpfs root, such as impermanence on /,
is refused, and so is a ZFS root. A wiped-on-boot /var or /home on its own
tmpfs is fine. Prepare the decoy lists the other settings
a NixOS decoy needs.
With a hidden NixOS configuration, activation builds the hidden system into the overlay’s store before the switch. It builds the first of these that applies:
- a flake named with
--nixos-flakeor thenixos_flakekey - a flake in
nixos/on the hidden volume, built for the machine’s hostname - the legacy configuration, which is the decoy’s
/etc/nixoswith the hidden hardware configuration thatnails initplaced in the upper
nails check names the choice under “NixOS profile”. The legacy configuration
imports the hidden module, config/nixos/configuration.nix on the hidden
volume, by itself. A flake has to import it, or the hidden system lacks its
settings.
The build reads the decoy store through the read-only lower layer and writes every new path to the hidden volume. The first entry also copies the hidden system’s whole closure onto the hidden volume. For a desktop that is several GB, and on a slow or encrypted backend it takes minutes. Later entries copy only new paths, and the decoy can collect its garbage or rebuild between sessions without breaking the hidden system.
No bootloader is installed and the system profile does not change, so the boot
default always remains the decoy. The hidden system has to run on the decoy’s
kernel. nails activate --overlay-only skips the build and enters with the
decoy’s own system plus the hidden /etc and home.
Flake decoys
Section titled “Flake decoys”A decoy whose /etc/nixos is a flake needs a hidden flake. Activation refuses
to build the decoy’s own flake, --no-preflight included, because the result
would lack the hidden module and the hidden hardware configuration. The refusal
catches the usual ways of naming that flake. A few spellings, such as a registry
alias, are not recognized.
Write and lock the hidden flake inside a session entered with
nails activate --overlay-only, never on the decoy. Make it import the hidden
module, and keep it self-contained, so that it reads none of the decoy’s files.
Changing the hidden system
Section titled “Changing the hidden system”Inside the hidden session, sudo nixos-rebuild test applies changes to the
legacy configuration. With a hidden flake, name it:
sudo nixos-rebuild test --flake /mnt/data/nixos#<host> --impure<host> is the decoy’s hostname, even when the network-identity module gives
the session another one. For a flake named with nixos_flake, pass that
reference instead. On a flake decoy, a plain nixos-rebuild test would build
the decoy’s own flake, without the hidden module.
Never use switch or boot. They try to install a bootloader into the
read-only ESP and fail. The next activation builds the hidden system again from
the hidden module or your flake, so keep that source in step with what you test.
On Debian and Ubuntu
Section titled “On Debian and Ubuntu”The same root overlay, without a build step. The hidden configuration is
whatever the upper’s /etc holds. Change it from inside the hidden session like
any system configuration, with apt, edits in /etc or systemctl enable.
On Ubuntu 26.04, chrony and CUPS fail in the hidden session. After the way back,
the decoy lists chrony as failed until its next full reboot, which hints that a
soft-reboot happened. Reboot fully after a session there. A plain
systemctl soft-reboot into another root, without NAILS, breaks chrony the same
way.
The way back
Section titled “The way back”nails deactivate runs in the hidden session. It mounts the decoy root again at
/run/nextroot and makes the decoy’s other mounts again below it, as the decoy
had them when the session began. It then marks the hidden volume for closing and
soft-reboots. If a mount recorded at entry cannot be made again, deactivate
refuses, and nails emergency powers off instead. Some decoy mounts, such as a
FUSE mount or a disk mounted by hand, are not made again. As after a plain
soft-reboot, they are gone until they are mounted again.
The orderly shutdown stops every hidden program. The last step before the switch:
- scrubs the top of
/runof what the hidden session added, while state deeper down stays, such as NetworkManager’s per-link records - deletes the runtime journal, which the decoy shares with the hidden session
- puts back the decoy’s name-server records below
/run/resolvconf - gives the kernel back the decoy’s module loader, hotplug helper and firmware path, which a hidden NixOS system points at its own store
- restores the decoy’s lines in libmount’s
utab - moves the decoy journal’s kernel-message cursor past the session’s records
The decoy then starts at its own login screen, or at its desktop if its display manager logs an account in automatically. The switch itself unmounts the hidden volume and closes its encryption mapping. Network links, addresses and routes that the session set up stay, as What a soft-reboot leaves explains.
nails emergency takes the same way back but kills every hidden program at
once. See Ending a session.
NAILS installs nothing on the decoy
Section titled “NAILS installs nothing on the decoy”Everything NAILS needs lives on the hidden volume. The session plan, the
transition step and the runtime units live in RAM under /run, and the way back
removes them. The ESP is read-only in the hidden session, and NAILS never
installs a boot entry, an initrd change, a unit or a binary on the decoy. Such
an enabler would be evidence in itself, even with the hidden data encrypted.
A line in the decoy’s /etc/crypttab or /etc/fstab that opens or mounts the
hidden volume is an enabler too. Activation refuses to run while one exists, and
no flag overrides that check.
End-to-end tests hash the ESP’s file listing and capture the EFI variables before activation and after deactivation, and check that nothing changed beyond what a plain reboot changes.
What a soft-reboot leaves
Section titled “What a soft-reboot leaves”A soft-reboot keeps the kernel running. On the decoy disk it leaves none of the
hidden session’s files, with one exception. If a network link the session
created is still there at the return, the decoy’s NetworkManager records it,
with the time, in /var/lib/NetworkManager/timestamps as soon as the decoy
starts. Powering off after the return comes too late for that record.
Until the machine is powered off, RAM also keeps:
- the kernel ring buffer (
dmesg), with the hidden session’s kernel messages - the soft-reboot counter, two per session, which
systemctl showreports on systemd 256 and newer - the same boot ID and continuous uptime across both transitions
- the links, addresses, routes and VPN tunnels the session set up, with the
hidden NetworkManager’s per-link records below
/run/NetworkManager - a random MAC address from the optional network-identity snippet, which can
stay on the decoy’s network device, and on a decoy without
/etc/hostnamethe hidden hostname - on a volatile decoy, a journal that starts at the return, while
uptimeanddmesgstill go back to the boot - the decoy’s root and its other mounts, made again with new mount IDs that
findmnt -o IDshows
Memory the hidden session freed is not wiped either.
Before you deactivate, take down every connection and VPN tunnel you brought up
and delete every link you created by hand with ip link delete, since
ip link set down keeps it. Ending the session with
nails deactivate --poweroff works too, because it powers off before the
decoy’s NetworkManager starts. End a session that used the network-identity
snippet with --poweroff in any case. Power off after a session whenever RAM
residue matters, and leave the machine off for a few minutes.
The decoy’s own shutdown before the session and its start after it write what an
ordinary shutdown and boot write, such as the random seed and a wtmp record.
On Debian and Ubuntu, the way back keeps the login and boot records reading as
one clean power cycle. The login records written after the return still carry
process numbers that continue from the decoy’s boot, where a fresh boot starts
them low. A session that ends with --poweroff has no return, so it writes none
of them.
The residual channels in the threat model list what the overlay cannot cover.
What the hidden session shares with the decoy
Section titled “What the hidden session shares with the decoy”The overlay separates files, not machines. The hidden session runs on the decoy’s kernel and starts with the decoy’s machine ID, hostname, MAC addresses and network profiles. The kernel keeps network interfaces and addresses across both switches, so what the session sets up there stays in the decoy until power-off, as described above.
By default it also runs as the decoy user, in the decoy home seen through the
overlay. The decoy’s browser profiles, SSH and GPG keys and history are live in
the hidden session, and changes to them land in the upper. The fresh-home
persona gives the hidden session an empty home instead.
Every service the decoy enables starts again in the hidden session unless the
hidden configuration turns it off, and in the default persona so do the decoy
account’s autostart entries and user services. nails check lists the ones that
need review. What the session sends through a shared account or sync client
stays on the remote side, and a decoy sync client can pull it back onto the
decoy disk later. See persona modes and
the threat model.