x0ne OS / Handbook / Cloud and security
Hardening
Works today (measured 2026-10-12, built from source): every point marked Available below has a test in the suites, green on that date, on the images built by make.
Planned: a release key held offline, a measured-boot VM image, the corresponding-source archive, an SBOM shipped with each release. Nothing is published yet; the test key signs everything.
One entry per feature: what it does, how to check it yourself, and where it stops. Commands run on the booted system unless the heading says otherwise.
Chain of trust
Signed apt repository, key named in the source Available
The x0ne repository is signed. The apt source file names the keyring (Signed-By); no source uses trusted=yes. The keyring ships as x0ne-archive-keyring, root-owned, mode 0644.
$ grep -E "Signed-By|Trusted" /etc/apt/sources.list.d/x0ne.sources $ ls -l /usr/share/keyrings/x0ne-archive-keyring.gpg
Limit: the key is a throwaway test key until the first release; nothing signed with it is published.
Release file: SHA-256 sums, seven-day validity, attacks refused Available
The Release file carries SHA-256 sums and a Valid-Until seven days after Date. In the test suite apt refuses a rogue key, a tampered InRelease, a tampered Packages index, a tampered .deb and an expired Release.
$ apt-get update 2>&1 | grep -iE "NO_PUBKEY|not signed|expired|hash sum" || echo cleanLimit: a stale mirror makes apt fail after seven days by design; refresh the mirror, do not disable the check.
Signed SHA256SUMS over the images Available
The live ISO, the installer ISO and the package lists are listed in SHA256SUMS, signed as SHA256SUMS.sig. A truncated ISO fails sha256sum --check.
$ gpg --verify SHA256SUMS.sig SHA256SUMS $ sha256sum --check --ignore-missing SHA256SUMS
Limit: test key today (see above). The qcow2 and microVM images have their own sums next to them but are not published.
Reproducible packages and ISO Available
Two builds with the same SOURCE_DATE_EPOCH give byte-identical .deb files and a bit-identical live ISO. The confidential VM image rebuilds to the same root hash, UKI and tarball.
$ make debs && sha256sum out/debs/*.deb > a && make debs && sha256sum -c aLimit: the plain VM and container images are built from the live Debian mirror, so only their package lists are recorded; they are not bit-reproducible yet.
Package list per image, boot chain included Available
Every image comes with its package list (name and version). The ISO list also names the signed boot chain (shim, GRUB) and the Debian snapshot it was built from, and it matches the root filesystem exactly.
$ dpkg-query -W -f '${Package} ${Version}\n' | sort | diff - <(grep -vE "^(#|boot |tool )" x0ne-os-amd64.packages | sort)Limit: this is a package list, not a full SPDX SBOM; the build writes one but no release ships it yet.
No fixable critical CVE at build Available
The container, VM and microVM images are scanned at build; a Critical vulnerability with a fix available fails the suite.
$ apt list --upgradable 2>/dev/null | grep -i securityLimit: a scan is a snapshot; after release, Debian security updates close the gap (see Updates).
Package hygiene Available
x0ne packages are lintian clean, root-owned, with no setuid or world-writable file, and their file paths come from an allow-list. No image contains a private key, and the build-time test key is never shipped.
$ find / -xdev -type f -perm /6000 2>/dev/null $ find / -xdev -type f \( -name "*.key" -o -name "id_ed25519" -o -name "id_rsa" \) 2>/dev/null
Limit: this covers x0ne packages and the images; Debian packages keep Debian's policy.
Boot and disk
Secure Boot with Debian's signed chain Available
The live ISO, the installer and the dedicated-server image boot with Secure Boot on, through Debian's shim (signed by Microsoft), GRUB and kernel (signed by Debian). The kernel then runs in lockdown integrity.
$ mokutil --sb-state $ cat /sys/kernel/security/lockdown
Limit: the plain VM image (qcow2) uses systemd-boot without Secure Boot. No x0ne key is enrolled anywhere: only Debian's chain.
LUKS2 full-disk encryption, argon2id Available
The unattended installer and the dedicated-server image put the root filesystem in a LUKS2 volume with the argon2id key derivation.
$ sudo cryptsetup luksDump /dev/disk/by-partlabel/root | grep -E "^Version|PBKDF"Limit: /boot and the EFI partition stay in clear, as on Debian. The live USB and the plain VM image are not encrypted.
Remote unlock over SSH (dropbear in the initramfs) Available
When enabled at build, the initramfs runs dropbear with the one key you gave, forced to cryptroot-unlock, no forwarding, no password. It is off by default on the installer.
$ sudo cat /etc/dropbear/initramfs/authorized_keysLimit: the dropbear host key sits in the unencrypted /boot. On the server image it is replaced at first login; on an installed system it is the build-time key.
Host keys made at build, no trust on first use Available
sshd and dropbear host keys are generated when the install medium is built; their fingerprints are printed and written to a known_hosts file, so the first connection is checked, not trusted.
$ ssh -o UserKnownHostsFile=out/private/HOST.known_hosts -o HostKeyAlias=HOST user@ADDRLimit: the medium therefore holds the private host keys: keep it private and delete it after the install.
Build-time passphrase replaced at first login Available
The LUKS passphrase typed at build time is still valid at first boot. Every interactive login of a sudo user runs the rekey until it succeeds: new passphrase added, checked, old slot removed. On the server image the volume key itself is replaced by a reencryption.
$ sudo cryptsetup luksDump /dev/disk/by-partlabel/root | grep -cE "^ [0-9]+: luks2"Limit: the LUKS passphrase is in clear in the private installer ISO (debian-installer has no hashed form): delete that ISO once installed.
CPU microcode in the early initramfs Available
The dedicated-server image carries amd64-microcode and intel-microcode for every CPU model, in the uncompressed early part of the initramfs.
$ dmesg | grep -i microcodeLimit: server image only; the ISO and VM images do not ship microcode.
Measured boot image (UKI, dm-verity, PCR 11) Planned
A general-purpose VM image booted as one signed UKI, with a dm-verity root and expected PCR values. Code and tests exist on a branch (2026-10-13); the suite has not run green on the main line.
Limit: until then, measured boot is available in the confidential VM image only.
Accounts and access
Root locked, no default account Available
Every image ships with root locked, no regular user, no account with a usable password, and an empty machine-id.
$ sudo passwd -S root $ sudo awk -F: '$2 !~ /^[*!]/ {print $1}' /etc/shadow
Limit: none known.
First boot: one sudo account, hashed credential only Available
The first boot asks for a user name and a password, 12 characters minimum. Credentials given by SMBIOS or kernel command line must be a crypt(5) hash; a clear password is refused. The password is written to no log, journal or file. Root stays locked.
$ sudo journalctl -b -u x0ne-firstboot $ ls /var/lib/x0ne/firstboot.done
Limit: a credential on the kernel command line is readable by any local user: pass a hash, never a password.
No SSH server, nothing listening Available
No image installs openssh-server. At boot x0ne OS prints listening tcp=0 udp=0; the suite checks it on the VM, microVM, live ISO and confidential VM.
$ ss -tulpnLimit: the unattended installer and the server image install sshd on purpose (next entry).
sshd where installed: keys only, no root Available
On the installed server: PasswordAuthentication no, PermitRootLogin no, your key in authorized_keys. The install builders refuse an RSA key (ed25519 or ecdsa only).
$ sudo sshd -T | grep -E "^(passwordauthentication|permitrootlogin|pubkeyauthentication)"Limit: an RSA key you add yourself afterwards is not removed.
Console: a login prompt, never a shell Available
The serial console shows login:, no autologin. The microVM initrd carries no rescue shell and the kernel line no debug shell. The confidential VM has no getty at all.
$ systemctl cat serial-getty@ttyS0 | grep -i autologin || echo noneLimit: none known.
Secrets never on a command line Available
Install builders read passwords masked on a terminal or from the environment, and refuse them as make arguments. Output is private (0700/0600), the account password is a salted SHA-512 crypt hash, and no secret is left on the installed disk.
$ ls -la out/private/Limit: the LUKS passphrase in the private installer ISO is in clear (see Boot and disk).
x0ne-agent: unprivileged agent user Available
The opt-in x0ne-agent package creates an agent user with password login disabled, no sudo, and subuid/subgid ranges for rootless podman.
$ sudo passwd -S agent $ sudo -l -U agent
Limit: rootless podman inside a nested container is not proven by the suite (expected failure).
Updates
Daily security updates, no automatic reboot Available
The VM and live images install unattended-upgrades with two allowed origins: Debian security (trixie-security) and the x0ne stable channel. Nothing from the Debian main archive, backports or the edge channel upgrades on its own. No automatic reboot.
$ apt-config dump | grep -A3 Origins-Pattern $ systemctl status apt-daily-upgrade.timer
Limit: the container image runs no timer; update it by rebuilding. A kernel update waits for your reboot.
x0ne never overrides a Debian package Available
The repository is NotAutomatic: yes, ButAutomaticUpgrades: yes: priority 100. x0ne packages install and upgrade, but a Debian package of the same name always wins.
$ apt-cache policy | grep -B1 -A1 x0neLimit: none known.
Recommended packages off Available
APT::Install-Recommends "false" ships with x0ne-base, also when installed on an existing Debian. Fewer packages, opt-in extras.
$ apt-config dump | grep Install-RecommendsLimit: some Debian packages expect their recommends; install them by name.
Two channels, edge opt-in Available
stable is on, edge is shipped disabled. Enabling edge moves the Engine and Harness to the edge build at the next upgrade; disabling it does not downgrade.
$ grep -E "^(Suites|Enabled)" /etc/apt/sources.list.d/x0ne.sourcesLimit: none known.
Third-party source pinned (Google Cloud SDK) Available
x0ne-gcloud-repo ships the Google apt source with its key vendored and Signed-By, pinned so nothing is installed from it unless you ask.
$ apt-cache policy | grep -A2 packages.cloud.google.comLimit: the key is Google's; rotation follows Google, checked at each x0ne build by fingerprint.
Confidential VM image (Intel TDX)
One UKI, dm-verity root, state in RAM Available
The ESP holds one file, the UKI, booted directly. The root is a squashfs under dm-verity; the root hash is on the UKI command line, and every writable path is a RAM overlay.
$ findmnt / $ tr " " "\n" < /proc/cmdline | grep -E "roothash|volatile"
Limit: last run on hardware on 2026-10-18 (x0ne-cvm 0.2.3, Google Cloud, one H100 in confidential mode, verdict VERIFIED); its image reference is signed by a test key until the first release.
Published measurements, event log replayed Available
reference.json gives the expected RTMR1 (UKI Authenticode digest) and RTMR2 (UKI sections) values; the platform measurement is checked by Google's launch endorsement, debug mode must be off. The test suite replays the TCG event log from a boot against that file.
Limit: RTMR0 is reported, not pinned yet. The reference is a test artefact until a release key signs it.
nftables: 443 and ICMP only Available
Default drop on input, TCP 443 and ICMP/ICMPv6 accepted, forwarding dropped, output open (metadata server, DNS, NTP, model download).
$ sudo nft list rulesetLimit: none known.
Inference service confined Available
x0ne-infer runs as its own user with CAP_NET_BIND_SERVICE only, NoNewPrivileges, ProtectSystem=strict, private /tmp, kernel tunables and modules protected, device policy closed with the NVIDIA and tdx_guest devices allow-listed.
$ systemd-analyze security x0ne-infer.serviceLimit: the GPU devices are necessarily shared with the service; the NVIDIA driver is out-of-tree code.
No sshd, no guest agent, no cloud-init, no login Available
None of these is installed. Console, serial, getty, logind and the rescue targets are masked. Configuration comes from one metadata attribute, copied byte for byte; the console shows only its size and hash.
Limit: the boot report on the serial console prints public facts (mounts, sockets, PCRs); whether it stays in the production image is open.
Live USB
Runs from RAM, writes nothing to the medium Available
Squashfs root, tmpfs overlay, no persistence, no swap. Two boots on the same stick leave its bytes unchanged; a token written during the session appears nowhere on the medium afterwards.
$ findmnt / $ swapon --show
Limit: RAM is the limit: what you want to keep goes to another disk or the network.
Authenticated time (NTS) Available
chrony with NTS sources from three operators (Cloudflare, Netnod, PTB), authselectmode prefer, no UDP command port.
$ chronyc -N authdataLimit: without network, time comes from the hardware clock.
x0ne channel configured, no binary pre-installed Available
The stable source is on and edge off; the Engine and Harness are not on the medium. apt install x0ne-engine fetches the current build.
$ dpkg -l x0ne-engine 2>/dev/null | grep ^ii || echo "not installed"Limit: needs the published repository, which does not exist yet.
Not done yet
- Release key held offline, signing subkey on a hardware token. The generation, rotation and revocation procedure has code and 25 green tests on a branch (2026-10-13); no release key exists.
- Measured-boot VM image: code and tests on a branch, not on the main line.
- Corresponding-source archive per ISO: built by
make sources; its test suite is not green yet. - SBOM shipped with each release, snapshot-pinned builds for the plain VM and container images, arm64 images.
How to undo
Each point is a default. You can change any of them with sudo; the security model says which ones we think you should keep.