/security

What's protected, and what isn't.

Ephemeral Nix sets careful defaults on every machine and documents each one together with what it doesn't cover. Nothing here makes a machine invulnerable. This page describes the config as released; each control links to the file that sets it.

01Implemented

On every machine, in every profile, unless marked.

full full profile only option off unless you turn it on

Storage

Full-disk encryptionLUKS2 with argon2id (5 s iteration time) over everything but the ESP; the swap file is inside it.modules/_disko-layout.nix
/boot permissionsThe ESP is mounted with umask=0077, readable by root only.modules/_disko-layout.nix
Ephemeral root/ is recreated from an empty snapshot at every boot, so files dropped outside the kept paths don’t survive it.modules/ephemeral-root.nix
Snapshots are root-onlyfullOld snapshots hold old password hashes and keys, so /persist/.snapshots is mode 750, owned by root.modules/snapshots.nix

Accounts and privilege

Declared usersmutableUsers = false: users come from the config. One user, in wheel.modules/users.nix
No root passwordRoot’s password is locked. sudo is for wheel only, with a password, and only runs from wheel members.modules/users.nix
Nix daemonallowed-users is @wheel; only root is a trusted user.modules/nix.nix
Password hashOnly a yescrypt hash, in /persist/passwords/<user>, mode 600, owned by root; permissions set again at every boot.modules/users.nix
Safe password changesephemeral-passwd asks twice and only replaces the hash once it has a valid one, so a typo can’t lock you out.scripts/ephemeral-passwd.sh

Network

FirewallOn, with no port opened unless a setting asks for it.modules/networking.nix
SSHOff. Turned on, it still keeps port 22 closed, allows keys only, refuses root logins, and turns off agent and X11 forwarding.modules/ssh.nix
OpenSnitchfullDefault action deny for outbound connections; it asks the first time each program connects.modules/opensnitch.nix
Wi-Fi MAC addressA different address on each network, stable for that network.modules/networking.nix
Name resolutionLLMNR off. mDNS discovery (Avahi) is off on purpose.modules/networking.nix
DNS over TLSoptionOptional, through Control D, with no fallback servers. The resolver id is kept root-only, never in the Nix store.modules/controld.nix
SMB mountsoptionSMB 3.1.1 with encryption (seal); passwords root-only in /persist/etc/smb.modules/smb.nix

Kernel

Kernel image protectionsecurity.protectKernelImage, which rules out kexec and hibernation.modules/hardening.nix
IOMMUintel_iommu=on, iommu.passthrough=0, iommu.strict=1: each device is limited to the memory it’s given.modules/hardening.nix
Memoryinit_on_free=1, slab_nomerge, page_alloc.shuffle=1, vsyscall=none, debugfs=off.modules/hardening.nix
sysctlskptr_restrict=2, dmesg_restrict, unprivileged BPF off, BPF JIT hardening, ptrace_scope=1, no suid core dumps, protected FIFOs and regular files, no line-discipline autoload, no ICMP redirects or source routing.modules/hardening.nix
Unused protocolsdccp, sctp, rds and tipc are blacklisted.modules/hardening.nix

Services and secrets

Systemd sandboxThe config’s own root services run with a read-only system, no network, no capabilities and restricted syscalls: Control D, Flathub, the user picture, root-reset-check and the snapshot setup.modules/sandbox.nix
No secrets in the storeephemeral-update and ephemeral-setup refuse to rebuild when the config folder holds files that look like keys or passwords.scripts/ephemeral-update.sh
Typed settingsEvery setting that reaches a script or a path has a type that refuses what would break it; settings-reject checks the refusals.modules/settings/_schema.nix
Unfree allowlistNothing unfree builds unless it’s listed by name.modules/unfree.nix
Pinned FlathubFlathub is added from a pinned copy of its repo file, so its signing key can’t be swapped.modules/flathub.nix

The installer

Passphrase handlingThe disk passphrase goes to partitioning only on standard input; the password only as a hash. Neither is in the environment or a log.ARCHITECTURE.md
Disk checksNothing is erased before every check passes, and the disk’s identity is checked again right before erasing.scripts/common.sh

02OpenSnitch

Outgoing connections are blocked until you allow them.

On the full profile, OpenSnitch asks the first time each program connects somewhere. If an app can't connect, a missed or denied prompt is the likely cause: open OpenSnitch and check its rules. Options that need the network, such as Tailscale and the Proton Pass SSH agent, add their own rule. "useOpenSnitch": false in settings.json turns it off.

03Not covered

Limits to know before you rely on it.

Boot integrity

/boot is not encrypted and Secure Boot is not set up; installing needs it turned off. Someone with the laptop in hand could replace the kernel or initrd with one that records the passphrase. There is no measured boot and no TPM: the disk unlocks with the passphrase only.

Before the kernel

The IOMMU makes DMA attacks over Thunderbolt and USB4 much harder once the kernel is running. It doesn't cover the firmware and boot loader before that.

TRIM

Discards pass through the encryption for SSD performance. That shows which blocks are in use, not what's in them.

Root-equivalent choices

Once ephemeral-setup makes /persist/etc/nixos yours, anything running as you can change what the next rebuild installs as root. Docker and virt-manager add you to docker or libvirtd, which has the same effect. Each is your choice, and each says so when you make it.

Convenience defaults

On the full profile nix-ld lets prebuilt Linux programs run unpatched. smartd's disk warnings go over the system bus, so any local process can show you a notification. With fingerprint login on, a finger also works for sudo. The security notes say how to turn each off.

Visible on the network

With Control D, the resolver id is part of the server name TLS sends unencrypted, so your network can see it. It tells Control D which settings to use; it doesn't log you in.

04Threat model

Who it's built against.

A single-user laptop or desktop, used day to day. The aim is to protect data at rest, keep the network surface small, make unexpected outbound traffic visible, and keep system state from silently accumulating, without a setup that's tiring to live with. It is not built for a physically targeted user or a hostile multi-user machine.

ThreatAddressedHow, and the gap
A powered-off laptop is lost or stolenyesEverything but /boot is LUKS2-encrypted with argon2id. The passphrase is the only key; choose a strong one.
Someone tampers with the laptop while you’re away, then you unlock itno/boot is not encrypted and Secure Boot isn’t set up, so a modified kernel or initrd could record the passphrase.
A running, unlocked machine is takenpartlyThe screen locks when idle (Noctalia after 10 minutes on niri and Umbriel; the desktop’s own settings on GNOME and Plasma). Disk encryption doesn’t help once unlocked.
DMA over Thunderbolt or USB4partlyThe IOMMU limits devices once the kernel runs. It doesn’t cover the firmware and boot loader before that. Devices need approving once.
A connection in from the networkyesFirewall on, SSH off, no open ports by default.
A program calling home or exfiltrating datapartlyOn the full profile OpenSnitch denies outbound connections you haven’t allowed. It’s only as strict as the rules you approve.
Malware that persists in system directoriespartlyAnything written to / outside the kept paths is gone at the next boot. Your home folder, /persist and a user-owned config folder are kept, so it isn’t a defence on its own.
A local process becoming rootpartlyKernel hardening, sandboxed root services and wheel-only sudo raise the bar. Docker and libvirtd group membership, or a config folder you own, are root-equivalent.
Secrets leaking into the Nix storeyesRebuilds refuse when the config folder holds key- or password-like files; secrets live in root-only files read at runtime.
A compromised upstream inputpartlyInputs are pinned and releases immutable; only nixpkgs updates merge on their own after tests, others are read by a person first. Nothing can verify every upstream line.

05Supply chain

What you install can be checked.

  • Releases are immutable: once published, a tag and its files can't change.
  • Installer ISOs are built by CI and attested; gh attestation verify proves which workflow built one, and gh release verify-asset that it's the one the release went out with.
  • Release tags cut by hand are signed. Weekly releases tagged by CI aren't, but GitHub signs a record of each release's tag and files, which gh release verify checks.
  • Machines only build a pinned, released tag, never a branch. ephemeral-update only offers a newer, well-formed tag.
  • Only nixpkgs updates merge on their own, after CI and the boot tests. Other inputs are read by a person first.
  • CI pins every action by commit, asks for permissions per job, keeps tokens out of the checkout, lints workflows with actionlint and zizmor, scans for secrets, and runs govulncheck on the installer.
checking an ISO
sha256sum -c SHA256SUMS
gh attestation verify ephemeral-nix-*.iso -R the-wittch/ephemeral-nixos
gh release verify-asset ephemeral-nix-*.iso -R the-wittch/ephemeral-nixos

More on the install page.

06Planned

No security changes are announced.

The repositories don't announce Secure Boot, TPM unlocking or measured boot, and the docs make no promise to add them. The config's rule is that kernel hardening only gets stronger: turning anything off needs a changelog entry saying how to undo it. Changes are listed in the changelog as they ship.

07Reporting

Found a problem? Report it privately.