TacidOS
Developers
Documentation

Decision 0095

95. A password is asked at installation, and the genome says where its hash is

Architecture decision record 0095: 95. A password is asked at installation, and the genome says where its hash is

Status
accepted
Date
2026-10-06

Refines ADR-0041 · ADR-0036 · ADR-0007

Context

Every machine this installer wrote had the same password. owner.nix carried initialHashedPassword for tacid, and that hash is published in genesis.rs. The owner is in wheel, so it was also the password for sudo.

The comment beside it said to change it on the first boot. That could not last. The root is made again at every boot (ADR-0036), /etc/shadow is not kept, and users.mutableUsers was never set. passwd changed a file that the next boot threw away. The password that opened the console, the greeter and sudo on every Tacid machine was therefore the one in the source, at every boot, whatever its owner had done about it.

SSH was not affected, because password logins are off wherever sshd runs. Everything else was.

Decision

The owner’s password is asked at installation, hashed where it was typed, and kept on the machine’s /persist. The genome names the file and never carries the hash.

  • tacid install --yes at a terminal asks twice, with nothing echoed. --password-file is for scripts and tests. With neither, the installation does not go ahead. The passphrase and the TPM PIN are asked the same way when their files are not given.
  • The CLI hashes the password with SHA-512 crypt (RustCrypto’s sha-crypt). What crosses the keeper’s socket is the $6$ string. The keeper scrubs it from everything it writes, as it does the passphrase.
  • A new guarded stem operation, PasswordWrite { mount, user, hash }, writes /persist/etc/tacid-passwords/<user>. The directory is mode 0700 and the file 0600, both root’s. The stem checks the hash’s shape independently of the keeper: no :, no line break, and one of $6$, $y$, $7$ or $2b$.
  • owner.nix says hashedPasswordFile = "/persist/etc/tacid-passwords/<user>". Every boot rebuilds /etc/shadow from that file, because every boot starts with no /etc/shadow to keep.
  • tacid password changes it later. The same operation runs with mount = "/", then chpasswd --encrypted applies it now, with the line on standard input. Only the owner may do this, and only for their own account.

Consequences

  • A genome can be published, shared or carried to another machine without carrying anything anyone can try passwords against.
  • passwd lasted only until the next reboot when this was written. ADR-0098 carries a change made with it to /persist. tacid password changes both at once.
  • The hash survives a reinstall of the genome only if /persist does. A genome carried to a new machine brings the file’s name and not its contents. The new machine’s installation asks again, which is the right moment to ask.
  • The reference hosts keep the published tacid. They are machines somebody boots from this tree to look at, not machines this code installs for a person.
  • The stem’s vocabulary grows by one guarded operation and one tool (chpasswd, from shadow, on every NixOS machine). Neither carries a command line from a caller.

Alternatives rejected

  • The hash in owner.nix. Simplest, and it puts an offline-crackable hash into a repository that is committed, read by people, and meant to travel. Changing it would also mean editing the one file Tacid never writes and signing the constitution again.
  • Keep tacid, force a change at first login, and persist /etc/shadow. It keeps a window in which every new machine has a published password. It also binds a file that NixOS’s activation rewrites, which is the same mistake this tree has already made with /etc/ssh.
  • users.mutableUsers = false. It changes nothing here, because there is no /etc/shadow to keep. It would also make passwd fail outright rather than last until a reboot.