Project a dev VM's own external SSH aliases onto devIdentities #34

Merged
vnprc merged 2 commits from agent/dev-vm-ssh-aliases into master 2026-09-20 14:49:07 +01:00
Member

The template's dev machine declares one external SSH alias of its own, and the identity that every dev VM is built from gains the field the framework reads for such aliases: devIdentities.<vm>.sshHosts, projected from devVMs.<vm>.sshHosts with the VM's own key and identitiesOnly filled in. On a running machine nothing changes until a deploy lock also carries the framework change (allod/archetypes#85); against the current framework the field is inert. The projection refuses, at evaluation and naming the machine and alias, anything that could capture or replace the VM's forge entry: an alias naming the forge host or any machine of the deployment (compared case-insensitively, as OpenSSH matches host patterns), an alias that is not one literal token, and an entry without a hostname. A machine that declares no aliases projects to an empty set, so a fork that predates the field composes unchanged. A new check proves each refusal fires on sabotaged input. Merge this before allod/archetypes#85: that branch then bumps its own secrets lock so its composed check sees the template alias. If it is wrong, revert this PR.

What changes

  • identity.nix: devVMs.allod-dev.sshHosts.example-build-cache (192.0.2.40, user cache, extraOptions.HostKeyAlias).
  • lib/dev-ssh-hosts.nix (new): reservedNames and project; exported as lib.projectDevSshHosts so a downstream fork calls the same rules rather than respelling them.
  • flake.nix: devIdentities.<vm>.sshHosts from the projection; the dev-ssh-hosts check.
  • README.md: the new check, the new export, and sshHosts in the lib.devIdentities row.

Risk

R2. A new validator on public template data with a new consumer in the framework; the refusals are the security-relevant part and each has a sabotage witness. The worst credible failure after validation is a deployment's real entry the projection rejects, which is a loud evaluation error naming the machine and alias at the lock bump. Rollback is a revert.

Validation

Run by the integrator at 255af16:

  • nix flake check exits 0 with dev-ssh-hosts among the built checks (worker run); the check was shown failing when one refusal's expectation was inverted, then reverted.
  • Each refusal was forced by hand with a fixture identity and produced its own message naming the machine and the alias: an upper-cased machine name, the forge host, *, a b, Host x, an entry with no hostname, an entry that is a string.
  • nix eval .#lib.devIdentities.allod-dev.sshHosts --json prints the entry with identityFile ~/.ssh/allod_vm and identitiesOnly true; the record's other keys are unchanged from master.
  • Deploy template with this branch and allod/archetypes#85 overriding together: allod-dev renders the alias beside its forge block, both keyed to ~/.ssh/allod_vm, and nexus is byte-identical to the current lock.

Cold cross-family review: gpt-5.6-sol via codex exec, reasoning effort xhigh, read this diff at 72e0bec together with the allod/archetypes#85 diff (Claude-family workers authored both). Three findings concerned this projection, all marked blocker, all confirmed and fixed in 255af16: extraOptions.header and newlines in string fields could open a block that captures the forge entry on the VM, now refused; aliases differing only by case both matched one host, now refused; the third, the framework's own fallback comparison, is fixed on the archetypes side. Each new refusal has a sabotage witness in the dev-ssh-hosts check. No second reading follows the fix round; the fixes are settled by the rebuilt check and the by-hand refusal texts.

Refs allod/archetypes#83
Refs allod/strategy#57

The template's dev machine declares one external SSH alias of its own, and the identity that every dev VM is built from gains the field the framework reads for such aliases: `devIdentities.<vm>.sshHosts`, projected from `devVMs.<vm>.sshHosts` with the VM's own key and `identitiesOnly` filled in. On a running machine nothing changes until a deploy lock also carries the framework change (allod/archetypes#85); against the current framework the field is inert. The projection refuses, at evaluation and naming the machine and alias, anything that could capture or replace the VM's forge entry: an alias naming the forge host or any machine of the deployment (compared case-insensitively, as OpenSSH matches host patterns), an alias that is not one literal token, and an entry without a hostname. A machine that declares no aliases projects to an empty set, so a fork that predates the field composes unchanged. A new check proves each refusal fires on sabotaged input. Merge this before allod/archetypes#85: that branch then bumps its own `secrets` lock so its composed check sees the template alias. If it is wrong, revert this PR. ## What changes - `identity.nix`: `devVMs.allod-dev.sshHosts.example-build-cache` (`192.0.2.40`, user `cache`, `extraOptions.HostKeyAlias`). - `lib/dev-ssh-hosts.nix` (new): `reservedNames` and `project`; exported as `lib.projectDevSshHosts` so a downstream fork calls the same rules rather than respelling them. - `flake.nix`: `devIdentities.<vm>.sshHosts` from the projection; the `dev-ssh-hosts` check. - `README.md`: the new check, the new export, and `sshHosts` in the `lib.devIdentities` row. ## Risk R2. A new validator on public template data with a new consumer in the framework; the refusals are the security-relevant part and each has a sabotage witness. The worst credible failure after validation is a deployment's real entry the projection rejects, which is a loud evaluation error naming the machine and alias at the lock bump. Rollback is a revert. ## Validation Run by the integrator at 255af16: - `nix flake check` exits 0 with `dev-ssh-hosts` among the built checks (worker run); the check was shown failing when one refusal's expectation was inverted, then reverted. - Each refusal was forced by hand with a fixture identity and produced its own message naming the machine and the alias: an upper-cased machine name, the forge host, `*`, `a b`, `Host x`, an entry with no hostname, an entry that is a string. - `nix eval .#lib.devIdentities.allod-dev.sshHosts --json` prints the entry with `identityFile` `~/.ssh/allod_vm` and `identitiesOnly` true; the record's other keys are unchanged from master. - Deploy template with this branch and allod/archetypes#85 overriding together: `allod-dev` renders the alias beside its forge block, both keyed to `~/.ssh/allod_vm`, and `nexus` is byte-identical to the current lock. Cold cross-family review: `gpt-5.6-sol` via `codex exec`, reasoning effort `xhigh`, read this diff at 72e0bec together with the allod/archetypes#85 diff (Claude-family workers authored both). Three findings concerned this projection, all marked blocker, all confirmed and fixed in 255af16: `extraOptions.header` and newlines in string fields could open a block that captures the forge entry on the VM, now refused; aliases differing only by case both matched one host, now refused; the third, the framework's own fallback comparison, is fixed on the archetypes side. Each new refusal has a sabotage witness in the `dev-ssh-hosts` check. No second reading follows the fix round; the fixes are settled by the rebuilt check and the by-hand refusal texts. Refs allod/archetypes#83 Refs allod/strategy#57
devVMs.<vm>.sshHosts declares the few external hosts a dev VM reaches
with its own key. lib/dev-ssh-hosts.nix projects that field onto
devIdentities.<vm>.sshHosts, defaulting identityFile to the VM's own
key and identitiesOnly to true, and refusing anything that could
capture or replace the forge entry the framework renders: a machine of
the deployment or the forge host (compared case-insensitively, as
OpenSSH matches Host patterns), an alias that is not one literal host
token, and an entry that is not an attribute set or carries no string
hostname.

The template's dev machine carries one synthetic entry so the
framework check has composed data to exercise. A fork whose identity
predates the field projects to an empty set.

The new dev-ssh-hosts check pins the defaults, the pass-through of an
entry's own fields, and one sabotage witness per refusal against a
fixture identity; it also reads the template's own projection.

Refs allod/archetypes#83
Refs allod/strategy#57
An alias check alone does not bound the block a dev VM's entry renders.
Home Manager's programs.ssh.settings submodule declares `header`, the
block's own Host/Match line, and the framework flattens extraOptions
into the block, so extraOptions.header = "Host <forge>" captures the
forge entry. A newline or carriage return inside any string in the
entry does the same by ending the directive line.

The projection now refuses an extraOptions key that is `header`, that
starts with `__`, or that is not a bare directive name; and any string
anywhere in the entry, walked through attrsets and lists, that carries
a newline or carriage return. It also refuses two aliases that lower-
case to the same string, since OpenSSH matches Host patterns
case-insensitively and would take whichever block it reads first.

Every message names the machine, the alias, and the offending key or
field path. The dev-ssh-hosts check gains one sabotage witness per new
refusal.

Refs allod/archetypes#83
Refs allod/strategy#57
vnprc approved these changes 2026-09-20 14:49:02 +01:00
vnprc merged commit 255af16a4e into master 2026-09-20 14:49:07 +01:00
vnprc deleted branch agent/dev-vm-ssh-aliases 2026-09-20 14:49:07 +01:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
allod/secrets!34
No description provided.