Add a static-site host layer #58

Merged
vnprc merged 3 commits from agent/static-site-host into master 2026-08-31 00:42:27 +01:00
Member

This adds a reusable static-site host layer on the existing public-host base: consumers provide a hostname and store-backed built directory, while archetypes provides a Zola builder and shared content checks. The publicHost base still exclusively owns Caddy/ACME, SSH, fail2ban, and the firewall; no machine or service builder is added. ./check.sh passes all 31 steps at e6a3ee4, including a first-boot VM that compares served bytes to the build and logs in on port 22022 while 22 stays closed. Rollback is a straight revert; these commits create no persistent or deployed state.

Shape

  • Export nixosModules.staticSite, which imports publicHost and contributes one directory-backed Caddy virtual host. siteRoot uses the normalized in-store path type and separately requires string context, while a system build scans any caller-supplied root for escaping symlinks and fails closed if the root is absent or unenumerable.
  • Export lib.buildZolaSite { pkgs, websiteSrc }, retaining the source-tree symlink refusal from the parts bin.
  • Carry over the content scanner, sabotage fixtures, and shared symlink-escape scanner without simplifying their cases. Additional sabotage watches missing roots, directory targets without an index, and writable entries fail.
  • Add a NixOS VM witness for serving on first boot, byte-for-byte content identity, operator-key SSH on the administrative port, rejection of another key, and absence of SSH on 22.

Risk

R3 — this touches public web serving, administrative authentication composition, and the symlink boundary that prevents a public web root from exposing host files. It does not deploy a host or mutate persistent state.

Independent review

  • Claude Opus 5 at xhigh found two high-severity validation bypasses in ea88281; both are repaired in e6a3ee4 with explicit regression witnesses.
  • Claude Fable 5 at xhigh then reviewed origin/master...e6a3ee4, reproduced the former traversal bypass, verified both repairs against pinned nixpkgs and built checks, and reported no blocker remaining.

Validation

  • ./check.sh — all 31 enumerated steps passed at e6a3ee4.
  • nix build -L .#checks.x86_64-linux.site-content — sabotage and control suite passed.
  • nix build -L .#checks.x86_64-linux.static-site — first-boot serving and SSH VM passed.

Refs allod/archetypes#55
Refs allod/archetypes#56
Refs allod/archetypes#57

This adds a reusable static-site host layer on the existing public-host base: consumers provide a hostname and store-backed built directory, while archetypes provides a Zola builder and shared content checks. The publicHost base still exclusively owns Caddy/ACME, SSH, fail2ban, and the firewall; no machine or service builder is added. `./check.sh` passes all 31 steps at `e6a3ee4`, including a first-boot VM that compares served bytes to the build and logs in on port 22022 while 22 stays closed. Rollback is a straight revert; these commits create no persistent or deployed state. ## Shape - Export `nixosModules.staticSite`, which imports `publicHost` and contributes one directory-backed Caddy virtual host. `siteRoot` uses the normalized in-store path type and separately requires string context, while a system build scans any caller-supplied root for escaping symlinks and fails closed if the root is absent or unenumerable. - Export `lib.buildZolaSite { pkgs, websiteSrc }`, retaining the source-tree symlink refusal from the parts bin. - Carry over the content scanner, sabotage fixtures, and shared symlink-escape scanner without simplifying their cases. Additional sabotage watches missing roots, directory targets without an index, and writable entries fail. - Add a NixOS VM witness for serving on first boot, byte-for-byte content identity, operator-key SSH on the administrative port, rejection of another key, and absence of SSH on 22. ## Risk R3 — this touches public web serving, administrative authentication composition, and the symlink boundary that prevents a public web root from exposing host files. It does not deploy a host or mutate persistent state. ## Independent review - Claude Opus 5 at xhigh found two high-severity validation bypasses in `ea88281`; both are repaired in `e6a3ee4` with explicit regression witnesses. - Claude Fable 5 at xhigh then reviewed `origin/master...e6a3ee4`, reproduced the former traversal bypass, verified both repairs against pinned nixpkgs and built checks, and reported no blocker remaining. ## Validation - `./check.sh` — all 31 enumerated steps passed at `e6a3ee4`. - `nix build -L .#checks.x86_64-linux.site-content` — sabotage and control suite passed. - `nix build -L .#checks.x86_64-linux.static-site` — first-boot serving and SSH VM passed. Refs allod/archetypes#55 Refs allod/archetypes#56 Refs allod/archetypes#57
Author
Member

Independent adversarial review requested after opening: Claude Opus 5 at xhigh effort reviewed origin/master...ea88281 read-only.

It found two high-severity paths that need repair before merge: the finished-tree scanner accepts a nonexistent/un-enumerable root because the process-substitution find status is lost, and the siteRoot prefix/context assertions accept both .. traversal and context borrowed from an unrelated derivation. Together those can leave Caddy rooted outside the intended store tree while the build stays green.

It also found that the static-site composition is not pinned against acquiring machine-wide firewall/SSH ownership, the module check is pinned only by derivation name rather than generated command, and the directory-without-index and writable-tree scanner rules have no sabotage fixtures. I am verifying these against the pinned nixpkgs and will amend the PR for every confirmed finding, then rerun ./check.sh.

Independent adversarial review requested after opening: Claude Opus 5 at xhigh effort reviewed `origin/master...ea88281` read-only. It found two high-severity paths that need repair before merge: the finished-tree scanner accepts a nonexistent/un-enumerable root because the process-substitution `find` status is lost, and the `siteRoot` prefix/context assertions accept both `..` traversal and context borrowed from an unrelated derivation. Together those can leave Caddy rooted outside the intended store tree while the build stays green. It also found that the static-site composition is not pinned against acquiring machine-wide firewall/SSH ownership, the module check is pinned only by derivation name rather than generated command, and the directory-without-index and writable-tree scanner rules have no sabotage fixtures. I am verifying these against the pinned nixpkgs and will amend the PR for every confirmed finding, then rerun `./check.sh`.
Author
Member

Follow-up on the independent review above: every confirmed finding is repaired in e6a3ee4.

  • siteRoot now uses nixpkgs pathInStore, which normalizes and rejects store-prefixed .. traversal; the separate string-context assertion remains.
  • The shared output scanner rejects missing roots and propagates find failures instead of losing them through process substitution.
  • The static-site contract now pins the composed firewall, SSH, fail2ban, Caddy/ACME, vhost, empty service-port contribution, and exact system-check command.
  • Sabotage fixtures now watch absent roots, directory targets without a served index, and writable content fail; the VM explicitly proves ss exists before checking port 22.

./check.sh passed all 31 steps at e6a3ee4. A second independent pass with Claude Fable 5 at xhigh reproduced the old traversal weakness against the former implementation, verified the repairs against the pinned nixpkgs and built checks, and concluded: No blocker remains. Its remaining notes were low residual test-depth observations, not defects.

Follow-up on the independent review above: every confirmed finding is repaired in `e6a3ee4`. - `siteRoot` now uses nixpkgs `pathInStore`, which normalizes and rejects store-prefixed `..` traversal; the separate string-context assertion remains. - The shared output scanner rejects missing roots and propagates `find` failures instead of losing them through process substitution. - The static-site contract now pins the composed firewall, SSH, fail2ban, Caddy/ACME, vhost, empty service-port contribution, and exact system-check command. - Sabotage fixtures now watch absent roots, directory targets without a served index, and writable content fail; the VM explicitly proves `ss` exists before checking port 22. `./check.sh` passed all 31 steps at `e6a3ee4`. A second independent pass with Claude Fable 5 at xhigh reproduced the old traversal weakness against the former implementation, verified the repairs against the pinned nixpkgs and built checks, and concluded: **No blocker remains.** Its remaining notes were low residual test-depth observations, not defects.
vnprc approved these changes 2026-08-31 00:42:19 +01:00
vnprc merged commit e6a3ee43d8 into master 2026-08-31 00:42:27 +01:00
vnprc deleted branch agent/static-site-host 2026-08-31 00:42:27 +01:00
Sign in to join this conversation.
No description provided.