Make the static-site checks establish what they claim #65

Merged
vnprc merged 2 commits from agent/static-site-check-fixes into master 2026-09-02 07:08:52 +01:00
Member

Makes three static-site assertions establish what they claim, and deletes three rules that could not. Replaces allod/archetypes#64, which reached the same tree through eleven commits of back-and-forth; this branch is that tree in two commits and is byte-identical to it. ./check.sh passes: all 31 steps.

Net effect: the content check drops from 215 lines to 55, and every rule that remains has a fixture that goes red when the rule is removed.

What is fixed

The site root reached a shell unquoted. types.pathInStore accepts a store path containing a space, so siteRoot = "${site}/public files" produced a command that word-split and tried to execute files. Module and expectation both use lib.escapeShellArg now.

That expectation was also not load-bearing, which is the more interesting half: escapeShellArg is the identity for a clean store path and the fixture root is one, so reverting the module left the comparison green. The check now evaluates the module with a root that actually needs quoting and asserts the escaped spelling is present and the bare one is not.

The closed-port assertion could not fail for the right reason. pages.fail("ss -ltnH 'sport = :22' | grep -q .") is satisfied when ss errors on a malformed filter — grep matches nothing, the pipeline fails, and the subtest records port 22 as closed without having asked. It now asserts ss succeeds and returns nothing, with a positive control on the administrative port, because an expression ss silently ignores would also return nothing.

What is deleted, and why deletion rather than repair

The title rule could not be written. Three versions, each wrong in both directions — accepting a title inside a comment or a <textarea>, rejecting a real title containing < or preceded by <!-- in an attribute. Deciding whether text is an element is a parse, not a match.

The immutability rule could never fire. siteRoot is always a store path and store paths are r-xr-xr-x by construction. The only trees that tripped it were the fixtures built to trip it.

The reference scan caught only root-relative links. missing.png, ./missing.png and ../gone.html all passed. It was the most intricate code in the file and produced two defects of its own while being fixed: xargs reporting 123 for a command that exited 1, and case-sensitive --include globs that silently stopped scanning PAGE.HTML.

What survives is the rule whose failure is both silent and severe: a symlink escaping the site root, which Caddy resolves at request time against the host's own filesystem. allod/memory testing.md's deletion rule is the standard applied — drop a check when a cheaper witness pins the same property. A live site pins a missing title and a broken link the first time anyone looks; nothing but this pins the escape.

Not fixed, deliberately

Item 4 of the issue asks the string-context assertion to check whose context it is. That is not achievable at evaluation time: builtins.getContext "${drv}" names the derivation path, textually unrelated to the output path the string spells, and mapping one to the other requires building. Established by writing the stronger assertion and watching it reject the legitimate site root. The earlier suggestion of types.package was itself wrong — lib.types.package.check accepts a plain store-path string — and the module records both facts.

Risk

R1. Deletions from a build-time check plus two assertion corrections. No machine configuration changes. Rollback is a straight revert.

Human scrutiny worth spending: whether the three deleted rules are ones you want gone. Each is defensible on its own and the reasoning is in the commit messages, but they are judgement calls rather than defects.

Closes allod/archetypes#62

Makes three static-site assertions establish what they claim, and deletes three rules that could not. Replaces `allod/archetypes#64`, which reached the same tree through eleven commits of back-and-forth; this branch is that tree in two commits and is byte-identical to it. `./check.sh` passes: all 31 steps. Net effect: the content check drops from 215 lines to 55, and every rule that remains has a fixture that goes red when the rule is removed. ## What is fixed **The site root reached a shell unquoted.** `types.pathInStore` accepts a store path containing a space, so `siteRoot = "${site}/public files"` produced a command that word-split and tried to execute `files`. Module and expectation both use `lib.escapeShellArg` now. That expectation was also not load-bearing, which is the more interesting half: `escapeShellArg` is the identity for a clean store path and the fixture root is one, so reverting the module left the comparison green. The check now evaluates the module with a root that actually needs quoting and asserts the escaped spelling is present and the bare one is not. **The closed-port assertion could not fail for the right reason.** `pages.fail("ss -ltnH 'sport = :22' | grep -q .")` is satisfied when `ss` errors on a malformed filter — grep matches nothing, the pipeline fails, and the subtest records port 22 as closed without having asked. It now asserts `ss` succeeds and returns nothing, with a positive control on the administrative port, because an expression `ss` silently ignores would also return nothing. ## What is deleted, and why deletion rather than repair **The title rule could not be written.** Three versions, each wrong in both directions — accepting a title inside a comment or a `<textarea>`, rejecting a real title containing `<` or preceded by `<!--` in an attribute. Deciding whether text is an element is a parse, not a match. **The immutability rule could never fire.** `siteRoot` is always a store path and store paths are `r-xr-xr-x` by construction. The only trees that tripped it were the fixtures built to trip it. **The reference scan caught only root-relative links.** `missing.png`, `./missing.png` and `../gone.html` all passed. It was the most intricate code in the file and produced two defects of its own while being fixed: `xargs` reporting 123 for a command that exited 1, and case-sensitive `--include` globs that silently stopped scanning `PAGE.HTML`. What survives is the rule whose failure is both silent and severe: a symlink escaping the site root, which Caddy resolves at request time against the host's own filesystem. `allod/memory` testing.md's deletion rule is the standard applied — drop a check when a cheaper witness pins the same property. A live site pins a missing title and a broken link the first time anyone looks; nothing but this pins the escape. ## Not fixed, deliberately Item 4 of the issue asks the string-context assertion to check *whose* context it is. That is not achievable at evaluation time: `builtins.getContext "${drv}"` names the derivation path, textually unrelated to the output path the string spells, and mapping one to the other requires building. Established by writing the stronger assertion and watching it reject the legitimate site root. The earlier suggestion of `types.package` was itself wrong — `lib.types.package.check` accepts a plain store-path string — and the module records both facts. ## Risk R1. Deletions from a build-time check plus two assertion corrections. No machine configuration changes. Rollback is a straight revert. Human scrutiny worth spending: whether the three deleted rules are ones you want gone. Each is defensible on its own and the reasoning is in the commit messages, but they are judgement calls rather than defects. Closes allod/archetypes#62
Three assertions passed without checking the thing they named.

The site root reached a shell unquoted. `types.pathInStore` accepts a store
path containing a space, so `siteRoot = "${site}/public files"` produced a
command that word-split and tried to execute `files`. Both the module and the
expectation that pins it now use `lib.escapeShellArg`.

That expectation was also not load-bearing: `escapeShellArg` is the identity
for a clean store path, and the fixture root is one, so reverting the module
left the comparison green. The check now also evaluates the module with a root
that needs quoting and asserts the generated command contains the escaped
spelling and not the bare one.

The closed-port assertion could not fail for the right reason. `pages.fail("ss
-ltnH 'sport = :22' | grep -q .")` is satisfied when `ss` errors on a malformed
filter: grep matches nothing, the pipeline fails, and the subtest records port
22 as closed without having asked. It now asserts `ss` succeeds and returns
nothing, plus a positive control on the administrative port, since an
expression `ss` silently ignores would also return nothing.

The string-context assertion is deliberately unchanged. Checking that the
context names the path being served is not achievable at evaluation time:
`builtins.getContext "${drv}"` names the derivation path, textually unrelated
to the output path the string spells, and mapping one to the other requires
building. The module records that, and records that `types.package` is not the
fix — `lib.types.package.check` accepts a plain store-path string.

Refs allod/archetypes#62
Three rules are deleted. Each looked worth having and none was.

The title rule could not be written. Three versions were tried and every one
was wrong in both directions: `grep -q '<title>'` accepted `<!-- <title> -->`;
requiring a closing tag accepted `<!-- <title>Fake</title> -->`; stripping
comments first still accepted a title inside a `<textarea>` and one behind the
`--!>` terminator, while rejecting a real title containing `<` and one preceded
by `<!--` in an attribute value. Deciding whether text is an element is a parse,
not a match. `xmllint --html --xpath 'count(//title)'` would settle it, and the
file says so for anyone who needs the rule later.

The immutability rule could never fire. `siteRoot` is `types.pathInStore`, so
it is always a store path, and store paths are `r-xr-xr-x` by construction. The
only trees that ever tripped it were the fixtures built to trip it.

The reference scan caught real broken links, but only root-relative ones:
`missing.png`, `./missing.png` and `../gone.html` all passed. It was the most
intricate code here by a wide margin and produced two defects of its own while
being fixed — `xargs` reporting 123 for a command that exited 1, which made a
reference-free site indistinguishable from a broken scan, and case-sensitive
`--include` globs that silently stopped scanning `PAGE.HTML`. A broken link is
also a visible failure: a 404 the first time anyone follows it.

What survives is the rule whose failure is both silent and severe. A symlink
escaping the site root is resolved by Caddy at request time against the host's
own filesystem, so `leak -> /etc` answers `GET /leak/passwd` from a public web
server and nothing about the running site looks wrong until someone asks.

`allod/memory` testing.md's deletion rule is the standard applied: drop a check
when a cheaper witness pins the same property. A live site pins a missing title
and a broken link; nothing but this pins the escape.

Refs allod/archetypes#62
vnprc approved these changes 2026-09-02 07:08:42 +01:00
vnprc merged commit 125899ec85 into master 2026-09-02 07:08:52 +01:00
vnprc deleted branch agent/static-site-check-fixes 2026-09-02 07:08:53 +01:00
Sign in to join this conversation.
No description provided.