Prepare the decoy
The decoy is the installed system, which should stay mundane and plausible. NAILS needs it to be an amnesic decoy: one that keeps no durable record of the hidden session. That means a volatile journal, no syslog or audit daemon, no disk swap and core dumps kept off the disk.
The decoy runs before the soft-reboot into the hidden session and again after the way back. A decoy that writes logs, audit records, crash dumps or swap to its own disk records the activation command, its own shutdown and start around each session, and what it imports from the kernel ring buffer. The overlay keeps the hidden session off the decoy only if the decoy itself keeps no durable record of it.
What NAILS enforces
Section titled “What NAILS enforces”Before activation, the decoy-persistence check inspects the decoy. It only
reads. It never rotates or deletes the decoy’s own logs, because destroyed log
history is itself a tell.
Activation is refused when the decoy has any of these:
- a persistent systemd journal
- a classic syslog daemon, rsyslog or syslog-ng
- the audit daemon, auditd
--accept-persistent-decoy on activate and check, or
accept_persistent_decoy = true in nails.toml, overrides these three
refusals. Use it only if you accept that what the decoy logs around each
soft-reboot may survive on its disk. It never covers the swap check, which
refuses active disk swap. RAM-backed zram is allowed.
Activation is also refused while a line in the decoy’s /etc/crypttab or
/etc/fstab opens or mounts the hidden volume or a device it is stored on. No
flag overrides this. Open the hidden volume by hand each time, and remove such a
line long before the next use, since the decoy’s configuration history can keep
it. On NixOS, activation is refused as well while a .nix file in /etc/nixos
names the hidden volume or NAILS.
These are reported as warnings and never block activation:
- a root filesystem mounted without
noatimeorrelatime, which records read access - crash dump channels that can write memory to the decoy disk or to firmware
storage: a
kernel.core_patternthat writes to the disk, systemd-coredump without bothStorage=noneandProcessSizeMax=0, pstore records or an armed kdump nixos-upgrade.timeron NixOS, which rebuilds the decoy on its own schedule- sudo’s own log file or I/O logging
- lid or idle settings that would suspend an active session, and a closed lid
set to
ignore, which leaves the session running and unlocked
Platform setup
Section titled “Platform setup”Set these in the decoy configuration, not in the hidden one.
services.journald.storage = "volatile";fileSystems."/".options = [ "noatime" ];boot.kernelParams = [ "init_on_free=1" "page_poison=1" ];
systemd.coredump.settings.Coredump = { Storage = "none"; ProcessSizeMax = 0;};
services.logind.settings.Login = { HandleLidSwitch = "lock"; HandleLidSwitchExternalPower = "lock"; HandleLidSwitchDocked = "ignore"; IdleAction = "ignore";};- Keep the root on a disk. A tmpfs root, such as impermanence on
/, is refused. A wiped-on-boot/varor/homeon its own tmpfs is fine. - Leave
swapDevicesempty, including inhardware-configuration.nix, and do not configureboot.resumeDeviceor a resume kernel parameter. - Do not enable
services.rsyslogd,services.syslog-ngorsecurity.auditd. - If your nixpkgs does not install
soft-reboot.targetandsystemd-soft-reboot.service, declare them with the modulenails checkprints. The hidden configuration needs them too. - Keep
/etc/nixosfree of anything that names the hidden volume. See What /etc/nixos must not name.
Debian 13. A later release runs with a warning that NAILS has not been tested on
it. Root overlays such as bilibop-lockfs are not supported, because the way
back mounts the decoy root by its device.
[Journal]Storage=volatile[Coredump]Storage=noneProcessSizeMax=0[Login]HandleLidSwitch=lockHandleLidSwitchExternalPower=lockHandleLidSwitchDocked=ignoreIdleAction=ignore- Remove or disable rsyslog, syslog-ng and auditd if they are installed.
- Add the coredump drop-in if systemd-coredump is installed.
- Mount the root with
noatime, and disable swap and hibernation. - Add
init_on_free=1 page_poison=1to the kernel command line.
Ubuntu 24.04 or 26.04. A later release runs with a warning that NAILS has not
been tested on it. overlayroot is not supported, because an overlay root
cannot be mounted again for the way back.
[Journal]Storage=volatile[Coredump]Storage=noneProcessSizeMax=0[Login]HandleLidSwitch=lockHandleLidSwitchExternalPower=lockHandleLidSwitchDocked=ignoreIdleAction=ignore- Remove or disable rsyslog and auditd if they are installed.
- Add the coredump drop-in if systemd-coredump is installed.
- Mount the root with
noatime, and disable swap and hibernation. - Add
init_on_free=1 page_poison=1to the kernel command line.
NAILS reads the journald, logind and systemd-coredump settings the way systemd
does, from the main file and every drop-in, so a drop-in that sets
Storage=persistent is caught too.
Where systemd-coredump is installed, keep it running with both settings rather
than turning it off. Storage=none alone still writes each core to
/var/lib/systemd/coredump on the decoy disk while it is processed. Without
systemd-coredump, the kernel writes a core file into the crashing program’s
working directory.
init_on_free=1 page_poison=1 make the kernel wipe freed memory. The hidden
session runs on the same kernel, so this covers it too. The memory-wipe check
reports whether the booted command line sets init_on_free=1.
What /etc/nixos must not name
Section titled “What /etc/nixos must not name”On NixOS, the suspicious-nails-reference check reads every .nix file in
/etc/nixos, comments included. It refuses activation, naming the file, the
line and the text it found, when a line holds:
- the hidden volume’s mount point as a whole path, such as
/mnt/data, or the units systemd names after it, such asmnt-data.mount - its device, such as
/dev/mapper/data, or the unit systemd names after it - the storage it is on, by device path, udev link, UUID or label, unless the decoy’s own volumes sit on it too
- its mapping name where a line opens the volume, such as a
boot.initrd.luks.devicesentry, a crypttab line or acryptsetup opencommand, in a comment as in code - text NAILS writes during a session, or the word
nails
Ordinary configuration passes, such as the mapping name used as an ordinary word
or a directory like /var/lib/postgresql/data.
The check reads text and does not evaluate Nix, so it misses a name built with a
let binding or an interpolation. Keep such lines out yourself. It also reads
only .nix files. Keep notes, scripts and a README in /etc/nixos free of
these names too. Where /etc/nixos is a git repository, as a flake often is,
its history counts as well. A line removed from a file stays in the commit that
added it.
Run nixos-generate-config only while the hidden volume is closed. It writes an
entry for every mounted filesystem.
If /etc/nixos holds a flake, activation refuses to build it, --no-preflight
included, because that build would leave out the hidden module and the hidden
hardware configuration. The refusal does not recognize every way of naming that
flake, such as a registry alias, so never name it with nixos_flake or
--nixos-flake. Write a self-contained hidden flake on the hidden volume
instead, one that imports the hidden module and reads none of the decoy’s files.
Write and lock it inside a hidden session, never on the decoy.
nails activate --overlay-only enters one with the decoy’s own system, without
building the hidden configuration. See
the nixos_flake setting.
Suspend and the lid switch
Section titled “Suspend and the lid switch”Suspending during a session keeps the whole hidden session in RAM, including the open encryption mapping and its key, behind the hidden session’s lock screen. Whoever unlocks that screen is in the live hidden session. By default logind suspends when the lid closes, and desktops add their own idle suspend.
- Set the lid switch to
lock, as above.ignorealso keeps the host awake, but it leaves the session unlocked behind a closed lid, and thesleep-policycheck warns about it. - Turn off automatic suspend in the desktop’s power settings too, such as GNOME’s or KDE’s, since logind does not govern it.
- The hidden session starts from the decoy’s
/etc, so it keeps these settings unless the hidden configuration changes them. - Consider
sleep_policy.modeinnails.tomlas a backstop.blockprevents sleep during a session. When a closed lid would sleep, it also takes the lid switch from logind, so a closed lid then does nothing, not even lock the screen. A lid set tolockstays with logind and keeps locking.teardownpowers the machine off when it is about to sleep. See the sleep policy settings.
Neither mode replaces the lid switch setting. Keep it at lock, so a closed lid
locks the screen. A laptop kept running with its lid shut can overheat.
sudo logging
Section titled “sudo logging”Running sudo is itself logged, with the invoking user, the command and its
full path. On an amnesic decoy that record stays in RAM. Two kinds of sudo
settings write to the disk on their own. Remove both from /etc/sudoers and
/etc/sudoers.d/:
Defaults logfile=, which writes every sudo command line to a file- every I/O logging option, such as
log_input,log_outputand theLOG_INPUTandLOG_OUTPUTtags, which record whole terminal sessions, keystrokes and output included
NAILS cannot suppress sudo’s own logging without tampering with the decoy, which would itself be a tell. Keep the binary at a neutral path on the hidden volume.
Shell history on the decoy
Section titled “Shell history on the decoy”The unlock and mount commands must be typed on the decoy, before NAILS runs. Run
them, and nails activate, in a shell with history off:
set +o historyActivation kills the decoy’s interactive shells as its last step, so none of
them can write the activation command to a history file on exit. A shell that
writes history after every command has already written it. The
incremental-history-flush check warns about that, for zsh’s
INC_APPEND_HISTORY or history -a in bash’s PROMPT_COMMAND.
NAILS does not configure the decoy’s shells. To give an interactive shell
nowhere to write, set HISTSIZE=0 and HISTFILESIZE=0 in the decoy’s shell
profile and point the history files at /dev/null. In the shell you unlock
from, do not turn history back on or run history -a or history -w. Each of
them writes the commands you typed to the decoy disk.
What the volatile journal keeps
Section titled “What the volatile journal keeps”On an amnesic decoy, the decoy’s records from around each switch stay in RAM:
the sudo line of the activation, the hidden build’s progress, the decoy’s
shutdown before the switch and its start after the way back. They are in the
runtime journal below /run/log/journal, which the hidden session’s journald
writes to as well.
The way back deletes that journal whole, the decoy’s own records from before the
session included. The decoy’s journal then starts at the return, without the
kernel’s boot messages, while uptime and dmesg still go back to the boot.
Until the machine is powered off, anyone with a shell can see that the journal
starts mid-uptime. Power off after a session.
With a persistent journal, accepted with --accept-persistent-decoy, these
records are on the decoy disk and stay there, with a gap where the hidden
session ran.
Keep the decoy plausible
Section titled “Keep the decoy plausible”Use the decoy normally between sessions, so its logs account for the hours the
machine ran. Otherwise some records point to use the logs do not explain. The
clock synchronization stamp and timer stamps can date a session, and the drive
counts its own power-on hours, which no software can change. A /var on a
wiped-on-boot tmpfs loses the two stamps at every boot.
nails verify reports them as notes, so
you can see what an examiner would see.
Full details
Section titled “Full details”- Decoy requirements, with the full residue catalog
- Configuration: sleep policy
- Shell history forensics