A site root that is itself a symlink passes the escape check and publishes its target #61
Labels
No labels
blocked
bug
decision
duplicate
enhancement
help wanted
invalid
landed?
question
ready-to-merge
stale
wontfix
bug
duplicate
enhancement
help wanted
invalid
question
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
allod/archetypes#61
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Refuse a site root that is itself a symlink, so the escape check cannot be bypassed by moving the link up one level.
The defect
checks/site-symlink-escape.shrefuses a symlink inside the site root but accepts a root that is a symlink pointing outside the store. Caddy's file server resolves symlinks at request time against the running host, so such a root publishes the linked directory from a public web root — the leak the script's own header says it exists to stop.Three things combine:
readlink -fintosite_root_real.find "$site_root" -type lreports the root itself when the root is a symlink.site_root_real, and for the root entry those are equal by construction, so it is classified as inside.The
[[ ! -d $site_root ]]guard on line 33 does not catch it either, because-dfollows the link.Reproduction
The second case is the one the fixtures cover; the first is not covered anywhere.
Why the other guards do not stop it
types.pathInStoreis lexical — path concatenation normalises..before the store-prefix test, but nothing resolves symlinks — so a caller settingsiteRoot = "${theirDerivation}/public"wherepublicis a symlink to/etcsatisfies it. The string carries store context, so the assertion inmodules/static-site.nixis satisfied too.nix/build-zola-site.nixdoes not apply: it scans the source tree of a site it builds itself and its output is always a real directory, whereas this check exists precisely for roots that builder never produced.checks/static-site.nixtests lexical../..traversal only.The result is that a derivation whose output is
ln -s /etc $out, or any generator emitting a symlinked subdirectory, passes the whole system build and serves the target publicly.README.md's claim that the system build refuses a symlink escaping the root is currently false for this shape.Fix shape
Refuse when the root does not resolve to itself —
readlink -f "$site_root"differing from"$site_root"— or require[[ ! -L $site_root ]]before the scan. Either way the fixture suite needs a case for a symlinked root, since its absence is why this survived.How it was found
An adversarial review pass over the merged files, asked specifically whether a symlink could get past both the build-time refusal and the run-time one. Reproduced independently before filing.
Independently reproduced by a second review pass using a different model, which described the same three-part mechanism:
findreports the root itself when the root is a symlink, and comparing that entry's resolved target against the resolved root makes them equal by construction. Two reviewers reaching the same finding from different starting points is the strongest evidence available that it is real rather than an artefact of one model's reading. The remaining findings from both passes are filed separately; this one is the only one with a security consequence.